From 5b4b19a1c82a33b4c636b05d018555d080dacf1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 28 Jan 2025 14:54:27 +0100 Subject: [PATCH 01/19] add cut_with_plane and new clip method --- .../Polygon_mesh_processing/cut_with_plane.h | 649 ++++++++++++++++++ .../Polygon_mesh_processing/data-clip/c.off | 11 + .../Polygon_mesh_processing/data-clip/ee.off | 21 + .../Polygon_mesh_processing/test_pmp_clip.cpp | 54 ++ .../internal/parameters_interface.h | 3 + 5 files changed, 738 insertions(+) create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/data-clip/c.off create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/data-clip/ee.off diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h new file mode 100644 index 00000000000..d587e86ee97 --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h @@ -0,0 +1,649 @@ +// Copyright (c) 2024 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) : Sébastien Loriot + + +#ifndef CGAL_POLYGON_MESH_PROCESSING_CUT_WITH_PLANE_H +#define CGAL_POLYGON_MESH_PROCESSING_CUT_WITH_PLANE_H + +#include + +#include +#include +#include +#include +#ifndef CGAL_PLANE_CLIP_DO_NOT_USE_TRIANGULATION +#include +#endif +#ifndef CGAL_PLANE_CLIP_DO_NOT_USE_BOX_INTERSECTION_D +#include +#endif + +namespace CGAL { +namespace Polygon_mesh_processing { + +template +struct Default_cut_visitor +{ + void before_edge_split(typename boost::graph_traits::halfedge_descriptor, PolygonMesh&) {} + void edge_split(typename boost::graph_traits::halfedge_descriptor, PolygonMesh&) {} + void vertices_on_cut(const std::vector::vertex_descriptor>&, PolygonMesh&){} +}; + +// TODO: doc me or hide me in the np +template +struct Orthogonal_cut_plane_traits +{ + using FT = typename Kernel::FT; + using Plane_3 = std::pair; + using Point_3 = typename Kernel::Point_3; + + struct Oriented_side_3 + { + Oriented_side operator()(const Plane_3& plane, const Point_3& p) const + { + if (p[plane.first]==plane.second) return ON_ORIENTED_BOUNDARY; + return p[plane.first] coords; + for (int i=0;i<3;++i) + { + if (i==plane.first) + coords[i] = plane.second; + else + coords[i] = (p[i]==q[i])?p[i]:p[i]*alpha +(1-alpha)*q[i]; + } + return Point_3(coords[0], coords[1], coords[2]); + } + }; + + Oriented_side_3 oriented_side_3_object() const + { + return Oriented_side_3(); + } + + Construct_plane_line_intersection_point_3 construct_plane_line_intersection_point_3_object() const + { + return Construct_plane_line_intersection_point_3(); + } + +#ifndef CGAL_PLANE_CLIP_DO_NOT_USE_BOX_INTERSECTION_D +// for does self-intersect + using Segment_3 = typename Kernel::Segment_3; + using Triangle_3 = typename Kernel::Triangle_3; + using Construct_segment_3 = typename Kernel::Construct_segment_3; + using Construct_triangle_3 =typename Kernel::Construct_triangle_3; + using Do_intersect_3 = typename Kernel::Do_intersect_3; + Construct_segment_3 construct_segment_3_object() const { return Construct_segment_3(); } + Construct_triangle_3 construct_triangle_3_object() const { return Construct_triangle_3(); } + Do_intersect_3 do_intersect_3_object() const { return Do_intersect_3(); } +#endif +}; + +/*! + * \ingroup PMP_corefinement_grp + * + * refines `pm` by inserting the intersection points of `plane` with its edges and faces. + * + * \tparam PolygonMesh a model of `HalfedgeListGraph`, `FaceListGraph`, and `MutableFaceGraph` + * \tparam Plane_3 plane type, equal to `GeomTraits::Plane_3`, `GeomTraits` being the type of the parameter `geom_traits`. + * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" + * + * \param pm input mesh to be refined + * \param plane the plane used to refine the mesh + * \param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below: + * + * \cgalNamedParamsBegin + * + * \cgalParamNBegin{concurrency_tag} + * \cgalParamDescription{a tag indicating if the task should be done using one or several threads.} + * \cgalParamType{Either `CGAL::Sequential_tag`, or `CGAL::Parallel_tag`, or `CGAL::Parallel_if_available_tag`} + * \cgalParamDefault{`CGAL::Sequential_tag`} + * \cgalParamNEnd + * + * \cgalParamNBegin{edge_is_constrained_map} + * \cgalParamDescription{a property map containing the constrained-or-not status of each edge of `pm`. + * If an edge marked as constrained is split, the two resulting edges will be marked as constrained.} + * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits::%edge_descriptor` + * as key type and `bool` as value type} + * \cgalParamDefault{a constant property map returning `false` for any edge} + * \cgalParamNEnd + * + * \cgalParamNBegin{edge_is_marked_map} + * \cgalParamDescription{a property map filled by this function that puts `true` for all intersection edge of faces + * of `pm` and `plane`, and `false` for all other edges.} + * \cgalParamType{a class model of `WritablePropertyMap` with `boost::graph_traits::%edge_descriptor` + * as key type and `bool` as value type} + * \cgalParamDefault{a constant property map returning `false` for any edge} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_oriented_side_map} + * \cgalParamDescription{a property map filled by this function containing the position + * of each vertex relative to the oriented plane `plane`.} + * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits::%vertex_descriptor` + * as key type and `Oriented_side` as value type} + * \cgalParamDefault{Dynamic vertex property map} + * \cgalParamExtra{If the concurrenty tag is set to `Parallel_tag`, the property map might be filled by several thread at the same time.} + * \cgalParamNEnd + * + * \cgalParamNBegin{visitor} + * \cgalParamDescription{TODO add concept} + * \cgalParamType{reference wrapper recommeded if it has state TODO} + * \cgalParamDefault{None} + * \cgalParamNEnd + * + * \cgalParamNBegin{do_not_triangulate_faces} + * \cgalParamDescription{If the input mesh is triangulated and this parameter is set to `false`, the mesh will be kept triangulated.} + * \cgalParamType{`bool`} + * \cgalParamDefault{`true`} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_point_map} + * \cgalParamDescription{a property map associating points to the vertices of `tm_in`} + * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits::%vertex_descriptor` + * as key type and `GeomTraits::Point_3` as value type, `GeomTraits` being the type of the parameter `geom_traits`} + * \cgalParamDefault{`boost::get(CGAL::vertex_point, pm)`} + * \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` must be available in `PolygonMesh`.} + * \cgalParamNEnd + * + * \cgalParamNBegin{geom_traits} + * \cgalParamDescription{an instance of a geometric traits class} + * \cgalParamType{a class model of `Kernel`} + * \cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`} + * \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.} + * \cgalParamNEnd + * + * \cgalNamedParamsEnd + */ +template +void cut_with_plane(PolygonMesh& pm, + const Plane_3& plane, + const NamedParameters& np = parameters::default_values()) +{ + // TODO: concurrency tag + // TODO: if you want to clip with many planes (**Kernel**), + // it might be interesting to first classify all vertices with all planes + // to limit the number of intersection points computed: several classify done lazily + // on points not already eliminated (verices all out with adjacent vertices out too) + // actually might be a global classifier to filter out edges, testing all planes at once per vertex and stop as soon as one is out + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::get_parameter_reference; + using parameters::is_default_parameter; + + // graph typedefs + using BGT = boost::graph_traits; + using face_descriptor = typename BGT::face_descriptor; + using edge_descriptor = typename BGT::edge_descriptor; + using halfedge_descriptor = typename BGT::halfedge_descriptor; + using vertex_descriptor = typename BGT::vertex_descriptor; + + // np typedefs + using Default_ecm = Static_boolean_property_map; + using Default_visitor = Default_cut_visitor; + using Visitor_ref = typename internal_np::Lookup_named_param_def::reference; + using GT = typename GetGeomTraits::type; + GT traits = choose_parameter(get_parameter(np, internal_np::geom_traits)); + + static_assert(std::is_same_v); + + auto ecm = choose_parameter(get_parameter(np, internal_np::edge_is_constrained)); + auto edge_is_marked = choose_parameter(get_parameter(np, internal_np::edge_is_marked_map)); + + Default_visitor default_visitor; + Visitor_ref visitor = choose_parameter(get_parameter_reference(np, internal_np::visitor), default_visitor); + auto vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), + get_property_map(vertex_point, pm)); + + bool triangulate = !choose_parameter(get_parameter(np, internal_np::do_not_triangulate_faces), true); + if (triangulate && !is_triangle_mesh(pm)) + triangulate = false; + + bool throw_on_self_intersection = choose_parameter(get_parameter(np, internal_np::use_compact_clipper), false); + if (throw_on_self_intersection && !is_triangle_mesh(pm)) + throw_on_self_intersection = false; + + typedef typename internal_np::Lookup_named_param_def < + internal_np::concurrency_tag_t, + NamedParameters, + Sequential_tag + > ::type Concurrency_tag; + + // constexpr bool parallel_execution = std::is_same_v; + + auto oriented_side = traits.oriented_side_3_object(); + auto intersection_point = traits.construct_plane_line_intersection_point_3_object(); + + // TODO: the default is not thread-safe for example for Polyhedron + using V_os_tag = dynamic_vertex_property_t; + static constexpr bool use_default_vosm = + is_default_parameter::value; + + using Vertex_oriented_side_map = + std::conditional_t::type, + typename internal_np::Get_param::type>; + + + Vertex_oriented_side_map vertex_os; + if constexpr (use_default_vosm) + vertex_os = get(V_os_tag(), pm); + else + vertex_os = get_parameter(np, internal_np::vertex_oriented_side_map); + + std::vector inters; + + bool all_in = true; + bool all_out = true; + bool at_least_one_on = false; + std::vector on_obnd; + //TODO: parallel for + for (vertex_descriptor v : vertices(pm)) + { + Oriented_side os = oriented_side(plane, get(vpm, v)); + put(vertex_os,v,os); + switch(os) + { + case ON_POSITIVE_SIDE: + all_in = false; + break; + case ON_NEGATIVE_SIDE: + all_out = false; + break; + case ON_ORIENTED_BOUNDARY: + at_least_one_on=true; + on_obnd.push_back(v); + } + } + + if (at_least_one_on || (!all_in && !all_out)) + { + //TODO: parallel for + for(edge_descriptor e : edges(pm)) + { + vertex_descriptor src = source(e,pm), tgt = target(e,pm); + if (get(vertex_os, src)==CGAL::ON_ORIENTED_BOUNDARY) + { + if (get(vertex_os, tgt)==CGAL::ON_ORIENTED_BOUNDARY) + { + bool pure_coplanar=true; + if (!is_border(e, pm)) + { + halfedge_descriptor he=halfedge(e, pm); + for (halfedge_descriptor h : halfedges_around_face(he,pm)) + if (get(vertex_os,target(h, pm))!=CGAL::ON_ORIENTED_BOUNDARY) + { + pure_coplanar=false; + break; + } + if (pure_coplanar) + { + he=opposite(he, pm); + for (halfedge_descriptor h : halfedges_around_face(he,pm)) + if (get(vertex_os, target(h, pm))!=CGAL::ON_ORIENTED_BOUNDARY) + { + pure_coplanar=false; + break; + } + } + } + if (!pure_coplanar) + put(edge_is_marked, e, true); + } + } + else + if (get(vertex_os, tgt)!=CGAL::ON_ORIENTED_BOUNDARY && + get(vertex_os, src)!=get(vertex_os, tgt)) + inters.push_back(e); + } + } + + if (all_in || all_out) + { + visitor.vertices_on_cut(on_obnd, pm); + return; + } + + std::unordered_map > splitted_faces; + + if (throw_on_self_intersection) + { + std::vector test_faces; + for (edge_descriptor e : inters) + { + halfedge_descriptor h = halfedge(e, pm); + if (!is_border(h,pm)) + test_faces.push_back(face(h,pm)); + h=opposite(h, pm); + if (!is_border(h,pm)) + test_faces.push_back(face(h,pm)); + } + std::sort(test_faces.begin(), test_faces.end()); + auto last = std::unique(test_faces.begin(), test_faces.end()); + test_faces.erase(last, test_faces.end()); + if (does_self_intersect(test_faces, pm, np)) + throw std::runtime_error("TODO Corefinement::Self_intersection_exception"); + } + + //TODO: parallel for + for (edge_descriptor e : inters) + { + halfedge_descriptor h = halfedge(e, pm); + auto pts = CGAL::make_sorted_pair(get(vpm, source(h,pm)), get(vpm, target(h, pm))); + typename GT::Point_3 ip = intersection_point(plane, pts.first, pts.second); + + bool was_marked = get(ecm, edge(h, pm)); + visitor.before_edge_split(h, pm); + h = CGAL::Euler::split_edge(h, pm); + put(vpm, target(h, pm), ip); + put(vertex_os, target(h, pm), ON_ORIENTED_BOUNDARY); + visitor.edge_split(h, pm); + if (was_marked) + put(ecm, edge(h, pm), true); + + if (!is_border(h, pm)) + splitted_faces[face(h, pm)].push_back(h); + h=prev(opposite(h,pm),pm); + if (!is_border(h, pm)) + splitted_faces[face(h, pm)].push_back(h); + } + + visitor.vertices_on_cut(on_obnd, pm); + + // collect faces to be cut that have one vertex on the cut plane + for (vertex_descriptor v : on_obnd) + { + halfedge_descriptor hv = halfedge(v, pm); + for (halfedge_descriptor h : halfedges_around_target(hv, pm)) + { + if (is_border(h, pm)) continue; + Oriented_side prev_ori = get(vertex_os, source(h, pm)), + next_ori = get(vertex_os, target(next(h, pm), pm)); + if ( prev_ori == ON_ORIENTED_BOUNDARY || next_ori == ON_ORIENTED_BOUNDARY) continue; // skip full edge + if (prev_ori!=next_ori) splitted_faces[face(h, pm)].push_back(h); // skip tangency point + } + } + + //TODO: parallel for + for (std::pair>& f_and_hs : splitted_faces) + { + std::size_t nb_hedges = f_and_hs.second.size(); + + CGAL_assertion( nb_hedges%2 ==0 ); + + if (nb_hedges==2) + { + halfedge_descriptor h1=f_and_hs.second[0], h2=f_and_hs.second[1]; + CGAL_assertion(next(h1,pm)!=h2 && next(h2,pm)!=h1); // the edge does not already exist + halfedge_descriptor res = CGAL::Euler::split_face(h1, h2, pm); + put(edge_is_marked, edge(res, pm), true); + + if (triangulate) + { + if (!is_triangle(res, pm)) + { + // TODO: take the criteria in triangulate_faces ? + halfedge_descriptor newh = + CGAL::Euler::split_face(res, next(next(res, pm), pm), pm); + put(edge_is_marked, edge(newh, pm), false); + } + else + { + res = opposite(res, pm); + if (!is_triangle(res, pm)) + { + // TODO: take the criteria in triangulate_faces ? + halfedge_descriptor newh = + CGAL::Euler::split_face(res, next(next(res, pm), pm), pm); + put(edge_is_marked, edge(newh, pm), false); + } + } + } + } + else + { + // sort hedges to make them match + CGAL_assertion(!triangulate); + // TODO: need mechanism to make it robust even with EPICK + auto less_hedge = [&pm, vpm](halfedge_descriptor h1, halfedge_descriptor h2) + { + return lexicographically_xyz_smaller(get(vpm,target(h1,pm)), get(vpm,target(h2,pm))); + }; + std::sort(f_and_hs.second.begin(), f_and_hs.second.end(), less_hedge); + + for (std::size_t i=0; i::%vertex_descriptor` + * as key type and `%Point_3` as value type} + * \cgalParamDefault{`boost::get(CGAL::vertex_point, tm)`} + * \cgalParamNEnd + * + * \cgalParamNBegin{visitor} + * \cgalParamDescription{a visitor used to track the creation of new faces} + * \cgalParamType{a class model of `PMPCorefinementVisitor`} + * \cgalParamDefault{`Corefinement::Default_visitor`} + * \cgalParamNEnd + * + * \cgalParamNBegin{throw_on_self_intersection} + * \cgalParamDescription{If `true`, the set of triangles close to the intersection of `tm` + * and `plane` will be checked for self-intersections + * and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` + * will be thrown if at least one self-intersection is found. Always `false` if `pm` is not a triangle mesh.} + * \cgalParamType{Boolean} + * \cgalParamDefault{`false`} + * \cgalParamNEnd + * + * \cgalParamNBegin{clip_volume} + * \cgalParamDescription{If `true`, and `tm` is closed, the clipping will be done on + * the volume \link coref_def_subsec bounded \endlink by `tm` + * rather than on its surface (i.e., `tm` will be kept closed).} + * \cgalParamType{Boolean} + * \cgalParamDefault{`false`} + * \cgalParamNEnd + * + * \cgalParamNBegin{use_compact_clipper} + * \cgalParamDescription{if `false` the parts of `tm` coplanar with `plane` will not be part of the output} + * \cgalParamType{Boolean} + * \cgalParamDefault{`true`} + * \cgalParamNEnd + * + * \cgalParamNBegin{allow_self_intersections} + * \cgalParamDescription{If `true`, self-intersections are accepted for `tm`.} + * \cgalParamType{Boolean} + * \cgalParamDefault{`false`} + * \cgalParamExtra{If this option is set to `true`, `tm` is no longer required to be without self-intersection. + * Setting this option to `true` will automatically set `throw_on_self_intersection` to `false` + * and `clip_volume` to `false`.} + * \cgalParamNEnd + * + * \cgalParamNBegin{do_not_triangulate_faces} + * \cgalParamDescription{If the input mesh is triangulated and this parameter is set to `false`, the mesh will be kept triangulated. + * Always `true` if `pm` is not a triangle mesh.} + * \cgalParamType{`bool`} + * \cgalParamDefault{`true`} + * \cgalParamNEnd + * + * \cgalNamedParamsEnd + * + * @return `true` if the output surface mesh is manifold. + * If `false` is returned `tm` is only refined by the intersection with `plane`. + * + * @see `split()` + */ +template +void new_clip(PolygonMesh& pm, +#ifdef DOXYGEN_RUNNING + const Plane_3& plane, +#else + const typename GetGeomTraits::type::Plane_3& plane, +#endif + const NamedParameters& np = parameters::default_values()) +{ + using parameters::choose_parameter; + using parameters::get_parameter; + + using face_descriptor = typename boost::graph_traits::face_descriptor; + using edge_descriptor = typename boost::graph_traits::edge_descriptor; + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; + using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; + + using GT = typename GetGeomTraits::type; + GT traits = choose_parameter(get_parameter(np, internal_np::geom_traits)); + auto vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), + get_property_map(vertex_point, pm)); + + typedef typename internal_np::Lookup_named_param_def < + internal_np::concurrency_tag_t, + NamedParameters, + Sequential_tag + > ::type Concurrency_tag; + + // config flags + bool clip_volume = + parameters::choose_parameter(parameters::get_parameter(np, internal_np::clip_volume), false); + bool use_compact_clipper = + choose_parameter(get_parameter(np, internal_np::use_compact_clipper), true); + const bool throw_on_self_intersection = + choose_parameter(get_parameter(np, internal_np::use_compact_clipper), false); + const bool allow_self_intersections = + choose_parameter(get_parameter(np, internal_np::allow_self_intersections), false); + bool triangulate = !choose_parameter(get_parameter(np, internal_np::do_not_triangulate_faces), true); + + // TODO: generic versions + auto vos = pm.template add_property_map("v:os").first; + auto ecm = pm.template add_property_map("e:ecm", false).first; + + if (triangulate && !is_triangle_mesh(pm)) + triangulate = false; + + cut_with_plane(pm, plane, parameters::vertex_oriented_side_map(vos) + .edge_is_marked_map(ecm) + .vertex_point_map(vpm) + .geom_traits(traits) + .do_not_triangulate_faces(!triangulate) + .throw_on_self_intersection(!allow_self_intersections && + throw_on_self_intersection) + .concurrency_tag(Concurrency_tag())); + + + if (allow_self_intersections) + { + clip_volume=false; + triangulate=false; + } + + if (clip_volume && !is_closed(pm)) clip_volume=false; + if (clip_volume && !use_compact_clipper) use_compact_clipper=true; + +// TODO: dynamic map + auto fcc = pm.template add_property_map("f:cc").first; + + std::size_t nbcc = connected_components(pm, fcc, CGAL::parameters::edge_is_constrained_map(ecm)); + + std::vector classified(nbcc, false); + std::vector ccs_to_remove; + + for (auto f : faces(pm)) + { + std::size_t ccid = get(fcc, f); + if (classified[ccid]) continue; + halfedge_descriptor hf = halfedge(f, pm); + for(halfedge_descriptor h : CGAL::halfedges_around_face(hf, pm)) + { + CGAL::Oriented_side os = get(vos, target(h,pm)); + if (os==CGAL::ON_ORIENTED_BOUNDARY) continue; + classified[ccid]=true; + if (os==CGAL::ON_POSITIVE_SIDE) ccs_to_remove.push_back(ccid); + } + + if (!classified[ccid]) + { + if (!use_compact_clipper) ccs_to_remove.push_back(ccid); + classified[ccid]=true; + } + } + + remove_connected_components(pm, ccs_to_remove, fcc); + + if (clip_volume) + { + std::vector borders; + extract_boundary_cycles(pm, std::back_inserter(borders)); + + for (halfedge_descriptor h : borders) + { + Euler::fill_hole(h, pm); +#ifndef CGAL_PLANE_CLIP_DO_NOT_USE_TRIANGULATION + if (triangulate) + triangulate_face(face(h,pm), pm, parameters::vertex_point_map(vpm).geom_traits(traits)); +#endif + } + } +} + + +} } // CGAL::Polygon_mesh_processing + + +#endif // CGAL_POLYGON_MESH_PROCESSING_CUT_WITH_PLANE_H diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data-clip/c.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data-clip/c.off new file mode 100644 index 00000000000..38eba7dc82a --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data-clip/c.off @@ -0,0 +1,11 @@ +OFF +6 1 0 + +3 1 0 +3 0 0 +1 1 0 +0 0 0 +0 2 0 +2 2 0 +6 0 2 5 4 3 1 + diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data-clip/ee.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data-clip/ee.off new file mode 100644 index 00000000000..db9cd1b2ae1 --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data-clip/ee.off @@ -0,0 +1,21 @@ +OFF +16 1 0 + +1 5 0 +1 2 0 +4 1 0 +1 6 0 +1 1 0 +4 0 0 +4 4 0 +1 4 0 +1 3 0 +4 6 0 +4 7 0 +0 0 0 +4 2 0 +4 3 0 +0 7 0 +4 5 0 +16 8 7 6 15 0 3 9 10 14 11 5 2 4 1 12 13 + diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp index 1c00e06c200..c07b8303503 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -856,6 +857,53 @@ void test_isocuboid() assert(vertices(meshes[0]).size() == 20); assert(vertices(meshes[1]).size() == 4); } + +template +void test_new_clip() +{ + { + TriangleMesh e; + std::ifstream("data-clip/ee.off") >> e; + PMP::cut_with_plane(e, K::Plane_3(1,0,0,-2)); + assert(faces(e).size()==5); + assert(vertices(e).size()==24); + } + + { + TriangleMesh c; + std::ifstream("data-clip/c.off") >> c; + PMP::cut_with_plane(c, K::Plane_3(1,0,0,-2)); + assert(faces(c).size()==2); + assert(vertices(c).size()==8); + } + + { + TriangleMesh e; + std::ifstream("data-clip/ee.off") >> e; + PMP::triangulate_faces(e); + PMP::cut_with_plane(e, K::Plane_3(1,0,0,-2), CGAL::parameters::do_not_triangulate_faces(false)); + assert(faces(e).size()==30); + assert(vertices(e).size()==28); + } + + { + TriangleMesh c; + std::ifstream("data-clip/c.off") >> c; + PMP::triangulate_faces(c); + PMP::cut_with_plane(c, K::Plane_3(1,0,0,-2), CGAL::parameters::do_not_triangulate_faces(false)); + assert(faces(c).size()==8); + assert(vertices(c).size()==9); + } + + { + TriangleMesh ele; + std::ifstream(CGAL::data_file_path("meshes/elephant.off")) >> ele; + PMP::new_clip(ele, K::Plane_3(1,0,0,0)); + PMP::new_clip(ele, K::Plane_3(0,1,0,0)); + PMP::new_clip(ele, K::Plane_3(0,0,1,0)); + } +} + int main() { std::cout << "Surface Mesh" << std::endl; @@ -878,5 +926,11 @@ int main() std::cout << "running test_iso_cuboid with Polyhedron\n"; test_isocuboid(); std::cout << "Done!" << std::endl; + std::cout << "running test_new_clip with Surface_mesh\n"; + test_new_clip(); + std::cout << "Done!" << std::endl; +// std::cout << "running test_new_clip with Polyhedron\n"; +// test_new_clip(); +// std::cout << "Done!" << std::endl; return EXIT_SUCCESS; } diff --git a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h index c95d2933e89..b9167d3ce5e 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -20,6 +20,7 @@ CGAL_add_named_parameter(visitor_t, visitor, visitor) CGAL_add_named_parameter(point_t, point_map, point_map) CGAL_add_named_parameter(edge_is_constrained_t, edge_is_constrained, edge_is_constrained_map) +CGAL_add_named_parameter(edge_is_marked_map_t, edge_is_marked_map, edge_is_marked_map) CGAL_add_named_parameter(first_index_t, first_index, first_index) CGAL_add_named_parameter(number_of_iterations_t, number_of_iterations, number_of_iterations) CGAL_add_named_parameter(verbosity_level_t, verbosity_level, verbosity_level) @@ -57,6 +58,8 @@ CGAL_add_named_parameter(face_color_output_iterator_t, face_color_output_iterato CGAL_add_named_parameter(vertex_normal_map_t, vertex_normal_map, vertex_normal_map) CGAL_add_named_parameter(vertex_color_map_t, vertex_color_map, vertex_color_map) CGAL_add_named_parameter(vertex_texture_map_t, vertex_texture_map, vertex_texture_map) +CGAL_add_named_parameter(vertex_oriented_side_map_t, vertex_oriented_side_map, vertex_oriented_side_map) + CGAL_add_named_parameter(face_color_map_t, face_color_map, face_color_map) CGAL_add_named_parameter(repair_polygon_soup_t, repair_polygon_soup, repair_polygon_soup) CGAL_add_named_parameter(output_color_t, output_color, output_color) From 9a4809e8d308e61792dfa89de76748d7c8219c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 25 Mar 2025 18:08:16 +0100 Subject: [PATCH 02/19] wording --- .../CGAL/Polygon_mesh_processing/cut_with_plane.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h index d587e86ee97..06169bb3447 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h @@ -166,7 +166,7 @@ struct Orthogonal_cut_plane_traits * \cgalParamNBegin{geom_traits} * \cgalParamDescription{an instance of a geometric traits class} * \cgalParamType{a class model of `Kernel`} - * \cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`} + * \cgalParamDefault{a \cgal kernel deduced from the point type, using `CGAL::Kernel_traits`} * \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.} * \cgalParamNEnd * @@ -448,8 +448,8 @@ void cut_with_plane(PolygonMesh& pm, * If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volume` is set to `true`. * See Subsection \ref coref_clip for more details. * - * \note `Plane_3` must be from the same %Kernel as the point of the internal vertex point map of `PolygonMesh`. - * \note `Plane_3` must be from the same %Kernel as the point of the vertex point map of `tm`. + * \note `Plane_3` must be from the same kernel as the point of the internal vertex point map of `PolygonMesh`. + * \note `Plane_3` must be from the same kernel as the point of the vertex point map of `tm`. * * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm)` \endlink * @@ -459,7 +459,7 @@ void cut_with_plane(PolygonMesh& pm, * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * * @param tm input triangulated surface mesh - * @param plane plane whose negative side defines the half-space to intersect `tm` with. + * @param plane plane whose negative side defines the halfspace to intersect `tm` with. * `Plane_3` is the plane type for the same CGAL kernel as the point of the vertex point map of `tm`. * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * @@ -513,7 +513,7 @@ void cut_with_plane(PolygonMesh& pm, * \cgalParamDefault{`false`} * \cgalParamExtra{If this option is set to `true`, `tm` is no longer required to be without self-intersection. * Setting this option to `true` will automatically set `throw_on_self_intersection` to `false` - * and `clip_volume` to `false`.} + * and `clip_volume` to `false` (overwriting any value provided)} * \cgalParamNEnd * * \cgalParamNBegin{do_not_triangulate_faces} From 15da6dab19fd53fe02ae3387f84d39ee7ff8f25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 25 Mar 2025 21:40:58 +0100 Subject: [PATCH 03/19] replace clip with plane with new implementation based on clip_with_plane --- Installation/CHANGES.md | 3 + .../CGAL/Polygon_mesh_processing/clip.h | 183 +++++++++++----- .../Polygon_mesh_processing/cut_with_plane.h | 203 ------------------ ...est_degenerate_pmp_clip_split_corefine.cpp | 2 +- .../Polygon_mesh_processing/test_pmp_clip.cpp | 42 ++-- 5 files changed, 156 insertions(+), 277 deletions(-) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index f95d719aa56..8adbd4d700f 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -3,6 +3,9 @@ ## [Release 6.1](https://github.com/CGAL/cgal/releases/tag/v6.1) +### [Polygon Mesh Processing](https://doc.cgal.org/6.1/Manual/packages.html#PkgPolygonMeshProcessing) +- added new implementation of `CGAL::Polygon_mesh_processing::clip()` with a plane as clipper that is much faster and is now able to handle non-triangulated surface meshes. + ### [Algebraic Kernel](https://doc.cgal.org/6.1/Manual/packages.html#PkgAlgebraicKernelD) - **Breaking change**: Classes based on the RS Library are no longer provided. diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index 95462371821..c9c2e049be8 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -43,6 +43,8 @@ #include #include +#include + namespace CGAL{ namespace Polygon_mesh_processing { namespace internal { @@ -633,119 +635,196 @@ clip(TriangleMesh& tm, /** * \ingroup PMP_corefinement_grp * - * \brief clips `tm` by keeping the part that is on the negative side of `plane` (side opposite to its normal vector). + * \brief clips `pm` by keeping the part that is on the negative side of `plane` (side opposite to its normal vector). * - * If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volume` is set to `true`. + * If `pm` is closed, the clipped part can be closed too if the named parameter `clip_volume` is set to `true`. * See Subsection \ref coref_clip for more details. * - * \note `Plane_3` must be from the same %Kernel as the point of the internal vertex point map of `TriangleMesh`. - * \note `Plane_3` must be from the same %Kernel as the point of the vertex point map of `tm`. + * \note `Plane_3` must be from the same kernel as the point of the internal vertex point map of `PolygonMesh`. + * \note `Plane_3` must be from the same kernel as the point of the vertex point map of `pm`. * - * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm)` \endlink + * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(pm)` \endlink * - * @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`. + * @tparam PolygonMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`. * An internal property map for `CGAL::vertex_point_t` must be available. * * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * - * @param tm input triangulated surface mesh - * @param plane plane whose negative side defines the half-space to intersect `tm` with. - * `Plane_3` is the plane type for the same CGAL kernel as the point of the vertex point map of `tm`. + * @param pm input surface mesh + * @param plane plane whose negative side defines the halfspace to intersect `pm` with. + * `Plane_3` is the plane type for the same CGAL kernel as the point of the vertex point map of `pm`. * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin + * + * \cgalParamNBegin{concurrency_tag} + * \cgalParamDescription{a tag indicating if the task should be done using one or several threads.} + * \cgalParamType{Either `CGAL::Sequential_tag`, or `CGAL::Parallel_tag`, or `CGAL::Parallel_if_available_tag`} + * \cgalParamDefault{`CGAL::Sequential_tag`} + * \cgalParamNEnd + * * \cgalParamNBegin{vertex_point_map} - * \cgalParamDescription{a property map associating points to the vertices of `tm`} - * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits::%vertex_descriptor` + * \cgalParamDescription{a property map associating points to the vertices of `pm`} + * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits::%vertex_descriptor` * as key type and `%Point_3` as value type} - * \cgalParamDefault{`boost::get(CGAL::vertex_point, tm)`} + * \cgalParamDefault{`boost::get(CGAL::vertex_point, pm)`} * \cgalParamNEnd * * \cgalParamNBegin{visitor} * \cgalParamDescription{a visitor used to track the creation of new faces} * \cgalParamType{a class model of `PMPCorefinementVisitor`} - * \cgalParamDefault{`Corefinement::Default_visitor`} + * \cgalParamDefault{`Corefinement::Default_visitor`} * \cgalParamNEnd * * \cgalParamNBegin{throw_on_self_intersection} - * \cgalParamDescription{If `true`, the set of triangles close to the intersection of `tm` + * \cgalParamDescription{If `true`, the set of triangles close to the intersection of `pm` * and `plane` will be checked for self-intersections * and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` - * will be thrown if at least one self-intersection is found.} + * will be thrown if at least one self-intersection is found. Always `false` if `pm` is not a triangle mesh.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamNEnd * * \cgalParamNBegin{clip_volume} - * \cgalParamDescription{If `true`, and `tm` is closed, the clipping will be done on - * the volume \link coref_def_subsec bounded \endlink by `tm` - * rather than on its surface (i.e., `tm` will be kept closed).} + * \cgalParamDescription{If `true`, and `pm` is closed, the clipping will be done on + * the volume \link coref_def_subsec bounded \endlink by `pm` + * rather than on its surface (i.e., `pm` will be kept closed).} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamNEnd * * \cgalParamNBegin{use_compact_clipper} - * \cgalParamDescription{if `false` the parts of `tm` coplanar with `plane` will not be part of the output} + * \cgalParamDescription{if `false` the parts of `pm` coplanar with `plane` will not be part of the output} * \cgalParamType{Boolean} * \cgalParamDefault{`true`} * \cgalParamNEnd * * \cgalParamNBegin{allow_self_intersections} - * \cgalParamDescription{If `true`, self-intersections are accepted for `tm`.} + * \cgalParamDescription{If `true`, self-intersections are accepted for `pm`.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} - * \cgalParamExtra{If this option is set to `true`, `tm` is no longer required to be without self-intersection. + * \cgalParamExtra{If this option is set to `true`, `pm` is no longer required to be without self-intersection. * Setting this option to `true` will automatically set `throw_on_self_intersection` to `false` - * and `clip_volume` to `false`.} + * and `clip_volume` to `false` (overwriting any value provided)} * \cgalParamNEnd - * \cgalNamedParamsEnd * - * @return `true` if the output surface mesh is manifold. - * If `false` is returned `tm` is only refined by the intersection with `plane`. + * \cgalParamNBegin{do_not_triangulate_faces} + * \cgalParamDescription{If the input mesh is triangulated and this parameter is set to `false`, the mesh will be kept triangulated. + * Always `true` if `pm` is not a triangle mesh.} + * \cgalParamType{`bool`} + * \cgalParamDefault{`false`} + * \cgalParamNEnd + * + * \cgalNamedParamsEnd * * @see `split()` */ -template -bool clip(TriangleMesh& tm, +void clip(PolygonMesh& pm, #ifdef DOXYGEN_RUNNING const Plane_3& plane, #else - const typename GetGeomTraits::type::Plane_3& plane, + const typename GetGeomTraits::type::Plane_3& plane, #endif const NamedParameters& np = parameters::default_values()) { - namespace PMP = CGAL::Polygon_mesh_processing; - namespace params = CGAL::parameters; + using parameters::choose_parameter; + using parameters::get_parameter; - using params::get_parameter; - using params::choose_parameter; + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; - if(std::begin(faces(tm))==std::end(faces(tm))) return true; + using GT = typename GetGeomTraits::type; + GT traits = choose_parameter(get_parameter(np, internal_np::geom_traits)); + auto vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), + get_property_map(vertex_point, pm)); - CGAL::Bbox_3 bbox = ::CGAL::Polygon_mesh_processing::bbox(tm); + typedef typename internal_np::Lookup_named_param_def < + internal_np::concurrency_tag_t, + NamedParameters, + Sequential_tag + > ::type Concurrency_tag; - //extend the bbox a bit to avoid border cases - double xd=(std::max)(1.,(bbox.xmax()-bbox.xmin())/100); - double yd=(std::max)(1.,(bbox.ymax()-bbox.ymin())/100); - double zd=(std::max)(1.,(bbox.zmax()-bbox.zmin())/100); - bbox=CGAL::Bbox_3(bbox.xmin()-xd, bbox.ymin()-yd, bbox.zmin()-zd, - bbox.xmax()+xd, bbox.ymax()+yd, bbox.zmax()+zd); - TriangleMesh clipper; - Oriented_side os = internal::clip_to_bbox(plane, bbox, clipper, parameters::default_values()); - switch(os) + // config flags + bool clip_volume = + parameters::choose_parameter(parameters::get_parameter(np, internal_np::clip_volume), false); + bool use_compact_clipper = + choose_parameter(get_parameter(np, internal_np::use_compact_clipper), true); + const bool throw_on_self_intersection = + choose_parameter(get_parameter(np, internal_np::throw_on_self_intersection), false); + const bool allow_self_intersections = + choose_parameter(get_parameter(np, internal_np::allow_self_intersections), false); + bool triangulate = !choose_parameter(get_parameter(np, internal_np::do_not_triangulate_faces), false); + + auto vos = get(dynamic_vertex_property_t(), pm); + auto ecm = get(dynamic_edge_property_t(), pm, false); + + if (triangulate && !is_triangle_mesh(pm)) + triangulate = false; + + cut_with_plane(pm, plane, parameters::vertex_oriented_side_map(vos) + .edge_is_marked_map(ecm) + .vertex_point_map(vpm) + .geom_traits(traits) + .do_not_triangulate_faces(!triangulate) + .throw_on_self_intersection(!allow_self_intersections && + throw_on_self_intersection) + .concurrency_tag(Concurrency_tag())); + + + if (allow_self_intersections) { - case ON_NEGATIVE_SIDE: - return true; // nothing to clip, the full mesh is on the negative side - case ON_POSITIVE_SIDE: - remove_all_elements(tm); // clear the mesh that is fully on the positive side - return true; - default: - break; + clip_volume=false; + triangulate=false; } - const bool do_not_modify = choose_parameter(get_parameter(np, internal_np::allow_self_intersections), false); - return clip(tm, clipper, np, params::do_not_modify(do_not_modify)); + if (clip_volume && !is_closed(pm)) clip_volume=false; + if (clip_volume && !use_compact_clipper) use_compact_clipper=true; + + + auto fcc = get(dynamic_face_property_t(), pm); + + std::size_t nbcc = connected_components(pm, fcc, CGAL::parameters::edge_is_constrained_map(ecm)); + + std::vector classified(nbcc, false); + std::vector ccs_to_remove; + + for (auto f : faces(pm)) + { + std::size_t ccid = get(fcc, f); + if (classified[ccid]) continue; + halfedge_descriptor hf = halfedge(f, pm); + for(halfedge_descriptor h : CGAL::halfedges_around_face(hf, pm)) + { + CGAL::Oriented_side os = get(vos, target(h,pm)); + if (os==CGAL::ON_ORIENTED_BOUNDARY) continue; + classified[ccid]=true; + if (os==CGAL::ON_POSITIVE_SIDE) ccs_to_remove.push_back(ccid); + } + + if (!classified[ccid]) + { + if (!use_compact_clipper) ccs_to_remove.push_back(ccid); + classified[ccid]=true; + } + } + + remove_connected_components(pm, ccs_to_remove, fcc); + + if (clip_volume) + { + std::vector borders; + extract_boundary_cycles(pm, std::back_inserter(borders)); + + for (halfedge_descriptor h : borders) + { + Euler::fill_hole(h, pm); +#ifndef CGAL_PLANE_CLIP_DO_NOT_USE_TRIANGULATION + if (triangulate) + triangulate_face(face(h,pm), pm, parameters::vertex_point_map(vpm).geom_traits(traits)); +#endif + } + } } /** diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h index 06169bb3447..246ff91a1df 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h @@ -440,209 +440,6 @@ void cut_with_plane(PolygonMesh& pm, } } -/** - * \ingroup PMP_corefinement_grp - * - * \brief clips `tm` by keeping the part that is on the negative side of `plane` (side opposite to its normal vector). - * - * If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volume` is set to `true`. - * See Subsection \ref coref_clip for more details. - * - * \note `Plane_3` must be from the same kernel as the point of the internal vertex point map of `PolygonMesh`. - * \note `Plane_3` must be from the same kernel as the point of the vertex point map of `tm`. - * - * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm)` \endlink - * - * @tparam PolygonMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`. - * An internal property map for `CGAL::vertex_point_t` must be available. - * - * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" - * - * @param tm input triangulated surface mesh - * @param plane plane whose negative side defines the halfspace to intersect `tm` with. - * `Plane_3` is the plane type for the same CGAL kernel as the point of the vertex point map of `tm`. - * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below - * - * \cgalNamedParamsBegin - * - * \cgalParamNBegin{concurrency_tag} - * \cgalParamDescription{a tag indicating if the task should be done using one or several threads.} - * \cgalParamType{Either `CGAL::Sequential_tag`, or `CGAL::Parallel_tag`, or `CGAL::Parallel_if_available_tag`} - * \cgalParamDefault{`CGAL::Sequential_tag`} - * \cgalParamNEnd - * - * \cgalParamNBegin{vertex_point_map} - * \cgalParamDescription{a property map associating points to the vertices of `tm`} - * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits::%vertex_descriptor` - * as key type and `%Point_3` as value type} - * \cgalParamDefault{`boost::get(CGAL::vertex_point, tm)`} - * \cgalParamNEnd - * - * \cgalParamNBegin{visitor} - * \cgalParamDescription{a visitor used to track the creation of new faces} - * \cgalParamType{a class model of `PMPCorefinementVisitor`} - * \cgalParamDefault{`Corefinement::Default_visitor`} - * \cgalParamNEnd - * - * \cgalParamNBegin{throw_on_self_intersection} - * \cgalParamDescription{If `true`, the set of triangles close to the intersection of `tm` - * and `plane` will be checked for self-intersections - * and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` - * will be thrown if at least one self-intersection is found. Always `false` if `pm` is not a triangle mesh.} - * \cgalParamType{Boolean} - * \cgalParamDefault{`false`} - * \cgalParamNEnd - * - * \cgalParamNBegin{clip_volume} - * \cgalParamDescription{If `true`, and `tm` is closed, the clipping will be done on - * the volume \link coref_def_subsec bounded \endlink by `tm` - * rather than on its surface (i.e., `tm` will be kept closed).} - * \cgalParamType{Boolean} - * \cgalParamDefault{`false`} - * \cgalParamNEnd - * - * \cgalParamNBegin{use_compact_clipper} - * \cgalParamDescription{if `false` the parts of `tm` coplanar with `plane` will not be part of the output} - * \cgalParamType{Boolean} - * \cgalParamDefault{`true`} - * \cgalParamNEnd - * - * \cgalParamNBegin{allow_self_intersections} - * \cgalParamDescription{If `true`, self-intersections are accepted for `tm`.} - * \cgalParamType{Boolean} - * \cgalParamDefault{`false`} - * \cgalParamExtra{If this option is set to `true`, `tm` is no longer required to be without self-intersection. - * Setting this option to `true` will automatically set `throw_on_self_intersection` to `false` - * and `clip_volume` to `false` (overwriting any value provided)} - * \cgalParamNEnd - * - * \cgalParamNBegin{do_not_triangulate_faces} - * \cgalParamDescription{If the input mesh is triangulated and this parameter is set to `false`, the mesh will be kept triangulated. - * Always `true` if `pm` is not a triangle mesh.} - * \cgalParamType{`bool`} - * \cgalParamDefault{`true`} - * \cgalParamNEnd - * - * \cgalNamedParamsEnd - * - * @return `true` if the output surface mesh is manifold. - * If `false` is returned `tm` is only refined by the intersection with `plane`. - * - * @see `split()` - */ -template -void new_clip(PolygonMesh& pm, -#ifdef DOXYGEN_RUNNING - const Plane_3& plane, -#else - const typename GetGeomTraits::type::Plane_3& plane, -#endif - const NamedParameters& np = parameters::default_values()) -{ - using parameters::choose_parameter; - using parameters::get_parameter; - - using face_descriptor = typename boost::graph_traits::face_descriptor; - using edge_descriptor = typename boost::graph_traits::edge_descriptor; - using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; - using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; - - using GT = typename GetGeomTraits::type; - GT traits = choose_parameter(get_parameter(np, internal_np::geom_traits)); - auto vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), - get_property_map(vertex_point, pm)); - - typedef typename internal_np::Lookup_named_param_def < - internal_np::concurrency_tag_t, - NamedParameters, - Sequential_tag - > ::type Concurrency_tag; - - // config flags - bool clip_volume = - parameters::choose_parameter(parameters::get_parameter(np, internal_np::clip_volume), false); - bool use_compact_clipper = - choose_parameter(get_parameter(np, internal_np::use_compact_clipper), true); - const bool throw_on_self_intersection = - choose_parameter(get_parameter(np, internal_np::use_compact_clipper), false); - const bool allow_self_intersections = - choose_parameter(get_parameter(np, internal_np::allow_self_intersections), false); - bool triangulate = !choose_parameter(get_parameter(np, internal_np::do_not_triangulate_faces), true); - - // TODO: generic versions - auto vos = pm.template add_property_map("v:os").first; - auto ecm = pm.template add_property_map("e:ecm", false).first; - - if (triangulate && !is_triangle_mesh(pm)) - triangulate = false; - - cut_with_plane(pm, plane, parameters::vertex_oriented_side_map(vos) - .edge_is_marked_map(ecm) - .vertex_point_map(vpm) - .geom_traits(traits) - .do_not_triangulate_faces(!triangulate) - .throw_on_self_intersection(!allow_self_intersections && - throw_on_self_intersection) - .concurrency_tag(Concurrency_tag())); - - - if (allow_self_intersections) - { - clip_volume=false; - triangulate=false; - } - - if (clip_volume && !is_closed(pm)) clip_volume=false; - if (clip_volume && !use_compact_clipper) use_compact_clipper=true; - -// TODO: dynamic map - auto fcc = pm.template add_property_map("f:cc").first; - - std::size_t nbcc = connected_components(pm, fcc, CGAL::parameters::edge_is_constrained_map(ecm)); - - std::vector classified(nbcc, false); - std::vector ccs_to_remove; - - for (auto f : faces(pm)) - { - std::size_t ccid = get(fcc, f); - if (classified[ccid]) continue; - halfedge_descriptor hf = halfedge(f, pm); - for(halfedge_descriptor h : CGAL::halfedges_around_face(hf, pm)) - { - CGAL::Oriented_side os = get(vos, target(h,pm)); - if (os==CGAL::ON_ORIENTED_BOUNDARY) continue; - classified[ccid]=true; - if (os==CGAL::ON_POSITIVE_SIDE) ccs_to_remove.push_back(ccid); - } - - if (!classified[ccid]) - { - if (!use_compact_clipper) ccs_to_remove.push_back(ccid); - classified[ccid]=true; - } - } - - remove_connected_components(pm, ccs_to_remove, fcc); - - if (clip_volume) - { - std::vector borders; - extract_boundary_cycles(pm, std::back_inserter(borders)); - - for (halfedge_descriptor h : borders) - { - Euler::fill_hole(h, pm); -#ifndef CGAL_PLANE_CLIP_DO_NOT_USE_TRIANGULATION - if (triangulate) - triangulate_face(face(h,pm), pm, parameters::vertex_point_map(vpm).geom_traits(traits)); -#endif - } - } -} - - } } // CGAL::Polygon_mesh_processing diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_degenerate_pmp_clip_split_corefine.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_degenerate_pmp_clip_split_corefine.cpp index 1027c841cfb..173b6448bc1 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_degenerate_pmp_clip_split_corefine.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_degenerate_pmp_clip_split_corefine.cpp @@ -106,5 +106,5 @@ void test() int main() { test(); -// test(); + test(); } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp index c07b8303503..9895b62167d 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -149,11 +148,10 @@ void test() assert(false); return ; } - PMP::clip(tm1, K::Plane_3(0,0,1,-4.5), params::throw_on_self_intersection(true) .allow_self_intersections(true)); - assert(vertices(tm1).size() == 16); + assert(vertices(tm1).size() == 13); } // clipping with identity @@ -322,7 +320,7 @@ void test() TriangleMesh tm1; make_triangle( K::Point_3(0, 0, 0), K::Point_3(0, 1, 0), K::Point_3(1, 0, 0), tm1 ); PMP::clip(tm1, K::Plane_3(0, -1, 0 , 0)); - assert(vertices(tm1).size() == 4); + assert(vertices(tm1).size() == 3); } // test with clipper on border edge: full triangle @@ -395,31 +393,31 @@ void test() // -> closed mesh, true/true PMP::clip(tm1, K::Plane_3(-1,0,0,0), params::use_compact_clipper(true).clip_volume(true)); - assert(faces(tm1).size() == 14); + assert(faces(tm1).size() == 12); assert(CGAL::is_closed(tm1)); // -> closed mesh, false/true PMP::clip(tm1, K::Plane_3(-1,0,0,0), params::use_compact_clipper(false).clip_volume(true)); - assert(faces(tm1).size() == 14); + assert(faces(tm1).size() == 12); assert(CGAL::is_closed(tm1)); // -> closed mesh, true/false PMP::clip(tm1, K::Plane_3(-1,0,0,0), params::use_compact_clipper(true).clip_volume(false)); - assert(faces(tm1).size() == 14); + assert(faces(tm1).size() == 12); assert(CGAL::is_closed(tm1)); // -> closed mesh, false/false PMP::clip(tm1, K::Plane_3(1,0,0,-1), params::use_compact_clipper(false).clip_volume(false)); - assert(faces(tm1).size() == 12); + assert(faces(tm1).size() == 10); assert(!CGAL::is_closed(tm1)); // -> open mesh true/true PMP::clip(tm1, K::Plane_3(-1,0,0,0), params::use_compact_clipper(true).clip_volume(true)); - assert(faces(tm1).size() == 12); + assert(faces(tm1).size() == 10); // -> open mesh true/false PMP::clip(tm1, K::Plane_3(-1,0,0,0), params::use_compact_clipper(true).clip_volume(false)); - assert(faces(tm1).size() == 12); + assert(faces(tm1).size() == 10); // -> open mesh false/false PMP::clip(tm1, K::Plane_3(-1,0,0,0), params::use_compact_clipper(false).clip_volume(false)); @@ -464,7 +462,7 @@ void test() ss >> tm1; CGAL::Euler::remove_face(halfedge(*std::prev(faces(tm1).end()),tm1),tm1); PMP::clip(tm1, K::Plane_3(-1,0,0,2)); - assert(vertices(tm1).size() == 6); + assert(vertices(tm1).size() == 5); } { @@ -512,7 +510,7 @@ void test() std::ifstream("data-coref/open_large_cube.off") >> tm1; std::size_t nbv = vertices(tm1).size(); PMP::clip(tm1, K::Plane_3(0,0,1,-1), CGAL::parameters::use_compact_clipper(true)); - assert(vertices(tm1).size()==nbv+2); // because of the plane diagonal + assert(vertices(tm1).size()==nbv); } { @@ -527,7 +525,7 @@ void test() std::ifstream("data-coref/open_large_cube.off") >> tm1; std::size_t nbv = vertices(tm1).size(); PMP::clip(tm1, K::Plane_3(0,0,1,-1), CGAL::parameters::use_compact_clipper(true).allow_self_intersections(true)); - assert(vertices(tm1).size()==nbv+2); // because of the plane diagonal + assert(vertices(tm1).size()==nbv); } { @@ -541,7 +539,7 @@ void test() TriangleMesh tm1; std::ifstream("data-coref/open_large_cube.off") >> tm1; PMP::clip(tm1, K::Plane_3(0,0,-1,1), CGAL::parameters::use_compact_clipper(true)); - assert(vertices(tm1).size()==178); + assert(vertices(tm1).size()==176); } { @@ -555,7 +553,7 @@ void test() TriangleMesh tm1; std::ifstream("data-coref/open_large_cube.off") >> tm1; PMP::clip(tm1, K::Plane_3(0,0,-1,1), CGAL::parameters::use_compact_clipper(true).allow_self_intersections(true)); - assert(vertices(tm1).size()==178); + assert(vertices(tm1).size()==176); } } @@ -898,9 +896,11 @@ void test_new_clip() { TriangleMesh ele; std::ifstream(CGAL::data_file_path("meshes/elephant.off")) >> ele; - PMP::new_clip(ele, K::Plane_3(1,0,0,0)); - PMP::new_clip(ele, K::Plane_3(0,1,0,0)); - PMP::new_clip(ele, K::Plane_3(0,0,1,0)); + PMP::clip(ele, K::Plane_3(1,0,0,0), CGAL::parameters::do_not_triangulate_faces(true).clip_volume(true)); + PMP::clip(ele, K::Plane_3(0,1,0,0), CGAL::parameters::do_not_triangulate_faces(true).clip_volume(true)); + PMP::clip(ele, K::Plane_3(0,0,1,0), CGAL::parameters::do_not_triangulate_faces(true).clip_volume(true)); + assert(faces(ele).size()==1220); + assert(vertices(ele).size()==691); } } @@ -929,8 +929,8 @@ int main() std::cout << "running test_new_clip with Surface_mesh\n"; test_new_clip(); std::cout << "Done!" << std::endl; -// std::cout << "running test_new_clip with Polyhedron\n"; -// test_new_clip(); -// std::cout << "Done!" << std::endl; + std::cout << "running test_new_clip with Polyhedron\n"; + test_new_clip(); + std::cout << "Done!" << std::endl; return EXIT_SUCCESS; } From 546f823b48f6356a16e7b240edbd598835c51cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 25 Mar 2025 22:25:42 +0100 Subject: [PATCH 04/19] replace split with plane with new implementation based on clip_with_plane --- Installation/CHANGES.md | 1 + .../CGAL/Polygon_mesh_processing/clip.h | 100 +++++++++++------- .../Polygon_mesh_processing/cut_with_plane.h | 2 +- .../Polygon_mesh_processing/test_pmp_clip.cpp | 13 +-- 4 files changed, 73 insertions(+), 43 deletions(-) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 8adbd4d700f..3c5e536bc2e 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -5,6 +5,7 @@ ### [Polygon Mesh Processing](https://doc.cgal.org/6.1/Manual/packages.html#PkgPolygonMeshProcessing) - added new implementation of `CGAL::Polygon_mesh_processing::clip()` with a plane as clipper that is much faster and is now able to handle non-triangulated surface meshes. +- added new implementation of `CGAL::Polygon_mesh_processing::split()` with a plane as clipper that is much faster and is now able to handle non-triangulated surface meshes. ### [Algebraic Kernel](https://doc.cgal.org/6.1/Manual/packages.html#PkgAlgebraicKernelD) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index c9c2e049be8..db33dd8d334 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -1028,28 +1028,28 @@ void split(TriangleMesh& tm, /** * \ingroup PMP_corefinement_grp * - * adds intersection edges of `plane` and `tm` in `tm` and duplicates those edges. + * adds intersection edges of `plane` and `pm` in `pm` and duplicates those edges. * - * \note `Plane_3` must be from the same %Kernel as the point of the internal vertex point map of `TriangleMesh`. - * \note `Plane_3` must be from the same %Kernel as the point of the vertex point map of `tm`. + * \note `Plane_3` must be from the same %kernel as the point of the internal vertex point map of `PolygonMesh`. + * \note `Plane_3` must be from the same %kernel as the point of the vertex point map of `tm`. * * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm)` \endlink * - * @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph`, and `FaceListGraph`. + * @tparam PolygonMesh a model of `MutableFaceGraph`, `HalfedgeListGraph`, and `FaceListGraph`. * An internal property map for `CGAL::vertex_point_t` must be available. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * - * @param tm input triangulated surface mesh - * @param plane the plane that will be used to split `tm`. - * `Plane_3` is the plane type for the same CGAL kernel as the point of the vertex point map of `tm`. + * @param pm input surface mesh + * @param plane the plane that will be used to split `pm`. + * `Plane_3` is the plane type for the same CGAL kernel as the point of the vertex point map of `pm`. * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin * \cgalParamNBegin{vertex_point_map} - * \cgalParamDescription{a property map associating points to the vertices of `tm`} + * \cgalParamDescription{a property map associating points to the vertices of `pm`} * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits::%vertex_descriptor` * as key type and `%Point_3` as value type} - * \cgalParamDefault{`boost::get(CGAL::vertex_point, tm)`} + * \cgalParamDefault{`boost::get(CGAL::vertex_point, pm)`} * \cgalParamNEnd * * \cgalParamNBegin{visitor} @@ -1059,7 +1059,7 @@ void split(TriangleMesh& tm, * \cgalParamNEnd * * \cgalParamNBegin{throw_on_self_intersection} - * \cgalParamDescription{If `true`, the set of triangles close to the intersection of `tm` + * \cgalParamDescription{If `true`, the set of triangles close to the intersection of `pm` * and `plane` will be checked for self-intersections * and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` * will be thrown if at least one self-intersection is found.} @@ -1068,50 +1068,78 @@ void split(TriangleMesh& tm, * \cgalParamNEnd * * \cgalParamNBegin{allow_self_intersections} - * \cgalParamDescription{If `true`, self-intersections are accepted for `tm`.} + * \cgalParamDescription{If `true`, self-intersections are accepted for `pm`.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} - * \cgalParamExtra{If this option is set to `true`, `tm` is no longer required to be without self-intersection. + * \cgalParamExtra{If this option is set to `true`, `pm` is no longer required to be without self-intersection. * Setting this option to `true` will automatically set `throw_on_self_intersection` to `false`.} * \cgalParamNEnd + * + * \cgalParamNBegin{do_not_triangulate_faces} + * \cgalParamDescription{If the input mesh is triangulated and this parameter is set to `false`, the mesh will be kept triangulated. + * Always `true` if `pm` is not a triangle mesh.} + * \cgalParamType{`bool`} + * \cgalParamDefault{`false`} + * \cgalParamNEnd * \cgalNamedParamsEnd * * @see `clip()` */ -template -void split(TriangleMesh& tm, +void split(PolygonMesh& pm, #ifdef DOXYGEN_RUNNING - const Plane_3& plane, + const Plane_3& plane, #else - const typename GetGeomTraits::type::Plane_3& plane, + const typename GetGeomTraits::type::Plane_3& plane, #endif - const NamedParameters& np = parameters::default_values()) + const NamedParameters& np = parameters::default_values()) { - namespace PMP = CGAL::Polygon_mesh_processing; - namespace params = CGAL::parameters; + using parameters::choose_parameter; + using parameters::get_parameter; - using params::get_parameter; - using params::choose_parameter; + using GT = typename GetGeomTraits::type; + GT traits = choose_parameter(get_parameter(np, internal_np::geom_traits)); + auto vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), + get_property_map(vertex_point, pm)); - // create a splitter mesh for the splitting plane using an internal CGAL function - CGAL::Bbox_3 bbox = ::CGAL::Polygon_mesh_processing::bbox(tm, np); - double xd = (std::max)(1., 0.01 * (bbox.xmax() - bbox.xmin())); - double yd = (std::max)(1., 0.01 * (bbox.ymax() - bbox.ymin())); - double zd = (std::max)(1., 0.01 * (bbox.zmax() - bbox.zmin())); - bbox = CGAL::Bbox_3(bbox.xmin()-xd, bbox.ymin()-yd, bbox.zmin()-zd, - bbox.xmax()+xd, bbox.ymax()+yd, bbox.zmax()+zd); + typedef typename internal_np::Lookup_named_param_def < + internal_np::concurrency_tag_t, + NamedParameters, + Sequential_tag + > ::type Concurrency_tag; - TriangleMesh splitter; - CGAL::Oriented_side os = PMP::internal::clip_to_bbox(plane, bbox, splitter, params::default_values()); + // config flags + const bool throw_on_self_intersection = + choose_parameter(get_parameter(np, internal_np::throw_on_self_intersection), false); + const bool allow_self_intersections = + choose_parameter(get_parameter(np, internal_np::allow_self_intersections), false); + bool triangulate = !choose_parameter(get_parameter(np, internal_np::do_not_triangulate_faces), false); - if(os == CGAL::ON_ORIENTED_BOUNDARY) - { - const bool do_not_modify = choose_parameter(get_parameter(np, internal_np::allow_self_intersections), false); - return split(tm, splitter, np, params::do_not_modify(do_not_modify)); - } + auto vos = get(dynamic_vertex_property_t(), pm); + auto ecm = get(dynamic_edge_property_t(), pm, false); - //else nothing to do, no intersection. + if (triangulate && !is_triangle_mesh(pm)) + triangulate = false; + + typedef typename internal_np::Lookup_named_param_def < + internal_np::visitor_t, + NamedParameters, + Corefinement::Default_visitor//default + > ::type User_visitor; + User_visitor uv(choose_parameter(get_parameter(np, internal_np::visitor))); + + cut_with_plane(pm, plane, parameters::vertex_oriented_side_map(vos) + .edge_is_marked_map(ecm) + .vertex_point_map(vpm) + .geom_traits(traits) + .do_not_triangulate_faces(!triangulate) + .throw_on_self_intersection(!allow_self_intersections && + throw_on_self_intersection) + .concurrency_tag(Concurrency_tag())); + + //split mesh along marked edges + internal::split_along_edges(pm, ecm, vpm, uv); } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h index 246ff91a1df..a81bb014226 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h @@ -156,7 +156,7 @@ struct Orthogonal_cut_plane_traits * \cgalParamNEnd * * \cgalParamNBegin{vertex_point_map} - * \cgalParamDescription{a property map associating points to the vertices of `tm_in`} + * \cgalParamDescription{a property map associating points to the vertices of `pm`} * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits::%vertex_descriptor` * as key type and `GeomTraits::Point_3` as value type, `GeomTraits` being the type of the parameter `geom_traits`} * \cgalParamDefault{`boost::get(CGAL::vertex_point, pm)`} diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp index 9895b62167d..6c3aa036c4d 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp @@ -580,9 +580,10 @@ void test_split_plane() assert(meshes.size() == 3); //if the order is not deterministc, put the num_vertices in a list and check //if the list does contain all those numbers. - assert(num_vertices(meshes[2]) == 48); - assert(num_vertices(meshes[0]) == 1527); - assert(num_vertices(meshes[1]) == 1674); + + assert(num_vertices(meshes[2]) == 46); + assert(num_vertices(meshes[0]) == 1523); + assert(num_vertices(meshes[1]) == 1668); CGAL::clear(tm1); meshes.clear(); @@ -601,7 +602,7 @@ void test_split_plane() PMP::split(tm1,K::Plane_3(0,0,1,-1)); PMP::split_connected_components(tm1, meshes, params::default_values()); - assert(meshes.size() == 281); + assert(meshes.size() == 2); CGAL::clear(tm1); meshes.clear(); @@ -641,8 +642,8 @@ void test_split_plane() assert(meshes.size() == 2); //if the order is not deterministc, put the num_vertices in a list and check //if the list does contain all those numbers. - assert(num_vertices(meshes[0]) == 16); - assert(num_vertices(meshes[1]) == 16); + assert(num_vertices(meshes[0]) == 12); + assert(num_vertices(meshes[1]) == 12); CGAL::clear(tm1); meshes.clear(); From 9f98b28effd50cce5ebf0d55a012ae6372c233e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 25 Mar 2025 23:25:34 +0100 Subject: [PATCH 05/19] add do_not_triangulate_faces option in the plugin + fixes --- .../Clip_polyhedron_plugin.cpp | 14 +- .../Clip_polyhedron_plugin.ui | 201 ++++++++++-------- 2 files changed, 122 insertions(+), 93 deletions(-) diff --git a/Lab/demo/Lab/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp b/Lab/demo/Lab/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp index b26e60c5d82..ffbb470c282 100644 --- a/Lab/demo/Lab/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp +++ b/Lab/demo/Lab/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp @@ -122,7 +122,8 @@ public : connect(ui_widget.clip_radioButton, &QRadioButton::toggled, [this](bool b){ - ui_widget.close_checkBox->setEnabled(!ui_widget.do_not_modify_CheckBox->isChecked() &&b); + ui_widget.close_checkBox->setEnabled(!ui_widget.do_not_modify_CheckBox->isChecked() && b); + ui_widget.coplanarCheckBox->setEnabled(ui_widget.clip_radioButton->isChecked()); }); connect(actionClipPolyhedra , SIGNAL(triggered()), this, SLOT(pop_widget())); @@ -235,7 +236,7 @@ public Q_SLOTS: for(int id : scene->selectionIndices()) { Scene_surface_mesh_item *sm_item = qobject_cast(scene->item(id)); - if(sm_item && CGAL::is_triangle_mesh(*sm_item->polyhedron())) + if(sm_item) { if(!ui_widget.do_not_modify_CheckBox->isChecked() && CGAL::Polygon_mesh_processing::does_self_intersect(*sm_item->face_graph())) CGAL::Three::Three::warning(tr("%1 has not been clipped because it has self intersections.").arg(sm_item->name())); @@ -255,10 +256,10 @@ public Q_SLOTS: { CGAL::Polygon_mesh_processing::clip(*(sm_item->face_graph()), plane->plane(), - CGAL::parameters::clip_volume( - ui_widget.close_checkBox->isChecked()). - throw_on_self_intersection(true). - use_compact_clipper( + CGAL::parameters::clip_volume(ui_widget.close_checkBox->isChecked()) + .do_not_triangulate_faces(!ui_widget.triangulated_checkBox->isChecked()) + .throw_on_self_intersection(true) + .use_compact_clipper( !ui_widget.coplanarCheckBox->isChecked()) .allow_self_intersections(ui_widget.do_not_modify_CheckBox->isChecked())); } @@ -280,6 +281,7 @@ public Q_SLOTS: CGAL::Polygon_mesh_processing::split(*(sm_item->face_graph()), plane->plane(), CGAL::parameters::throw_on_self_intersection(true) + .do_not_triangulate_faces(!ui_widget.triangulated_checkBox->isChecked()) .allow_self_intersections(ui_widget.do_not_modify_CheckBox->isChecked())); } } diff --git a/Lab/demo/Lab/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.ui b/Lab/demo/Lab/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.ui index db5d02c4ba1..bccb4d741a8 100644 --- a/Lab/demo/Lab/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.ui +++ b/Lab/demo/Lab/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.ui @@ -6,8 +6,8 @@ 0 0 - 386 - 231 + 326 + 358 @@ -56,33 +56,86 @@ - - - - - - 0 - 0 - - - - Behavior - - - - - - Qt::Vertical + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + Behavior + + + + + + + + Clip - - - 20 - 40 - + + true - + - + + + + Split + + + + + + + + + + + Qt::LeftToRight + + + false + + + &Keep Closed + + + + + + + Qt::LeftToRight + + + false + + + &Keep Triangulated + + + true + + + + true @@ -99,37 +152,7 @@ Only available in Split mode. - - - - Qt::LeftToRight - - - false - - - &Keep Closed - - - - - - - Split - - - - - - - Clip - - - true - - - - + Allow the use of a self-intersected clipper, but prevent the closing of the result. @@ -139,20 +162,11 @@ Only available in Split mode. - - - - - 0 - 0 - - - - Flip plane - - - - + + + + + @@ -165,23 +179,36 @@ Only available in Split mode. + + + + + 0 + 0 + + + + Flip plane + + + - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + From a2556bb9ec35b2245a58b7ee422905036a324859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 25 Mar 2025 23:53:35 +0100 Subject: [PATCH 06/19] update changes for cut_with_plane --- Installation/CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 3c5e536bc2e..a8929012ee4 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -6,6 +6,7 @@ ### [Polygon Mesh Processing](https://doc.cgal.org/6.1/Manual/packages.html#PkgPolygonMeshProcessing) - added new implementation of `CGAL::Polygon_mesh_processing::clip()` with a plane as clipper that is much faster and is now able to handle non-triangulated surface meshes. - added new implementation of `CGAL::Polygon_mesh_processing::split()` with a plane as clipper that is much faster and is now able to handle non-triangulated surface meshes. +- added the function `CGAL::Polygon_mesh_processing::cut_with_plane()`, which enables users to refine a mesh with their intersection with a plane. ### [Algebraic Kernel](https://doc.cgal.org/6.1/Manual/packages.html#PkgAlgebraicKernelD) From 84ee1f4f137eb219c7cd28eb27f1404ea5550788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 26 Mar 2025 00:11:26 +0100 Subject: [PATCH 07/19] hide visitor and special traits in the doc --- .../Polygon_mesh_processing/cut_with_plane.h | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h index a81bb014226..18e38bdd92a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h @@ -30,11 +30,15 @@ namespace CGAL { namespace Polygon_mesh_processing { +#ifndef DOXYGEN_RUNNING template struct Default_cut_visitor { + /// called before splitting an edge void before_edge_split(typename boost::graph_traits::halfedge_descriptor, PolygonMesh&) {} + /// called after an edge split, `h` = the new halfedge pointing to the new vertex void edge_split(typename boost::graph_traits::halfedge_descriptor, PolygonMesh&) {} + /// gives access to all the vertex that are on the cutting plane void vertices_on_cut(const std::vector::vertex_descriptor>&, PolygonMesh&){} }; @@ -96,6 +100,7 @@ struct Orthogonal_cut_plane_traits Do_intersect_3 do_intersect_3_object() const { return Do_intersect_3(); } #endif }; +#endif /*! * \ingroup PMP_corefinement_grp @@ -143,12 +148,6 @@ struct Orthogonal_cut_plane_traits * \cgalParamExtra{If the concurrenty tag is set to `Parallel_tag`, the property map might be filled by several thread at the same time.} * \cgalParamNEnd * - * \cgalParamNBegin{visitor} - * \cgalParamDescription{TODO add concept} - * \cgalParamType{reference wrapper recommeded if it has state TODO} - * \cgalParamDefault{None} - * \cgalParamNEnd - * * \cgalParamNBegin{do_not_triangulate_faces} * \cgalParamDescription{If the input mesh is triangulated and this parameter is set to `false`, the mesh will be kept triangulated.} * \cgalParamType{`bool`} @@ -183,6 +182,14 @@ void cut_with_plane(PolygonMesh& pm, // to limit the number of intersection points computed: several classify done lazily // on points not already eliminated (verices all out with adjacent vertices out too) // actually might be a global classifier to filter out edges, testing all planes at once per vertex and stop as soon as one is out + // TODO: doc visitor + /* + * \cgalParamNBegin{visitor} + * \cgalParamDescription{TODO add concept} + * \cgalParamType{reference wrapper recommeded if it has state TODO} + * \cgalParamDefault{None} + * \cgalParamNEnd + */ using parameters::choose_parameter; using parameters::get_parameter; using parameters::get_parameter_reference; From b6ed04c883ce22337c36ae312fcc06c8f8db9352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 26 Mar 2025 09:42:26 +0100 Subject: [PATCH 08/19] hide concurrency flag --- .../CGAL/Polygon_mesh_processing/cut_with_plane.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h index 18e38bdd92a..cbdae774c0e 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h @@ -117,12 +117,6 @@ struct Orthogonal_cut_plane_traits * * \cgalNamedParamsBegin * - * \cgalParamNBegin{concurrency_tag} - * \cgalParamDescription{a tag indicating if the task should be done using one or several threads.} - * \cgalParamType{Either `CGAL::Sequential_tag`, or `CGAL::Parallel_tag`, or `CGAL::Parallel_if_available_tag`} - * \cgalParamDefault{`CGAL::Sequential_tag`} - * \cgalParamNEnd - * * \cgalParamNBegin{edge_is_constrained_map} * \cgalParamDescription{a property map containing the constrained-or-not status of each edge of `pm`. * If an edge marked as constrained is split, the two resulting edges will be marked as constrained.} @@ -177,6 +171,13 @@ void cut_with_plane(PolygonMesh& pm, const NamedParameters& np = parameters::default_values()) { // TODO: concurrency tag + /* + * \cgalParamNBegin{concurrency_tag} + * \cgalParamDescription{a tag indicating if the task should be done using one or several threads.} + * \cgalParamType{Either `CGAL::Sequential_tag`, or `CGAL::Parallel_tag`, or `CGAL::Parallel_if_available_tag`} + * \cgalParamDefault{`CGAL::Sequential_tag`} + * \cgalParamNEnd + */ // TODO: if you want to clip with many planes (**Kernel**), // it might be interesting to first classify all vertices with all planes // to limit the number of intersection points computed: several classify done lazily From 0e8bfaacc3b81895cdcc395abdb74c2d0be095a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 27 Mar 2025 14:33:23 +0100 Subject: [PATCH 09/19] take review into account --- Installation/CHANGES.md | 4 +- .../CGAL/Polygon_mesh_processing/clip.h | 48 ++++++++++--------- .../Polygon_mesh_processing/cut_with_plane.h | 13 ++--- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index a8929012ee4..dc909495214 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -4,8 +4,8 @@ ## [Release 6.1](https://github.com/CGAL/cgal/releases/tag/v6.1) ### [Polygon Mesh Processing](https://doc.cgal.org/6.1/Manual/packages.html#PkgPolygonMeshProcessing) -- added new implementation of `CGAL::Polygon_mesh_processing::clip()` with a plane as clipper that is much faster and is now able to handle non-triangulated surface meshes. -- added new implementation of `CGAL::Polygon_mesh_processing::split()` with a plane as clipper that is much faster and is now able to handle non-triangulated surface meshes. +- new implementation of `CGAL::Polygon_mesh_processing::clip()` with a plane as clipper that is much faster and is now able to handle non-triangulated surface meshes. +- new implementation of `CGAL::Polygon_mesh_processing::split()` with a plane as clipper that is much faster and is now able to handle non-triangulated surface meshes. - added the function `CGAL::Polygon_mesh_processing::cut_with_plane()`, which enables users to refine a mesh with their intersection with a plane. ### [Algebraic Kernel](https://doc.cgal.org/6.1/Manual/packages.html#PkgAlgebraicKernelD) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index db33dd8d334..15998b8fbd2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016 GeometryFactory (France). +// Copyright (c) 2016-2025 GeometryFactory (France). // All rights reserved. // // This file is part of CGAL (www.cgal.org). @@ -680,25 +680,27 @@ clip(TriangleMesh& tm, * \cgalParamDescription{If `true`, the set of triangles close to the intersection of `pm` * and `plane` will be checked for self-intersections * and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` - * will be thrown if at least one self-intersection is found. Always `false` if `pm` is not a triangle mesh.} - * \cgalParamType{Boolean} - * \cgalParamDefault{`false`} - * \cgalParamNEnd - * - * \cgalParamNBegin{clip_volume} - * \cgalParamDescription{If `true`, and `pm` is closed, the clipping will be done on - * the volume \link coref_def_subsec bounded \endlink by `pm` - * rather than on its surface (i.e., `pm` will be kept closed).} + * will be thrown if at least one self-intersection is found. + * This option is only taken into account if `pm` is not a triangle mesh.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamNEnd * * \cgalParamNBegin{use_compact_clipper} - * \cgalParamDescription{if `false` the parts of `pm` coplanar with `plane` will not be part of the output} + * \cgalParamDescription{If `false`, the parts of `pm` coplanar with `plane` will not be part of the output. + * Always `true` if `clip_volume` is `true`.} * \cgalParamType{Boolean} * \cgalParamDefault{`true`} * \cgalParamNEnd * + * \cgalParamNBegin{clip_volume} + * \cgalParamDescription{If `true`, and if `pm` is closed, the clipping will be done on + * the volume \link coref_def_subsec bounded \endlink by `pm` + * rather than on its surface (i.e., `pm` will remain closed).} + * \cgalParamType{Boolean} + * \cgalParamDefault{`false`} + * \cgalParamNEnd + * * \cgalParamNBegin{allow_self_intersections} * \cgalParamDescription{If `true`, self-intersections are accepted for `pm`.} * \cgalParamType{Boolean} @@ -711,7 +713,7 @@ clip(TriangleMesh& tm, * \cgalParamNBegin{do_not_triangulate_faces} * \cgalParamDescription{If the input mesh is triangulated and this parameter is set to `false`, the mesh will be kept triangulated. * Always `true` if `pm` is not a triangle mesh.} - * \cgalParamType{`bool`} + * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamNEnd * @@ -773,10 +775,7 @@ void clip(PolygonMesh& pm, if (allow_self_intersections) - { clip_volume=false; - triangulate=false; - } if (clip_volume && !is_closed(pm)) clip_volume=false; if (clip_volume && !use_compact_clipper) use_compact_clipper=true; @@ -835,8 +834,8 @@ void clip(PolygonMesh& pm, * If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volume` is set to `true`. * See Subsection \ref coref_clip for more details. * - * \note `Iso_cuboid_3` must be from the same %Kernel as the point of the internal vertex point map of `TriangleMesh`. - * \note `Iso_cuboid_3` must be from the same %Kernel as the point of the vertex point map of `tm`. + * \note `Iso_cuboid_3` must be from the same kernel as the point of the internal vertex point map of `TriangleMesh`. + * \note `Iso_cuboid_3` must be from the same kernel as the point of the vertex point map of `tm`. * * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm)` \endlink * @@ -1028,10 +1027,13 @@ void split(TriangleMesh& tm, /** * \ingroup PMP_corefinement_grp * - * adds intersection edges of `plane` and `pm` in `pm` and duplicates those edges. + * splits a polygon mesh with a plane. * - * \note `Plane_3` must be from the same %kernel as the point of the internal vertex point map of `PolygonMesh`. - * \note `Plane_3` must be from the same %kernel as the point of the vertex point map of `tm`. + * The polygon mesh is refined with the intersection edges, and those edges are duplicated as to create a boundary, + * and thus separate connected components on either side of the plane. + * + * \note `Plane_3` must be from the same kernel as the point of the internal vertex point map of `PolygonMesh`. + * \note `Plane_3` must be from the same kernel as the point of the vertex point map of `tm`. * * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm)` \endlink * @@ -1078,7 +1080,7 @@ void split(TriangleMesh& tm, * \cgalParamNBegin{do_not_triangulate_faces} * \cgalParamDescription{If the input mesh is triangulated and this parameter is set to `false`, the mesh will be kept triangulated. * Always `true` if `pm` is not a triangle mesh.} - * \cgalParamType{`bool`} + * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamNEnd * \cgalNamedParamsEnd @@ -1148,8 +1150,8 @@ void split(PolygonMesh& pm, * * adds intersection edges of `iso_cuboid` and `tm` in `tm` and duplicates those edges. * - * \note `Iso_cuboid_3` must be from the same %Kernel as the point of the internal vertex point map of `TriangleMesh`. - * \note `Iso_cuboid_3` must be from the same %Kernel as the point of the vertex point map of `tm`. + * \note `Iso_cuboid_3` must be from the same kernel as the point of the internal vertex point map of `TriangleMesh`. + * \note `Iso_cuboid_3` must be from the same kernel as the point of the vertex point map of `tm`. * * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm)` \endlink * diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h index cbdae774c0e..afd6a174374 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h @@ -1,4 +1,4 @@ -// Copyright (c) 2024 GeometryFactory (France). +// Copyright (c) 2024-2025 GeometryFactory (France). // All rights reserved. // // This file is part of CGAL (www.cgal.org). @@ -20,9 +20,6 @@ #include #include #include -#ifndef CGAL_PLANE_CLIP_DO_NOT_USE_TRIANGULATION -#include -#endif #ifndef CGAL_PLANE_CLIP_DO_NOT_USE_BOX_INTERSECTION_D #include #endif @@ -105,7 +102,7 @@ struct Orthogonal_cut_plane_traits /*! * \ingroup PMP_corefinement_grp * - * refines `pm` by inserting the intersection points of `plane` with its edges and faces. + * refines `pm` by inserting the intersection points and edge of `plane` with faces and edges of `pm`. * * \tparam PolygonMesh a model of `HalfedgeListGraph`, `FaceListGraph`, and `MutableFaceGraph` * \tparam Plane_3 plane type, equal to `GeomTraits::Plane_3`, `GeomTraits` being the type of the parameter `geom_traits`. @@ -144,7 +141,7 @@ struct Orthogonal_cut_plane_traits * * \cgalParamNBegin{do_not_triangulate_faces} * \cgalParamDescription{If the input mesh is triangulated and this parameter is set to `false`, the mesh will be kept triangulated.} - * \cgalParamType{`bool`} + * \cgalParamType{Boolean} * \cgalParamDefault{`true`} * \cgalParamNEnd * @@ -164,6 +161,10 @@ struct Orthogonal_cut_plane_traits * \cgalParamNEnd * * \cgalNamedParamsEnd + * + * \sa split() + * \sa clip() + * */ template void cut_with_plane(PolygonMesh& pm, From b99c75acaf9634f03da4095068f24b3476d72f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 27 Mar 2025 14:45:11 +0100 Subject: [PATCH 10/19] rename function --- Installation/CHANGES.md | 2 +- .../PackageDescription.txt | 1 + .../CGAL/Polygon_mesh_processing/clip.h | 34 +++++++++---------- .../{cut_with_plane.h => refine_with_plane.h} | 12 +++---- .../Polygon_mesh_processing/test_pmp_clip.cpp | 8 ++--- 5 files changed, 29 insertions(+), 28 deletions(-) rename Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/{cut_with_plane.h => refine_with_plane.h} (98%) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index dc909495214..48c18944ea4 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -6,7 +6,7 @@ ### [Polygon Mesh Processing](https://doc.cgal.org/6.1/Manual/packages.html#PkgPolygonMeshProcessing) - new implementation of `CGAL::Polygon_mesh_processing::clip()` with a plane as clipper that is much faster and is now able to handle non-triangulated surface meshes. - new implementation of `CGAL::Polygon_mesh_processing::split()` with a plane as clipper that is much faster and is now able to handle non-triangulated surface meshes. -- added the function `CGAL::Polygon_mesh_processing::cut_with_plane()`, which enables users to refine a mesh with their intersection with a plane. +- added the function `CGAL::Polygon_mesh_processing::refine_with_plane()`, which enables users to refine a mesh with their intersection with a plane. ### [Algebraic Kernel](https://doc.cgal.org/6.1/Manual/packages.html#PkgAlgebraicKernelD) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 6b74ce9696a..561f604f0d6 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -271,6 +271,7 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. - `CGAL::Polygon_mesh_processing::region_growing_of_planes_on_faces()` - `CGAL::Polygon_mesh_processing::detect_corners_of_regions()` - `CGAL::Polygon_mesh_processing::refine_mesh_at_isolevel()` +- `CGAL::Polygon_mesh_processing::refine_with_plane()` \cgalCRPSection{I/O Functions} - \link PMP_IO_grp `CGAL::Polygon_mesh_processing::IO::read_polygon_mesh()`\endlink diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index 15998b8fbd2..d7db4d8f6d3 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -43,7 +43,7 @@ #include #include -#include +#include namespace CGAL{ namespace Polygon_mesh_processing { @@ -764,14 +764,14 @@ void clip(PolygonMesh& pm, if (triangulate && !is_triangle_mesh(pm)) triangulate = false; - cut_with_plane(pm, plane, parameters::vertex_oriented_side_map(vos) - .edge_is_marked_map(ecm) - .vertex_point_map(vpm) - .geom_traits(traits) - .do_not_triangulate_faces(!triangulate) - .throw_on_self_intersection(!allow_self_intersections && - throw_on_self_intersection) - .concurrency_tag(Concurrency_tag())); + refine_with_plane(pm, plane, parameters::vertex_oriented_side_map(vos) + .edge_is_marked_map(ecm) + .vertex_point_map(vpm) + .geom_traits(traits) + .do_not_triangulate_faces(!triangulate) + .throw_on_self_intersection(!allow_self_intersections && + throw_on_self_intersection) + .concurrency_tag(Concurrency_tag())); if (allow_self_intersections) @@ -1131,14 +1131,14 @@ void split(PolygonMesh& pm, > ::type User_visitor; User_visitor uv(choose_parameter(get_parameter(np, internal_np::visitor))); - cut_with_plane(pm, plane, parameters::vertex_oriented_side_map(vos) - .edge_is_marked_map(ecm) - .vertex_point_map(vpm) - .geom_traits(traits) - .do_not_triangulate_faces(!triangulate) - .throw_on_self_intersection(!allow_self_intersections && - throw_on_self_intersection) - .concurrency_tag(Concurrency_tag())); + refine_with_plane(pm, plane, parameters::vertex_oriented_side_map(vos) + .edge_is_marked_map(ecm) + .vertex_point_map(vpm) + .geom_traits(traits) + .do_not_triangulate_faces(!triangulate) + .throw_on_self_intersection(!allow_self_intersections && + throw_on_self_intersection) + .concurrency_tag(Concurrency_tag())); //split mesh along marked edges internal::split_along_edges(pm, ecm, vpm, uv); diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h similarity index 98% rename from Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h rename to Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h index afd6a174374..bec346fbb0a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/cut_with_plane.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h @@ -11,8 +11,8 @@ // Author(s) : Sébastien Loriot -#ifndef CGAL_POLYGON_MESH_PROCESSING_CUT_WITH_PLANE_H -#define CGAL_POLYGON_MESH_PROCESSING_CUT_WITH_PLANE_H +#ifndef CGAL_POLYGON_MESH_PROCESSING_REFINE_WITH_PLANE_H +#define CGAL_POLYGON_MESH_PROCESSING_REFINE_WITH_PLANE_H #include @@ -167,9 +167,9 @@ struct Orthogonal_cut_plane_traits * */ template -void cut_with_plane(PolygonMesh& pm, - const Plane_3& plane, - const NamedParameters& np = parameters::default_values()) +void refine_with_plane(PolygonMesh& pm, + const Plane_3& plane, + const NamedParameters& np = parameters::default_values()) { // TODO: concurrency tag /* @@ -452,4 +452,4 @@ void cut_with_plane(PolygonMesh& pm, } } // CGAL::Polygon_mesh_processing -#endif // CGAL_POLYGON_MESH_PROCESSING_CUT_WITH_PLANE_H +#endif // CGAL_POLYGON_MESH_PROCESSING_REFINE_WITH_PLANE_H diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp index 6c3aa036c4d..4d738d9598e 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp @@ -863,7 +863,7 @@ void test_new_clip() { TriangleMesh e; std::ifstream("data-clip/ee.off") >> e; - PMP::cut_with_plane(e, K::Plane_3(1,0,0,-2)); + PMP::refine_with_plane(e, K::Plane_3(1,0,0,-2)); assert(faces(e).size()==5); assert(vertices(e).size()==24); } @@ -871,7 +871,7 @@ void test_new_clip() { TriangleMesh c; std::ifstream("data-clip/c.off") >> c; - PMP::cut_with_plane(c, K::Plane_3(1,0,0,-2)); + PMP::refine_with_plane(c, K::Plane_3(1,0,0,-2)); assert(faces(c).size()==2); assert(vertices(c).size()==8); } @@ -880,7 +880,7 @@ void test_new_clip() TriangleMesh e; std::ifstream("data-clip/ee.off") >> e; PMP::triangulate_faces(e); - PMP::cut_with_plane(e, K::Plane_3(1,0,0,-2), CGAL::parameters::do_not_triangulate_faces(false)); + PMP::refine_with_plane(e, K::Plane_3(1,0,0,-2), CGAL::parameters::do_not_triangulate_faces(false)); assert(faces(e).size()==30); assert(vertices(e).size()==28); } @@ -889,7 +889,7 @@ void test_new_clip() TriangleMesh c; std::ifstream("data-clip/c.off") >> c; PMP::triangulate_faces(c); - PMP::cut_with_plane(c, K::Plane_3(1,0,0,-2), CGAL::parameters::do_not_triangulate_faces(false)); + PMP::refine_with_plane(c, K::Plane_3(1,0,0,-2), CGAL::parameters::do_not_triangulate_faces(false)); assert(faces(c).size()==8); assert(vertices(c).size()==9); } From 0a462f3b90946d81d72daad8901ea0dd30e8f6d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 27 Mar 2025 18:15:51 +0100 Subject: [PATCH 11/19] address more review points --- .../CGAL/Polygon_mesh_processing/clip.h | 44 ++++++------------- .../refine_with_plane.h | 14 +++--- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index d7db4d8f6d3..772a1c0fbcf 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -45,7 +45,7 @@ #include -namespace CGAL{ +namespace CGAL { namespace Polygon_mesh_processing { namespace internal { @@ -523,7 +523,7 @@ generic_clip_impl( * * \brief clips `tm` by keeping the part that is inside the volume \link coref_def_subsec bounded \endlink by `clipper`. * - * If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volume` is set to `true`. + * If `tm` is closed, the clipped part can be kept closed by setting the named parameter `clip_volume` to `true`. * See Subsection \ref coref_clip for more details. * * \attention With the current implementation, `clipper` will be modified (refined with the intersection with `tm`). @@ -635,16 +635,11 @@ clip(TriangleMesh& tm, /** * \ingroup PMP_corefinement_grp * - * \brief clips `pm` by keeping the part that is on the negative side of `plane` (side opposite to its normal vector). + * \brief clips `pm` by keeping the part that is on the negative side of `plane` (the side opposite to its normal vector). * - * If `pm` is closed, the clipped part can be closed too if the named parameter `clip_volume` is set to `true`. + * If `pm` is closed, the clipped part can be kept closed by setting the named parameter `clip_volume`to `true`. * See Subsection \ref coref_clip for more details. * - * \note `Plane_3` must be from the same kernel as the point of the internal vertex point map of `PolygonMesh`. - * \note `Plane_3` must be from the same kernel as the point of the vertex point map of `pm`. - * - * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(pm)` \endlink - * * @tparam PolygonMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`. * An internal property map for `CGAL::vertex_point_t` must be available. * @@ -681,7 +676,7 @@ clip(TriangleMesh& tm, * and `plane` will be checked for self-intersections * and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` * will be thrown if at least one self-intersection is found. - * This option is only taken into account if `pm` is not a triangle mesh.} + * This option is only taken into account if `pm` is a triangle mesh.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamNEnd @@ -719,11 +714,13 @@ clip(TriangleMesh& tm, * * \cgalNamedParamsEnd * + * @return `true` + * * @see `split()` */ template -void clip(PolygonMesh& pm, +bool clip(PolygonMesh& pm, #ifdef DOXYGEN_RUNNING const Plane_3& plane, #else @@ -824,6 +821,8 @@ void clip(PolygonMesh& pm, #endif } } + + return true; } /** @@ -831,7 +830,7 @@ void clip(PolygonMesh& pm, * * \brief clips `tm` by keeping the part that is inside `iso_cuboid`. * - * If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volume` is set to `true`. + * If `tm` is closed, the clipped part can be kept closed by setting the named parameter `clip_volume` to `true`. * See Subsection \ref coref_clip for more details. * * \note `Iso_cuboid_3` must be from the same kernel as the point of the internal vertex point map of `TriangleMesh`. @@ -1032,11 +1031,6 @@ void split(TriangleMesh& tm, * The polygon mesh is refined with the intersection edges, and those edges are duplicated as to create a boundary, * and thus separate connected components on either side of the plane. * - * \note `Plane_3` must be from the same kernel as the point of the internal vertex point map of `PolygonMesh`. - * \note `Plane_3` must be from the same kernel as the point of the vertex point map of `tm`. - * - * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm)` \endlink - * * @tparam PolygonMesh a model of `MutableFaceGraph`, `HalfedgeListGraph`, and `FaceListGraph`. * An internal property map for `CGAL::vertex_point_t` must be available. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" @@ -1064,19 +1058,12 @@ void split(TriangleMesh& tm, * \cgalParamDescription{If `true`, the set of triangles close to the intersection of `pm` * and `plane` will be checked for self-intersections * and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` - * will be thrown if at least one self-intersection is found.} + * will be thrown if at least one self-intersection is found. + * This option is only taken into account if `pm` is a triangle mesh.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamNEnd * - * \cgalParamNBegin{allow_self_intersections} - * \cgalParamDescription{If `true`, self-intersections are accepted for `pm`.} - * \cgalParamType{Boolean} - * \cgalParamDefault{`false`} - * \cgalParamExtra{If this option is set to `true`, `pm` is no longer required to be without self-intersection. - * Setting this option to `true` will automatically set `throw_on_self_intersection` to `false`.} - * \cgalParamNEnd - * * \cgalParamNBegin{do_not_triangulate_faces} * \cgalParamDescription{If the input mesh is triangulated and this parameter is set to `false`, the mesh will be kept triangulated. * Always `true` if `pm` is not a triangle mesh.} @@ -1114,8 +1101,6 @@ void split(PolygonMesh& pm, // config flags const bool throw_on_self_intersection = choose_parameter(get_parameter(np, internal_np::throw_on_self_intersection), false); - const bool allow_self_intersections = - choose_parameter(get_parameter(np, internal_np::allow_self_intersections), false); bool triangulate = !choose_parameter(get_parameter(np, internal_np::do_not_triangulate_faces), false); auto vos = get(dynamic_vertex_property_t(), pm); @@ -1136,8 +1121,7 @@ void split(PolygonMesh& pm, .vertex_point_map(vpm) .geom_traits(traits) .do_not_triangulate_faces(!triangulate) - .throw_on_self_intersection(!allow_self_intersections && - throw_on_self_intersection) + .throw_on_self_intersection(throw_on_self_intersection) .concurrency_tag(Concurrency_tag())); //split mesh along marked edges diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h index bec346fbb0a..f7b4cc172e1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h @@ -102,7 +102,7 @@ struct Orthogonal_cut_plane_traits /*! * \ingroup PMP_corefinement_grp * - * refines `pm` by inserting the intersection points and edge of `plane` with faces and edges of `pm`. + * refines `pm` by inserting new vertices and new edges at the intersection of `plane` with `pm`. * * \tparam PolygonMesh a model of `HalfedgeListGraph`, `FaceListGraph`, and `MutableFaceGraph` * \tparam Plane_3 plane type, equal to `GeomTraits::Plane_3`, `GeomTraits` being the type of the parameter `geom_traits`. @@ -119,15 +119,15 @@ struct Orthogonal_cut_plane_traits * If an edge marked as constrained is split, the two resulting edges will be marked as constrained.} * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits::%edge_descriptor` * as key type and `bool` as value type} - * \cgalParamDefault{a constant property map returning `false` for any edge} + * \cgalParamDefault{unused} * \cgalParamNEnd * * \cgalParamNBegin{edge_is_marked_map} - * \cgalParamDescription{a property map filled by this function that puts `true` for all intersection edge of faces + * \cgalParamDescription{a property map filled by this function that puts `true` for all intersection edges of faces * of `pm` and `plane`, and `false` for all other edges.} * \cgalParamType{a class model of `WritablePropertyMap` with `boost::graph_traits::%edge_descriptor` * as key type and `bool` as value type} - * \cgalParamDefault{a constant property map returning `false` for any edge} + * \cgalParamDefault{unused} * \cgalParamNEnd * * \cgalParamNBegin{vertex_oriented_side_map} @@ -136,13 +136,13 @@ struct Orthogonal_cut_plane_traits * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits::%vertex_descriptor` * as key type and `Oriented_side` as value type} * \cgalParamDefault{Dynamic vertex property map} - * \cgalParamExtra{If the concurrenty tag is set to `Parallel_tag`, the property map might be filled by several thread at the same time.} * \cgalParamNEnd * * \cgalParamNBegin{do_not_triangulate_faces} * \cgalParamDescription{If the input mesh is triangulated and this parameter is set to `false`, the mesh will be kept triangulated.} * \cgalParamType{Boolean} * \cgalParamDefault{`true`} + * \cgalParamExtra{The function `triangulate_faces()` can be used to triangule faces before calling this function.} * \cgalParamNEnd * * \cgalParamNBegin{vertex_point_map} @@ -178,6 +178,10 @@ void refine_with_plane(PolygonMesh& pm, * \cgalParamType{Either `CGAL::Sequential_tag`, or `CGAL::Parallel_tag`, or `CGAL::Parallel_if_available_tag`} * \cgalParamDefault{`CGAL::Sequential_tag`} * \cgalParamNEnd + * + * + for vertex_oriented_side_map + * ---> \cgalParamExtra{If parallelism is used, concurrent accesses to the property map must be safe.} + */ // TODO: if you want to clip with many planes (**Kernel**), // it might be interesting to first classify all vertices with all planes From 2a25a6080eb3ca09023a29e10cf8fda6b08e7ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 27 Mar 2025 18:51:01 +0100 Subject: [PATCH 12/19] use exception --- .../Self_intersection_exception.h | 34 +++++++++++++++++++ .../Corefinement/intersection_callbacks.h | 9 +---- .../refine_with_plane.h | 3 +- 3 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Self_intersection_exception.h diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Self_intersection_exception.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Self_intersection_exception.h new file mode 100644 index 00000000000..5dec557d109 --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Self_intersection_exception.h @@ -0,0 +1,34 @@ +// Copyright (c) 2016 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) : Sebastien Loriot + +#ifndef CGAL_POLYGON_MESH_PROCESSING_INTERNAL_SELF_INTERSECTION_EXCEPTION_H +#define CGAL_POLYGON_MESH_PROCESSING_INTERNAL_SELF_INTERSECTION_EXCEPTION_H + +#include + +#include + +namespace CGAL { +namespace Polygon_mesh_processing { +namespace Corefinement { + +struct Self_intersection_exception : + public std::runtime_error +{ + Self_intersection_exception() + : std::runtime_error("Self-intersection detected in input mesh") + {} +}; + +} } } // end of CGAL::Polygon_mesh_processing::Corefinement + +#endif // CGAL_POLYGON_MESH_PROCESSING_INTERNAL_SELF_INTERSECTION_EXCEPTION_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_callbacks.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_callbacks.h index d96f289ef83..6e0a2f38f9c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_callbacks.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_callbacks.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -28,14 +29,6 @@ namespace CGAL { namespace Polygon_mesh_processing { namespace Corefinement { -struct Self_intersection_exception : - public std::runtime_error -{ - Self_intersection_exception() - : std::runtime_error("Self-intersection detected in input mesh") - {} -}; - template class Collect_face_bbox_per_edge_bbox { protected: diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h index f7b4cc172e1..f7fe7c1cd15 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h @@ -20,6 +20,7 @@ #include #include #include +#include #ifndef CGAL_PLANE_CLIP_DO_NOT_USE_BOX_INTERSECTION_D #include #endif @@ -353,7 +354,7 @@ void refine_with_plane(PolygonMesh& pm, auto last = std::unique(test_faces.begin(), test_faces.end()); test_faces.erase(last, test_faces.end()); if (does_self_intersect(test_faces, pm, np)) - throw std::runtime_error("TODO Corefinement::Self_intersection_exception"); + throw Corefinement::Self_intersection_exception(); } //TODO: parallel for From 29505552eeeb0c481f59b3e2a005e80e3a44a934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 3 Apr 2025 16:55:10 +0200 Subject: [PATCH 13/19] Regroup related named parameters --- .../CGAL/Polygon_mesh_processing/clip.h | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index 772a1c0fbcf..3a79c1e2b06 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -681,11 +681,13 @@ clip(TriangleMesh& tm, * \cgalParamDefault{`false`} * \cgalParamNEnd * - * \cgalParamNBegin{use_compact_clipper} - * \cgalParamDescription{If `false`, the parts of `pm` coplanar with `plane` will not be part of the output. - * Always `true` if `clip_volume` is `true`.} + * \cgalParamNBegin{allow_self_intersections} + * \cgalParamDescription{If `true`, self-intersections are accepted for `pm`.} * \cgalParamType{Boolean} - * \cgalParamDefault{`true`} + * \cgalParamDefault{`false`} + * \cgalParamExtra{If this option is set to `true`, `pm` is no longer required to be without self-intersection. + * Setting this option to `true` will automatically set `throw_on_self_intersection` to `false` + * and `clip_volume` to `false` (overwriting any value provided)} * \cgalParamNEnd * * \cgalParamNBegin{clip_volume} @@ -696,13 +698,11 @@ clip(TriangleMesh& tm, * \cgalParamDefault{`false`} * \cgalParamNEnd * - * \cgalParamNBegin{allow_self_intersections} - * \cgalParamDescription{If `true`, self-intersections are accepted for `pm`.} + * \cgalParamNBegin{use_compact_clipper} + * \cgalParamDescription{If `false`, the parts of `pm` coplanar with `plane` will not be part of the output. + * Always `true` if `clip_volume` is `true`.} * \cgalParamType{Boolean} - * \cgalParamDefault{`false`} - * \cgalParamExtra{If this option is set to `true`, `pm` is no longer required to be without self-intersection. - * Setting this option to `true` will automatically set `throw_on_self_intersection` to `false` - * and `clip_volume` to `false` (overwriting any value provided)} + * \cgalParamDefault{`true`} * \cgalParamNEnd * * \cgalParamNBegin{do_not_triangulate_faces} @@ -714,7 +714,7 @@ clip(TriangleMesh& tm, * * \cgalNamedParamsEnd * - * @return `true` + * @return `true` * * @see `split()` */ @@ -854,15 +854,6 @@ bool clip(PolygonMesh& pm, * \cgalParamDefault{`Corefinement::Default_visitor`} * \cgalParamNEnd * - * \cgalParamNBegin{throw_on_self_intersection} - * \cgalParamDescription{If `true`, the set of triangles close to the intersection of `tm` - * and `iso_cuboid` will be checked for self-intersections - * and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` - * will be thrown if at least one self-intersection is found.} - * \cgalParamType{Boolean} - * \cgalParamDefault{`false`} - * \cgalParamNEnd - * * \cgalParamNBegin{clip_volume} * \cgalParamDescription{If `true`, and `tm` is closed, the clipping will be done on * the volume \link coref_def_subsec bounded \endlink by `tm` @@ -877,6 +868,15 @@ bool clip(PolygonMesh& pm, * \cgalParamDefault{`true`} * \cgalParamNEnd * + * \cgalParamNBegin{throw_on_self_intersection} + * \cgalParamDescription{If `true`, the set of triangles close to the intersection of `tm` + * and `iso_cuboid` will be checked for self-intersections + * and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` + * will be thrown if at least one self-intersection is found.} + * \cgalParamType{Boolean} + * \cgalParamDefault{`false`} + * \cgalParamNEnd + * * \cgalParamNBegin{allow_self_intersections} * \cgalParamDescription{If `true`, self-intersections are accepted for `tm`.} * \cgalParamType{Boolean} @@ -885,6 +885,7 @@ bool clip(PolygonMesh& pm, * Setting this option to `true` will automatically set `throw_on_self_intersection` to `false` * and `clip_volume` to `false`.} * \cgalParamNEnd + * * \cgalNamedParamsEnd * * @return `true` if the output surface mesh is manifold. @@ -1161,15 +1162,6 @@ void split(PolygonMesh& pm, * \cgalParamDefault{`Corefinement::Default_visitor`} * \cgalParamNEnd * - * \cgalParamNBegin{throw_on_self_intersection} - * \cgalParamDescription{If `true`, the set of triangles close to the intersection of `tm` - * and `iso_cuboid` will be checked for self-intersections - * and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` - * will be thrown if at least one self-intersection is found.} - * \cgalParamType{Boolean} - * \cgalParamDefault{`false`} - * \cgalParamNEnd - * * \cgalParamNBegin{clip_volume} * \cgalParamDescription{If `true`, and `tm` is closed, the clipping will be done on * the volume \link coref_def_subsec bounded \endlink by `tm` @@ -1184,7 +1176,16 @@ void split(PolygonMesh& pm, * \cgalParamDefault{`true`} * \cgalParamNEnd * - * * \cgalParamNBegin{allow_self_intersections} + * \cgalParamNBegin{throw_on_self_intersection} + * \cgalParamDescription{If `true`, the set of triangles close to the intersection of `tm` + * and `iso_cuboid` will be checked for self-intersections + * and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` + * will be thrown if at least one self-intersection is found.} + * \cgalParamType{Boolean} + * \cgalParamDefault{`false`} + * \cgalParamNEnd + * + * \cgalParamNBegin{allow_self_intersections} * \cgalParamDescription{If `true`, self-intersections are accepted for `tm`.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} @@ -1192,6 +1193,7 @@ void split(PolygonMesh& pm, * Setting this option to `true` will automatically set `throw_on_self_intersection` to `false` * and `clip_volume` to `false`.} * \cgalParamNEnd + * * \cgalNamedParamsEnd * * @see `clip()` From e001c7ce536b816bf3852224236bf6b5d6c69291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 3 Apr 2025 17:08:26 +0200 Subject: [PATCH 14/19] Misc minor doc changes --- .../include/CGAL/Polygon_mesh_processing/clip.h | 9 +++++---- .../CGAL/Polygon_mesh_processing/refine_with_plane.h | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index 3a79c1e2b06..277772864c2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -590,6 +590,7 @@ generic_clip_impl( * \cgalParamExtra{This option has an effect only if a surface and not a volume is clipped, * (i.e., if `clip_volume` is `false` or if `tm` is open).} * \cgalParamNEnd + * * \cgalParamNBegin{do_not_modify} * \cgalParamDescription{(`np_c` only) if `true`, `clipper` will not be modified.} * \cgalParamType{Boolean} @@ -653,7 +654,7 @@ clip(TriangleMesh& tm, * \cgalNamedParamsBegin * * \cgalParamNBegin{concurrency_tag} - * \cgalParamDescription{a tag indicating if the task should be done using one or several threads.} + * \cgalParamDescription{a tag indicating if the task should be performed using one or several threads.} * \cgalParamType{Either `CGAL::Sequential_tag`, or `CGAL::Parallel_tag`, or `CGAL::Parallel_if_available_tag`} * \cgalParamDefault{`CGAL::Sequential_tag`} * \cgalParamNEnd @@ -682,7 +683,7 @@ clip(TriangleMesh& tm, * \cgalParamNEnd * * \cgalParamNBegin{allow_self_intersections} - * \cgalParamDescription{If `true`, self-intersections are accepted for `pm`.} + * \cgalParamDescription{If `true`, self-intersections in `pm` are accepted.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamExtra{If this option is set to `true`, `pm` is no longer required to be without self-intersection. @@ -878,7 +879,7 @@ bool clip(PolygonMesh& pm, * \cgalParamNEnd * * \cgalParamNBegin{allow_self_intersections} - * \cgalParamDescription{If `true`, self-intersections are accepted for `tm`.} + * \cgalParamDescription{If `true`, self-intersections in `tm` are accepted.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamExtra{If this option is set to `true`, `tm` is no longer required to be without self-intersection. @@ -1186,7 +1187,7 @@ void split(PolygonMesh& pm, * \cgalParamNEnd * * \cgalParamNBegin{allow_self_intersections} - * \cgalParamDescription{If `true`, self-intersections are accepted for `tm`.} + * \cgalParamDescription{If `true`, self-intersections in `tm` are accepted.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamExtra{If this option is set to `true`, `tm` is no longer required to be without self-intersection. diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h index f7fe7c1cd15..588fcf4b375 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h @@ -124,7 +124,7 @@ struct Orthogonal_cut_plane_traits * \cgalParamNEnd * * \cgalParamNBegin{edge_is_marked_map} - * \cgalParamDescription{a property map filled by this function that puts `true` for all intersection edges of faces + * \cgalParamDescription{a property map filled by this function with `true` for all intersection edges of faces * of `pm` and `plane`, and `false` for all other edges.} * \cgalParamType{a class model of `WritablePropertyMap` with `boost::graph_traits::%edge_descriptor` * as key type and `bool` as value type} @@ -163,8 +163,8 @@ struct Orthogonal_cut_plane_traits * * \cgalNamedParamsEnd * - * \sa split() - * \sa clip() + * \sa `split()` + * \sa `clip()` * */ template From dc024f7e03e6fc14c6faa384a7b5859ee016f541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Sun, 13 Apr 2025 19:26:20 +0200 Subject: [PATCH 15/19] add backward compatible visitor calls --- .../CGAL/Polygon_mesh_processing/clip.h | 63 ++++++++++++++-- .../refine_with_plane.h | 71 ++++++++++++++++--- 2 files changed, 121 insertions(+), 13 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index 277772864c2..b32fef71dd8 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -516,6 +516,34 @@ generic_clip_impl( functor(CGAL::Emptyset_iterator(), false, true); } + + + +#ifndef CGAL_PLANE_CLIP_DO_NOT_USE_TRIANGULATION +template +struct Visitor_wrapper_for_triangulate_face +{ + using face_descriptor = typename boost::graph_traits::face_descriptor; + + Clip_visitor& clip_visitor; + const PolygonMesh& pm; + + Visitor_wrapper_for_triangulate_face(const PolygonMesh& pm, Clip_visitor& clip_visitor) + : pm(pm) + , clip_visitor(clip_visitor) + {} + + void before_subface_creations(face_descriptor /* f_split */) {} + void after_subface_creations() {} + + void after_subface_created(face_descriptor f_new) + { + clip_visitor.before_face_copy(boost::graph_traits::null_face(), pm, pm); + clip_visitor.after_face_copy(boost::graph_traits::null_face(), pm, f_new, pm); + } +}; +#endif + } // end of internal namespace /** @@ -667,7 +695,10 @@ clip(TriangleMesh& tm, * \cgalParamNEnd * * \cgalParamNBegin{visitor} - * \cgalParamDescription{a visitor used to track the creation of new faces} + * \cgalParamDescription{a visitor used to track the creation of new faces, edges, and faces. + * Note that as there are no mesh associated with `plane`, + * `boost::graph_traits::null_halfedge()` and `boost::graph_traits::null_face()` will be used when calling + * functions of the visitor expecting a halfedge or a face from `plane`. Similarly, `pm` will be used as the mesh of `plane`.} * \cgalParamType{a class model of `PMPCorefinementVisitor`} * \cgalParamDefault{`Corefinement::Default_visitor`} * \cgalParamNEnd @@ -731,6 +762,7 @@ bool clip(PolygonMesh& pm, { using parameters::choose_parameter; using parameters::get_parameter; + using parameters::get_parameter_reference ; using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; @@ -739,6 +771,13 @@ bool clip(PolygonMesh& pm, auto vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), get_property_map(vertex_point, pm)); + + using Default_visitor = Corefinement::Default_visitor; + Default_visitor default_visitor; + using Visitor_ref = typename internal_np::Lookup_named_param_def::reference; + Visitor_ref visitor = choose_parameter(get_parameter_reference(np, internal_np::visitor), default_visitor); + constexpr bool has_visitor = !std::is_same_v>>; + typedef typename internal_np::Lookup_named_param_def < internal_np::concurrency_tag_t, NamedParameters, @@ -769,16 +808,15 @@ bool clip(PolygonMesh& pm, .do_not_triangulate_faces(!triangulate) .throw_on_self_intersection(!allow_self_intersections && throw_on_self_intersection) + .visitor(visitor) .concurrency_tag(Concurrency_tag())); - if (allow_self_intersections) clip_volume=false; if (clip_volume && !is_closed(pm)) clip_volume=false; if (clip_volume && !use_compact_clipper) use_compact_clipper=true; - auto fcc = get(dynamic_face_property_t(), pm); std::size_t nbcc = connected_components(pm, fcc, CGAL::parameters::edge_is_constrained_map(ecm)); @@ -815,10 +853,22 @@ bool clip(PolygonMesh& pm, for (halfedge_descriptor h : borders) { + visitor.before_face_copy(boost::graph_traits::null_face(), pm, pm); Euler::fill_hole(h, pm); + visitor.after_face_copy(boost::graph_traits::null_face(), pm, face(h, pm), pm); + #ifndef CGAL_PLANE_CLIP_DO_NOT_USE_TRIANGULATION if (triangulate) - triangulate_face(face(h,pm), pm, parameters::vertex_point_map(vpm).geom_traits(traits)); + { + if constexpr (!has_visitor) + triangulate_face(face(h,pm), pm, parameters::vertex_point_map(vpm).geom_traits(traits)); + else + { + using Base_visitor = std::remove_cv_t>; + internal::Visitor_wrapper_for_triangulate_face visitor_wrapper(pm, visitor); + triangulate_face(face(h,pm), pm, parameters::vertex_point_map(vpm).geom_traits(traits).visitor(visitor_wrapper)); + } + } #endif } } @@ -1051,7 +1101,10 @@ void split(TriangleMesh& tm, * \cgalParamNEnd * * \cgalParamNBegin{visitor} - * \cgalParamDescription{a visitor used to track the creation of new faces} + * \cgalParamDescription{a visitor used to track the creation of new faces, edges, and vertices. + * Note that as there are no mesh associated with `plane`, + * `boost::graph_traits::null_halfedge()` and `boost::graph_traits::null_face()` will be used when calling + * functions of the visitor expecting a halfedge or a face from `plane`. Similarly, `pm` will be used as the mesh of `plane`.}} * \cgalParamType{a class model of `PMPCorefinementVisitor`} * \cgalParamDefault{`Corefinement::Default_visitor`} * \cgalParamNEnd diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h index 588fcf4b375..e37ac5b68f3 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h @@ -32,12 +32,38 @@ namespace Polygon_mesh_processing { template struct Default_cut_visitor { - /// called before splitting an edge - void before_edge_split(typename boost::graph_traits::halfedge_descriptor, PolygonMesh&) {} - /// called after an edge split, `h` = the new halfedge pointing to the new vertex - void edge_split(typename boost::graph_traits::halfedge_descriptor, PolygonMesh&) {} - /// gives access to all the vertex that are on the cutting plane - void vertices_on_cut(const std::vector::vertex_descriptor>&, PolygonMesh&){} + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; + using face_descriptor = typename boost::graph_traits::face_descriptor; + using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; + + /// @name Functions used for tracking face splits + /// @{ + void before_subface_creations(face_descriptor /* f_split */, const PolygonMesh& /* pm */){} + void after_subface_creations(const PolygonMesh& /* pm */){} + void before_subface_created(const PolygonMesh& /* pm */){} + void after_subface_created(face_descriptor /* f_new */, const PolygonMesh& /* pm */){} + /// @} + + /// @name Functions used for tracking edge splits and edge creation + /// @{ + void before_edge_split(halfedge_descriptor, PolygonMesh&) {} + void edge_split(halfedge_descriptor, PolygonMesh&) {} + void after_edge_split(){} + void add_retriangulation_edge(halfedge_descriptor h, const PolygonMesh& pm){} + /// @} + + /// @name Functions used when a new vertex is created + ///@{ + void intersection_point_detected(std::size_t /* i_id */, + int /* sdim */, + halfedge_descriptor /* h_e */, + halfedge_descriptor /* h_f */, + const PolygonMesh& /* tm_e */, + const PolygonMesh& /* tm_f */, + bool /* is_target_coplanar */, + bool /* is_source_coplanar */){} + void new_vertex_added(std::size_t /* i_id */, vertex_descriptor /* v */, const PolygonMesh& /* pm */) {} + ///@} }; // TODO: doc me or hide me in the np @@ -223,6 +249,8 @@ void refine_with_plane(PolygonMesh& pm, Default_visitor default_visitor; Visitor_ref visitor = choose_parameter(get_parameter_reference(np, internal_np::visitor), default_visitor); + constexpr bool has_visitor = !std::is_same_v>>; + auto vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), get_property_map(vertex_point, pm)); @@ -284,6 +312,8 @@ void refine_with_plane(PolygonMesh& pm, break; case ON_ORIENTED_BOUNDARY: at_least_one_on=true; + visitor.intersection_point_detected(on_obnd.size(), 2, halfedge(v, pm), boost::graph_traits::null_halfedge(), + pm, pm, true, false); on_obnd.push_back(v); } } @@ -326,13 +356,17 @@ void refine_with_plane(PolygonMesh& pm, else if (get(vertex_os, tgt)!=CGAL::ON_ORIENTED_BOUNDARY && get(vertex_os, src)!=get(vertex_os, tgt)) + { + visitor.intersection_point_detected(on_obnd.size()+inters.size(), 2, halfedge(e, pm), boost::graph_traits::null_halfedge(), + pm, pm, false, false); inters.push_back(e); + } } } if (all_in || all_out) { - visitor.vertices_on_cut(on_obnd, pm); + //visitor.vertices_on_cut(on_obnd, pm); return; } @@ -358,6 +392,7 @@ void refine_with_plane(PolygonMesh& pm, } //TODO: parallel for + std::size_t vid=on_obnd.size(); for (edge_descriptor e : inters) { halfedge_descriptor h = halfedge(e, pm); @@ -368,8 +403,10 @@ void refine_with_plane(PolygonMesh& pm, visitor.before_edge_split(h, pm); h = CGAL::Euler::split_edge(h, pm); put(vpm, target(h, pm), ip); + visitor.new_vertex_added(vid, target(h,pm), pm); put(vertex_os, target(h, pm), ON_ORIENTED_BOUNDARY); visitor.edge_split(h, pm); + visitor.after_edge_split(); if (was_marked) put(ecm, edge(h, pm), true); @@ -378,9 +415,10 @@ void refine_with_plane(PolygonMesh& pm, h=prev(opposite(h,pm),pm); if (!is_border(h, pm)) splitted_faces[face(h, pm)].push_back(h); + ++vid; } - visitor.vertices_on_cut(on_obnd, pm); + // visitor.vertices_on_cut(on_obnd, pm); // collect faces to be cut that have one vertex on the cut plane for (vertex_descriptor v : on_obnd) @@ -403,12 +441,17 @@ void refine_with_plane(PolygonMesh& pm, CGAL_assertion( nb_hedges%2 ==0 ); + visitor.before_subface_creations(f_and_hs.first, pm); + if (nb_hedges==2) { halfedge_descriptor h1=f_and_hs.second[0], h2=f_and_hs.second[1]; CGAL_assertion(next(h1,pm)!=h2 && next(h2,pm)!=h1); // the edge does not already exist + visitor.before_subface_created(pm); halfedge_descriptor res = CGAL::Euler::split_face(h1, h2, pm); + visitor.after_subface_created(face(res, pm), pm); put(edge_is_marked, edge(res, pm), true); + visitor.add_retriangulation_edge(res, pm); if (triangulate) { @@ -418,6 +461,7 @@ void refine_with_plane(PolygonMesh& pm, halfedge_descriptor newh = CGAL::Euler::split_face(res, next(next(res, pm), pm), pm); put(edge_is_marked, edge(newh, pm), false); + visitor.add_retriangulation_edge(newh, pm); } else { @@ -425,8 +469,10 @@ void refine_with_plane(PolygonMesh& pm, if (!is_triangle(res, pm)) { // TODO: take the criteria in triangulate_faces ? + visitor.before_subface_created(pm); halfedge_descriptor newh = CGAL::Euler::split_face(res, next(next(res, pm), pm), pm); + visitor.after_subface_created(face(newh, pm), pm); put(edge_is_marked, edge(newh, pm), false); } } @@ -447,10 +493,19 @@ void refine_with_plane(PolygonMesh& pm, { halfedge_descriptor h1=f_and_hs.second[i], h2=f_and_hs.second[i+1]; CGAL_assertion(next(h1,pm)!=h2 && next(h2,pm)!=h1); // the edge does not already exist + visitor.before_subface_created(pm); halfedge_descriptor res = CGAL::Euler::split_face(h1, h2, pm); + if constexpr (has_visitor) + { + if (face(h1,pm)!=face(h2,pm)) + visitor.after_subface_created(face(res, pm), pm); + } put(edge_is_marked, edge(res, pm), true); + visitor.add_retriangulation_edge(res, pm); } } + + visitor.after_subface_creations(pm); } } From 1211a5ee229b3b204562b8ef7c6e44b3aef2ca3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 15 Apr 2025 09:52:07 +0200 Subject: [PATCH 16/19] fix visitor call in clip + add tests --- .../CGAL/Polygon_mesh_processing/clip.h | 57 +++-- .../refine_with_plane.h | 17 +- .../triangulate_faces.h | 5 +- .../Polygon_mesh_processing/test_pmp_clip.cpp | 234 ++++++++++++++++++ 4 files changed, 286 insertions(+), 27 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index b32fef71dd8..0280f61a05a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -522,24 +522,39 @@ generic_clip_impl( #ifndef CGAL_PLANE_CLIP_DO_NOT_USE_TRIANGULATION template struct Visitor_wrapper_for_triangulate_face + : public ::CGAL::Polygon_mesh_processing::Hole_filling::Default_visitor +//TODO: @afabri --> I shouldn't be the one doing the inheritance... +//TODO: @afabri --> nothing on edge? { using face_descriptor = typename boost::graph_traits::face_descriptor; Clip_visitor& clip_visitor; const PolygonMesh& pm; + std::vector triangulation_faces; Visitor_wrapper_for_triangulate_face(const PolygonMesh& pm, Clip_visitor& clip_visitor) : pm(pm) , clip_visitor(clip_visitor) {} - void before_subface_creations(face_descriptor /* f_split */) {} - void after_subface_creations() {} + void before_subface_creations(face_descriptor f_split) + { + CGAL_assertion(triangulation_faces.empty()); + triangulation_faces.push_back(f_split); + } + void after_subface_creations() + { + for (face_descriptor f : triangulation_faces) + { + clip_visitor.before_face_copy(boost::graph_traits::null_face(), pm, pm); + clip_visitor.after_face_copy(boost::graph_traits::null_face(), pm, f, pm); + } + triangulation_faces.clear(); + } void after_subface_created(face_descriptor f_new) { - clip_visitor.before_face_copy(boost::graph_traits::null_face(), pm, pm); - clip_visitor.after_face_copy(boost::graph_traits::null_face(), pm, f_new, pm); + triangulation_faces.push_back(f_new); } }; #endif @@ -808,9 +823,11 @@ bool clip(PolygonMesh& pm, .do_not_triangulate_faces(!triangulate) .throw_on_self_intersection(!allow_self_intersections && throw_on_self_intersection) - .visitor(visitor) + .visitor(std::ref(visitor)) .concurrency_tag(Concurrency_tag())); + CGAL_assertion(is_valid_polygon_mesh(pm)); + if (allow_self_intersections) clip_volume=false; @@ -853,15 +870,14 @@ bool clip(PolygonMesh& pm, for (halfedge_descriptor h : borders) { - visitor.before_face_copy(boost::graph_traits::null_face(), pm, pm); - Euler::fill_hole(h, pm); - visitor.after_face_copy(boost::graph_traits::null_face(), pm, face(h, pm), pm); - #ifndef CGAL_PLANE_CLIP_DO_NOT_USE_TRIANGULATION if (triangulate) { + Euler::fill_hole(h, pm); // visitor call done in the triangulation visitor if constexpr (!has_visitor) + { triangulate_face(face(h,pm), pm, parameters::vertex_point_map(vpm).geom_traits(traits)); + } else { using Base_visitor = std::remove_cv_t>; @@ -869,7 +885,14 @@ bool clip(PolygonMesh& pm, triangulate_face(face(h,pm), pm, parameters::vertex_point_map(vpm).geom_traits(traits).visitor(visitor_wrapper)); } } + else #endif + { + visitor.before_face_copy(boost::graph_traits::null_face(), pm, pm); + Euler::fill_hole(h, pm); + visitor.after_face_copy(boost::graph_traits::null_face(), pm, face(h, pm), pm); + } + } } @@ -1141,6 +1164,7 @@ void split(PolygonMesh& pm, { using parameters::choose_parameter; using parameters::get_parameter; + using parameters::get_parameter_reference; using GT = typename GetGeomTraits::type; GT traits = choose_parameter(get_parameter(np, internal_np::geom_traits)); @@ -1164,12 +1188,10 @@ void split(PolygonMesh& pm, if (triangulate && !is_triangle_mesh(pm)) triangulate = false; - typedef typename internal_np::Lookup_named_param_def < - internal_np::visitor_t, - NamedParameters, - Corefinement::Default_visitor//default - > ::type User_visitor; - User_visitor uv(choose_parameter(get_parameter(np, internal_np::visitor))); + using Default_visitor = Corefinement::Default_visitor; + Default_visitor default_visitor; + using Visitor_ref = typename internal_np::Lookup_named_param_def::reference; + Visitor_ref visitor = choose_parameter(get_parameter_reference(np, internal_np::visitor), default_visitor); refine_with_plane(pm, plane, parameters::vertex_oriented_side_map(vos) .edge_is_marked_map(ecm) @@ -1177,10 +1199,11 @@ void split(PolygonMesh& pm, .geom_traits(traits) .do_not_triangulate_faces(!triangulate) .throw_on_self_intersection(throw_on_self_intersection) - .concurrency_tag(Concurrency_tag())); + .concurrency_tag(Concurrency_tag()) + .visitor(std::ref(visitor))); //split mesh along marked edges - internal::split_along_edges(pm, ecm, vpm, uv); + internal::split_along_edges(pm, ecm, vpm, visitor); } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h index e37ac5b68f3..c061d2e11a4 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h @@ -429,7 +429,7 @@ void refine_with_plane(PolygonMesh& pm, if (is_border(h, pm)) continue; Oriented_side prev_ori = get(vertex_os, source(h, pm)), next_ori = get(vertex_os, target(next(h, pm), pm)); - if ( prev_ori == ON_ORIENTED_BOUNDARY || next_ori == ON_ORIENTED_BOUNDARY) continue; // skip full edge + if (prev_ori == ON_ORIENTED_BOUNDARY || next_ori == ON_ORIENTED_BOUNDARY) continue; // skip full edge if (prev_ori!=next_ori) splitted_faces[face(h, pm)].push_back(h); // skip tangency point } } @@ -449,7 +449,7 @@ void refine_with_plane(PolygonMesh& pm, CGAL_assertion(next(h1,pm)!=h2 && next(h2,pm)!=h1); // the edge does not already exist visitor.before_subface_created(pm); halfedge_descriptor res = CGAL::Euler::split_face(h1, h2, pm); - visitor.after_subface_created(face(res, pm), pm); + visitor.after_subface_created(face(h2, pm), pm); put(edge_is_marked, edge(res, pm), true); visitor.add_retriangulation_edge(res, pm); @@ -458,9 +458,10 @@ void refine_with_plane(PolygonMesh& pm, if (!is_triangle(res, pm)) { // TODO: take the criteria in triangulate_faces ? + visitor.before_subface_created(pm); halfedge_descriptor newh = CGAL::Euler::split_face(res, next(next(res, pm), pm), pm); - put(edge_is_marked, edge(newh, pm), false); + visitor.after_subface_created(face(opposite(newh, pm), pm), pm); visitor.add_retriangulation_edge(newh, pm); } else @@ -472,8 +473,8 @@ void refine_with_plane(PolygonMesh& pm, visitor.before_subface_created(pm); halfedge_descriptor newh = CGAL::Euler::split_face(res, next(next(res, pm), pm), pm); - visitor.after_subface_created(face(newh, pm), pm); - put(edge_is_marked, edge(newh, pm), false); + visitor.after_subface_created(face(opposite(newh, pm), pm), pm); + visitor.add_retriangulation_edge(newh, pm); } } } @@ -498,10 +499,10 @@ void refine_with_plane(PolygonMesh& pm, if constexpr (has_visitor) { if (face(h1,pm)!=face(h2,pm)) - visitor.after_subface_created(face(res, pm), pm); + visitor.after_subface_created(face(h2, pm), pm); + visitor.add_retriangulation_edge(res, pm); } - put(edge_is_marked, edge(res, pm), true); - visitor.add_retriangulation_edge(res, pm); + // put(edge_is_marked, edge(res, pm), true); } } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/triangulate_faces.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/triangulate_faces.h index a2d234a563e..908a33afec4 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/triangulate_faces.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/triangulate_faces.h @@ -119,9 +119,10 @@ private: if(first) first = false; else + { f = add_face(pmesh); - - visitor.after_subface_created(f); + visitor.after_subface_created(f); + } std::array indices = make_array(triangle.first, triangle.second, diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp index 4d738d9598e..5ffe16c9108 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp @@ -905,6 +905,237 @@ void test_new_clip() } } +struct Clip_and_split_visitor +{ + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; + using face_descriptor = typename boost::graph_traits::face_descriptor; + using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; + + Surface_mesh& sm; + Surface_mesh::Property_map fid_map; + Surface_mesh::Property_map vid_map; + Surface_mesh::Property_map hid_map; + + int fid=-1; + int hid=-1; + std::size_t nbf=0; + std::size_t nbe=0; + std::size_t nb_input_v=sm.number_of_vertices(); + std::size_t nb_input_v_on=0; + std::size_t nb_new_v_on=0; + + Clip_and_split_visitor(Surface_mesh& sm) + : sm(sm) + { + bool is_new=false; + std::tie(fid_map, is_new)=sm.add_property_map("f:id", -1); + assert(is_new); + int i=0; for (auto f : faces(sm)) put(fid_map, f, i++); + std::tie(vid_map, is_new)=sm.add_property_map("v:id", -1); + assert(is_new); + i=0; for (auto v : vertices(sm)) put(vid_map, v, i++); + std::tie(hid_map, is_new)=sm.add_property_map("h:id", -1); + assert(is_new); + i=0; + for (auto e : edges(sm)) + { + auto h = halfedge(e, sm); + put(hid_map, opposite(h, sm), i); + put(hid_map, h, i++); + } + } + + void before_subface_creations(face_descriptor f_split, const Surface_mesh& pm) + { + fid=get(fid_map, f_split); + assert(fid!=-1); + assert(&pm==&sm); + } + void after_subface_creations(const Surface_mesh& pm) + { + assert(&pm==&sm); + } + void before_subface_created(const Surface_mesh& pm) + { + nbf=sm.number_of_faces(); + assert(&pm==&sm); + } + void after_subface_created(face_descriptor f_new, const Surface_mesh& pm) + { + assert(&pm==&sm); + assert(get(fid_map, f_new)==-1); + put(fid_map, f_new, fid); + assert(nbf+1==sm.number_of_faces()); + } + + void before_edge_split(halfedge_descriptor h, Surface_mesh& pm) + { + hid=get(hid_map, h); + assert(hid!=-1); + assert(&pm==&sm); + nbe=sm.number_of_edges(); + } + void edge_split(halfedge_descriptor hnew, Surface_mesh& pm) + { + assert(&pm==&sm); + assert(get(hid_map, hnew)==-1); + assert(hid!=-1); + put(hid_map, hnew, hid); + put(hid_map, opposite(hnew, sm), hid); + assert(nbe+1==sm.number_of_edges()); + } + void after_edge_split() + { + assert(nbe+1==sm.number_of_edges()); + } + void add_retriangulation_edge(halfedge_descriptor hnew, const Surface_mesh& pm) + { + assert(get(hid_map, hnew)==-1); + put(hid_map, hnew, -2); + put(hid_map, opposite(hnew, sm), -2); + assert(&pm==&sm); + } + + void intersection_point_detected(std::size_t /* i_id */, + int /* sdim */, + halfedge_descriptor h_e, + halfedge_descriptor h_f, + const Surface_mesh& tm_e, + const Surface_mesh& tm_f, + bool is_target_coplanar, + bool is_source_coplanar) + { + assert(is_source_coplanar==false); + assert(h_f==boost::graph_traits::null_halfedge()); + assert(h_e!=boost::graph_traits::null_halfedge()); + if (!is_target_coplanar) + ++nb_new_v_on; + else + ++nb_input_v_on; + assert(&tm_e==&sm); + assert(&tm_f==&sm); + } + + void new_vertex_added(std::size_t id, vertex_descriptor v, const Surface_mesh& pm) + { + assert(&pm==&sm); + assert(get(vid_map, v)==-1); + put(vid_map, v, nb_input_v+id-nb_input_v_on); + } + + void before_face_copy(face_descriptor f, const Surface_mesh& src, const Surface_mesh& tgt) + { + assert(f==boost::graph_traits::null_face()); + assert(&src==&sm); + assert(&tgt==&sm); + } + + void after_face_copy(face_descriptor fsrc, const Surface_mesh& src, face_descriptor ftgt, const Surface_mesh& tgt) + { + assert(fsrc==boost::graph_traits::null_face()); + assert(ftgt!=boost::graph_traits::null_face()); + //assert(get(fid_map, ftgt)==-1); + put(fid_map, ftgt, -2); + assert(&src==&sm); + assert(&tgt==&sm); + for (auto h : halfedges_around_face(halfedge(ftgt, sm), sm)) + { + if (get(hid_map, h)==-1) + put(hid_map, h, -2); + } + } + + void before_edge_duplicated(halfedge_descriptor h, Surface_mesh& tm) + { + hid = get(hid_map, h); + assert(hid!=-1); + assert(&tm==&sm); + } + + void after_edge_duplicated(halfedge_descriptor h, halfedge_descriptor new_hedge, Surface_mesh& tm) + { + assert(hid==get(hid_map, h)); + assert(&tm==&sm); + put(hid_map, new_hedge, hid); + put(hid_map, opposite(new_hedge, sm), hid); + } + + void before_vertex_copy(vertex_descriptor v, Surface_mesh& src, Surface_mesh& tgt) + { + assert(&src==&sm); + assert(&tgt==&sm); + assert(get(vid_map, v)!=-1); + } + void after_vertex_copy(vertex_descriptor v, Surface_mesh& src, vertex_descriptor nv, Surface_mesh& tgt) + { + assert(&src==&sm); + assert(&tgt==&sm); + assert(get(vid_map, v)!=-1); + put(vid_map, nv, get(vid_map, v)); + } + + + + void check() + { + for (auto f :faces(sm)) + { + if (get(fid_map, f)==-1) std::cout << sm.point(source(halfedge(f, sm), sm)) << " " << sm.point(target(halfedge(f, sm), sm)) << " " << sm.point(target(next(halfedge(f, sm), sm), sm)) << "\n"; + assert(get(fid_map, f)!=-1); + } + for (auto h :halfedges(sm)) + { + if (get(hid_map, h)==-1) + std::cout << sm.point(source(h, sm)) << " " << sm.point(target(h,sm)) << "\n"; + assert(get(hid_map, h)!=-1); + } + std::size_t nbv_max=nb_input_v+nb_new_v_on; + for (auto v :vertices(sm)) + assert(get(vid_map, v)!=-1 && get(vid_map, v)<(int)nbv_max); + } + +}; + +void test_clip_and_split_with_plane_visitor() +{ + auto test_clip =[](std::string fname, const K::Plane_3& plane, bool triangulate, bool clip_volume) + { + std::cout << " testing with clip " << fname << " vs " << plane << " (" << triangulate << "," << clip_volume << ")\n"; + Surface_mesh sm; + std::ifstream(fname) >> sm; + Clip_and_split_visitor visitor(sm); + visitor.check(); + PMP::clip(sm, plane, params::visitor(std::ref(visitor)).do_not_triangulate_faces(!triangulate).clip_volume(clip_volume)); + std::ofstream ("/tmp/out.off") << sm; + visitor.check(); + }; + + auto test_split =[](std::string fname, const K::Plane_3& plane, bool triangulate) + { + std::cout << " testing with split" << fname << " vs " << plane << " (" << triangulate << ")\n"; + Surface_mesh sm; + std::ifstream(fname) >> sm; + Clip_and_split_visitor visitor(sm); + visitor.check(); + PMP::split(sm, plane, params::visitor(std::ref(visitor)).do_not_triangulate_faces(!triangulate)); + visitor.check(); + }; + + auto test = [&](std::string fname, const K::Plane_3& plane) + { + test_clip(fname, plane, true, true); + test_clip(fname, plane, true, false); + test_clip(fname, plane, false, false); + test_clip(fname, plane, false, true); + test_split(fname, plane, false); + test_split(fname, plane, true); + }; + + test(CGAL::data_file_path("meshes/torus_quad.off"), K::Plane_3(0,0,1,0)); + test(CGAL::data_file_path("meshes/elephant.off"), K::Plane_3(0.137304, -0.293668, 0.945995, 0)); + +} + int main() { std::cout << "Surface Mesh" << std::endl; @@ -933,5 +1164,8 @@ int main() std::cout << "running test_new_clip with Polyhedron\n"; test_new_clip(); std::cout << "Done!" << std::endl; + std::cout << "running test_clip_and_split_with_plane_visitor\n"; + test_clip_and_split_with_plane_visitor(); + std::cout << "Done!" << std::endl; return EXIT_SUCCESS; } From d2c07e03ae0fcd31b3fbdd9a6d816d0cc68a59b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 16 Apr 2025 18:39:45 +0200 Subject: [PATCH 17/19] fix warning --- .../include/CGAL/Polygon_mesh_processing/refine_with_plane.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h index c061d2e11a4..0ff163b8eb2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/refine_with_plane.h @@ -49,7 +49,7 @@ struct Default_cut_visitor void before_edge_split(halfedge_descriptor, PolygonMesh&) {} void edge_split(halfedge_descriptor, PolygonMesh&) {} void after_edge_split(){} - void add_retriangulation_edge(halfedge_descriptor h, const PolygonMesh& pm){} + void add_retriangulation_edge(halfedge_descriptor, const PolygonMesh&){} /// @} /// @name Functions used when a new vertex is created From c5c535e79612b2779b04a778115475ea3c3ade78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 16 Apr 2025 18:43:07 +0200 Subject: [PATCH 18/19] fix warning --- .../include/CGAL/Polygon_mesh_processing/clip.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index 0280f61a05a..e020abd34c9 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -533,8 +533,8 @@ struct Visitor_wrapper_for_triangulate_face std::vector triangulation_faces; Visitor_wrapper_for_triangulate_face(const PolygonMesh& pm, Clip_visitor& clip_visitor) - : pm(pm) - , clip_visitor(clip_visitor) + : clip_visitor(clip_visitor) + , pm(pm) {} void before_subface_creations(face_descriptor f_split) From ce2c04600e75f7988ac165108f34188e5909b63d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 23 Apr 2025 13:19:59 +0200 Subject: [PATCH 19/19] fix default value (doc bug) --- .../include/CGAL/Polygon_mesh_processing/clip.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index e020abd34c9..36c5d029fce 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -629,7 +629,7 @@ struct Visitor_wrapper_for_triangulate_face * \cgalParamNBegin{use_compact_clipper} * \cgalParamDescription{if `false`, the parts of `tm` coplanar with `clipper` will not be part of the output.} * \cgalParamType{Boolean} - * \cgalParamDefault{`false`} + * \cgalParamDefault{`true`} * \cgalParamExtra{This option has an effect only if a surface and not a volume is clipped, * (i.e., if `clip_volume` is `false` or if `tm` is open).} * \cgalParamNEnd