// Copyright (c) 2013,2014,2015 GeometryFactory (France). // All rights reserved. // // This file is part of CGAL (www.cgal.org). // // $URL: https://github.com/CGAL/cgal/blob/v5.1/Polygon_mesh_processing/include/CGAL/Polygon_mesh_slicer.h $ // $Id: Polygon_mesh_slicer.h 254d60f 2019-10-19T15:23:19+02:00 Sébastien Loriot // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial // // // Author(s) : Ilker O. Yaz and Sebastien Loriot #ifndef CGAL_POLYGON_MESH_SLICER_H #define CGAL_POLYGON_MESH_SLICER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace CGAL { /// \ingroup PkgPolygonMeshProcessingRef /// Function object that computes the intersection of a plane with /// a triangulated surface mesh. /// /// \tparam TriangleMesh a triangulated surface mesh, model of `FaceGraph` and `HalfedgeListGraph` /// \tparam Traits a model of `AABBGeomTraits` /// \tparam VertexPointMap a model of `ReadablePropertyMap` with /// `boost::graph_traits::%vertex_descriptor` as key and /// `Traits::Point_3` as value type. /// The default is `typename boost::property_map< TriangleMesh, vertex_point_t>::%type`. /// \tparam AABBTree must be an instantiation of `CGAL::AABB_tree` able to handle /// the edges of `TriangleMesh`, having its `edge_descriptor` as primitive id. /// The default is `CGAL::AABB_tree > >` /// \tparam UseParallelPlaneOptimization if `true`, the code will use specific /// predicates and constructions in case the functor is called with a plane /// orthogonal to a frame axis, the non-null coefficient being 1 or -1. /// The default is `true`. /// /// The implemenation of this class depends on the package \ref PkgAABBTree. /// \todo Shall we document more in details what is required? /// `Traits` must provide: /// - `Plane_3` /// - `Point_3` /// - `Segment_3` /// - `Oriented_side_3` with `Oriented_side operator()(Plane_3, Point_3)` /// - `Do_intersect_3` with `boost::optional operator()(Plane_3,Segment_3)` /// - `Do_intersect_3` with `bool operator()(Plane_3, Bbox_3)` /// /// \todo If we keep the traits for plane orthogonal to a frame axis, `Traits` must also provide: /// - `FT` /// - `Construct_cartesian_const_iterator_3` with `Iterator operator()(Point_3)` `Iterator` being a random access iterator with `FT` as value type /// - `Construct_point_3` with `Point_3 operator()(FT,FT,FT)`; `Construct_source_3` with `const Point_3& operator()(Segment_3)` /// - `Construct_target_3` with `const Point_3& operator()(Segment_3)` /// /// \todo `_object()` functions must also be provided template::type, class AABBTree = AABB_tree< AABB_traits::type >::type, Default, VertexPointMap>::type> > >, bool UseParallelPlaneOptimization=true> class Polygon_mesh_slicer { /// Polygon_mesh typedefs typedef typename boost::graph_traits graph_traits; typedef typename graph_traits::vertex_descriptor vertex_descriptor; typedef typename graph_traits::edge_descriptor edge_descriptor; typedef typename graph_traits::halfedge_descriptor halfedge_descriptor; typedef typename graph_traits::face_descriptor face_descriptor; /// Geometric typedefs typedef typename Traits::Plane_3 Plane_3; typedef typename Traits::Segment_3 Segment_3; typedef typename Traits::Point_3 Point_3; typedef typename Traits::FT FT; /// typedefs for internal graph to get connectivity of the polylines typedef boost::variant AL_vertex_info; typedef boost::adjacency_list < boost::vecS, boost::vecS, boost::undirectedS, AL_vertex_info > AL_graph; typedef typename AL_graph::vertex_descriptor AL_vertex_descriptor; typedef std::pair AL_vertex_pair; typedef std::map Vertices_map; typedef std::pair Vertex_pair; /// Traversal traits typedef Polygon_mesh_slicer_::Traversal_traits< AL_graph, TriangleMesh, VertexPointMap, typename AABBTree::AABB_traits, Traits > General_traversal_traits; typedef Polygon_mesh_slicer_::Traversal_traits< AL_graph, TriangleMesh, VertexPointMap, typename AABBTree::AABB_traits, Polygon_mesh_slicer_::Axis_parallel_plane_traits > Axis_parallel_traversal_traits; /// Auxiliary classes // compare the faces using the halfedge descriptors struct Compare_face{ TriangleMesh& m_tmesh; Compare_face(TriangleMesh& tmesh) :m_tmesh(tmesh) {} bool operator()(halfedge_descriptor hd1, halfedge_descriptor hd2) const { return face(hd1,m_tmesh) < face(hd2,m_tmesh); } }; typedef std::map< halfedge_descriptor, AL_vertex_pair, Compare_face > AL_edge_map; template struct Polyline_visitor{ AL_graph& al_graph; TriangleMesh& m_tmesh; const Plane_3& m_plane; VertexPointMap m_vpmap; typename Traits_::Intersect_3 intersect_3; OutputIterator out; std::pair nodes_for_orient; Polyline_visitor( TriangleMesh& tmesh, AL_graph& al_graph, const Plane_3& plane, VertexPointMap vpmap, const Traits_& traits, OutputIterator out) : al_graph(al_graph) , m_tmesh(tmesh) , m_plane(plane) , m_vpmap(vpmap) , intersect_3( traits.intersect_3_object() ) , out(out) {} std::vector< Point_3 > current_poly; // returns true iff the polyline is not correctly oriented // Using the first edge is oriented such that the normal induced by the face // containing the edge, the oriented edge and the plane orthogonal vector defines // a direct orthogonal basis bool do_reverse_polyline() { if (current_poly.size() < 2) return false; AL_vertex_info v1 = al_graph[nodes_for_orient.first]; AL_vertex_info v2 = al_graph[nodes_for_orient.second]; if (const vertex_descriptor* vd1_ptr = boost::get(&v1) ) { if (const vertex_descriptor* vd2_ptr = boost::get(&v2) ) { CGAL_assertion( halfedge(*vd1_ptr, *vd2_ptr, m_tmesh).second ); halfedge_descriptor h_opp = halfedge(*vd1_ptr, *vd2_ptr, m_tmesh).first; if ( !is_border(h_opp, m_tmesh) ) { CGAL_assertion(source(h_opp, m_tmesh) == *vd1_ptr); CGAL::Oriented_side os = m_plane.oriented_side( get(m_vpmap, target(next(h_opp, m_tmesh), m_tmesh) ) ); if (os != CGAL::ON_ORIENTED_BOUNDARY) return m_plane.oriented_side( get(m_vpmap, target(next(h_opp, m_tmesh), m_tmesh)) ) == CGAL::ON_NEGATIVE_SIDE; } h_opp = opposite(h_opp, m_tmesh); if ( !is_border(h_opp, m_tmesh) ) { CGAL_assertion(source(h_opp, m_tmesh) == *vd2_ptr); CGAL::Oriented_side os = m_plane.oriented_side( get(m_vpmap, target(next(h_opp, m_tmesh), m_tmesh) ) ); if (os != CGAL::ON_ORIENTED_BOUNDARY) return m_plane.oriented_side( get(m_vpmap, target(next(h_opp, m_tmesh), m_tmesh)) ) == CGAL::ON_POSITIVE_SIDE; } return false; // since coplanar edges are filtered out, we should never end up here. } else { // e2 is intersected in its interior edge_descriptor e2 = boost::get(v2); halfedge_descriptor h2 = halfedge(e2, m_tmesh); if ( target(next(h2, m_tmesh), m_tmesh) != *vd1_ptr ) h2=opposite(h2, m_tmesh); return m_plane.oriented_side( get(m_vpmap, source(h2, m_tmesh)) ) == CGAL::ON_POSITIVE_SIDE; } } else { edge_descriptor e1 = boost::get(v1); halfedge_descriptor h1 = halfedge(e1, m_tmesh); if (const vertex_descriptor* vd2_ptr = boost::get(&v2) ) { // e1 is intersected in its interior if ( target(next(h1, m_tmesh), m_tmesh) != *vd2_ptr ) h1=opposite(h1, m_tmesh); CGAL_assertion( target(next(h1, m_tmesh), m_tmesh) == *vd2_ptr ); } else { // intersection in the interior of both edges edge_descriptor e2 = boost::get(v2); halfedge_descriptor h2 = halfedge(e2, m_tmesh); if ( face(h1, m_tmesh) != face(h2,m_tmesh) ) { halfedge_descriptor opp_h1 = opposite(h1, m_tmesh), opp_h2 = opposite(h2, m_tmesh); if ( face(opp_h1, m_tmesh) == face(opp_h2, m_tmesh) ) { h1=opp_h1; h2=opp_h2; } else if ( face(opp_h1, m_tmesh)==face(h2,m_tmesh) ) h1=opp_h1; else h2=opp_h2; } CGAL_assertion( face(h1, m_tmesh) == face(h2,m_tmesh) ); } return m_plane.oriented_side( get(m_vpmap, source(h1, m_tmesh)) ) == CGAL::ON_NEGATIVE_SIDE; } } void start_new_polyline() { current_poly.clear(); } void add_node(AL_vertex_descriptor node_id) { if (current_poly.empty()) nodes_for_orient.first=node_id; else if (current_poly.size()==1) nodes_for_orient.second=node_id; AL_vertex_info v = al_graph[node_id]; if (const vertex_descriptor* vd_ptr = boost::get(&v) ) { current_poly.push_back( get(m_vpmap, *vd_ptr) ); } else { edge_descriptor ed = boost::get(v); Segment_3 s( get(m_vpmap, source(ed, m_tmesh)), get(m_vpmap,target(ed, m_tmesh)) ); typename cpp11::result_of::type inter = intersect_3(m_plane, s); CGAL_assertion(inter != boost::none); const Point_3* pt_ptr = boost::get(&(*inter)); current_poly.push_back( *pt_ptr ); } } void end_polyline() { CGAL_assertion(!current_poly.empty()); if(do_reverse_polyline()) std::reverse(current_poly.begin(), current_poly.end()); *out++=current_poly; } }; /// member variables const AABBTree* m_tree_ptr; TriangleMesh& m_tmesh; VertexPointMap m_vpmap; Traits m_traits; bool m_own_tree; /// Convenience graph functions edge_descriptor next_edge(edge_descriptor ed) const { return edge( next( halfedge(ed, m_tmesh), m_tmesh), m_tmesh ); } edge_descriptor next_of_opposite_edge(edge_descriptor ed) const { return edge( next( opposite( halfedge(ed, m_tmesh), m_tmesh), m_tmesh), m_tmesh ); } face_descriptor opposite_face(edge_descriptor ed) const { return face( opposite( halfedge(ed, m_tmesh), m_tmesh), m_tmesh); } /// Other private functions /// handle edge insertion in the adjacency_list graph /// we add an edge betweem two edge_descriptor if they /// share a common facet void update_al_graph_connectivity( edge_descriptor ed, AL_vertex_descriptor vd, AL_edge_map& al_edge_map, AL_graph& al_graph) const { typename AL_edge_map::iterator itm; bool new_insertion; halfedge_descriptor hd=halfedge(ed, m_tmesh); if (face(hd, m_tmesh)!=graph_traits::null_face()) { std::tie(itm, new_insertion) = al_edge_map.insert( std::pair< halfedge_descriptor, AL_vertex_pair > (hd, AL_vertex_pair(vd, AL_graph::null_vertex())) ); if (!new_insertion) { CGAL_assertion(itm->second.second==AL_graph::null_vertex()); itm->second.second=vd; add_edge( itm->second.first, itm->second.second, al_graph); } } hd=opposite(hd, m_tmesh); if (face(hd, m_tmesh)!=graph_traits::null_face()) { std::tie(itm, new_insertion) = al_edge_map.insert( std::pair< halfedge_descriptor, AL_vertex_pair > (hd, AL_vertex_pair(vd, AL_graph::null_vertex())) ); if (!new_insertion) { CGAL_assertion(itm->second.second==AL_graph::null_vertex()); itm->second.second=vd; add_edge( itm->second.first, itm->second.second, al_graph); } } } std::pair axis_parallel_plane_info(const Plane_3& plane) const { FT a = m_traits.compute_a_3_object()(plane); FT b = m_traits.compute_b_3_object()(plane); FT c = m_traits.compute_c_3_object()(plane); FT d = m_traits.compute_d_3_object()(plane); if (a==0) { if (b==0) { if (c==1 || c==-1) return std::pair(2, -d*c); /// z=-d } else { if (c==0 && (b==1 || b==-1)) return std::pair(1, -d*b); /// y=-d } } else if (b==0 && c==0 && ( a==1 || a==-1)) return std::pair(0, -d*a); /// x=-d return std::pair(-1, 0); } public: /// the AABB-tree type used internally typedef AABBTree AABB_tree; /** * Constructor using `edges(tmesh)` to initialize the * internal `AABB_tree`. * @param tmesh the triangulated surface mesh to be sliced. * It must be valid and non modified as long * as the functor is used. * @param vpmap an instance of the vertex point property map associated to `tmesh` * @param traits a traits class instance, can be omitted */ Polygon_mesh_slicer(const TriangleMesh& tmesh, VertexPointMap vpmap, const Traits& traits = Traits()) : m_tmesh(const_cast(tmesh)) , m_vpmap(vpmap) , m_traits(traits) , m_own_tree(true) { m_tree_ptr = new AABBTree(edges(m_tmesh).first, edges(m_tmesh).second, m_tmesh, m_vpmap); } /** * Constructor using a pre-built `AABB_tree` of edges provided by the user. * @param tmesh the triangulated surface mesh to be sliced. * It must be valid and non modified as long * as the functor is used. * @param tree must be initialized with all the edges of `tmesh` * @param vpmap an instance of the vertex point property map associated to `tmesh` * @param traits a traits class instance, can be omitted */ Polygon_mesh_slicer(const TriangleMesh& tmesh, const AABBTree& tree, VertexPointMap vpmap, const Traits& traits = Traits()) : m_tree_ptr(&tree) , m_tmesh(const_cast(tmesh)) , m_vpmap(vpmap) , m_traits(traits) , m_own_tree(false) { } /** * Constructor using `edges(tmesh)` to initialize the * internal `AABB_tree`. The vertex point property map used * is `get(CGAL::vertex_point, tmesh)` * @param tmesh the triangulated surface mesh to be sliced. * It must be valid and non modified as long * as the functor is used. * @param traits a traits class instance, can be omitted */ Polygon_mesh_slicer(const TriangleMesh& tmesh, const Traits& traits = Traits()) : m_tmesh(const_cast(tmesh)) , m_vpmap(get(boost::vertex_point, m_tmesh)) , m_traits(traits) , m_own_tree(true) { m_tree_ptr = new AABBTree(edges(m_tmesh).first, edges(m_tmesh).second, m_tmesh, m_vpmap); } /** * Constructor using a `AABB_tree` provided by the user. * The vertex point property map used is `get(CGAL::vertex_point, tmesh)` * @param tmesh the triangulated surface mesh to be sliced. * It must be valid and non modified as long * as the functor is used. * @param tree must be initialized with all the edges of `tmesh` * @param traits a traits class instance, can be omitted */ Polygon_mesh_slicer(const TriangleMesh& tmesh, const AABBTree& tree, const Traits& traits = Traits()) : m_tree_ptr(&tree) , m_tmesh(const_cast(tmesh)) , m_vpmap(get(boost::vertex_point, m_tmesh)) , m_traits(traits) , m_own_tree(false) { } /** * Constructs the intersecting polylines of `plane` with the input triangulated surface mesh. * * If a polyline is closed, the first and last point of that polyline will be identical. * * Each resulting polyline `P` is oriented such that for two consecutive points `p` and `q` in `P`, * the normal vector of the face(s) containing the segment `pq`, the vector `pq`, and the orthogonal vertor of `plane` * is a direct orthogonal basis. * The normal vector of each face is chosen to point on the side of the face * where its sequence of vertices is seen counterclockwise. * * Note that an edge shared by two faces included in `plane` will not be reported. For example, * if `plane` passes though one face of a cube, only one closed polyline will be reported (the boundary of the face) * * @tparam OutputIterator an output iterator accepting polylines. * A polyline is provided as `std::vector`. * A polyline is closed if its first and last point are identical. * @param plane the plane to intersect the triangulated surface mesh with * @param out output iterator of polylines */ template OutputIterator operator() (const Plane_3& plane, OutputIterator out) const { CGAL_precondition(!plane.is_degenerate()); // containers for storing edges wrt their position with the plane std::set all_coplanar_edges; std::vector iedges; Vertices_map vertices; // get all edges intersected by the plane and classify them std::pair app_info = axis_parallel_plane_info(plane); if (!UseParallelPlaneOptimization || app_info.first==-1) { General_traversal_traits ttraits( all_coplanar_edges, iedges, vertices, m_tmesh, m_vpmap, m_tree_ptr->traits(), m_traits); m_tree_ptr->traversal(plane, ttraits); } else { Polygon_mesh_slicer_::Axis_parallel_plane_traits traits(app_info.first, app_info.second, m_traits); Axis_parallel_traversal_traits ttraits( all_coplanar_edges, iedges, vertices, m_tmesh, m_vpmap, m_tree_ptr->traits(), traits); m_tree_ptr->traversal(plane, ttraits); } // init output graph AL_graph al_graph; // add nodes for each vertex in the plane for(Vertex_pair& vdp : vertices) { vdp.second=add_vertex(al_graph); al_graph[vdp.second]=vdp.first; } Compare_face less_face(m_tmesh); AL_edge_map al_edge_map( less_face ); // Filter coplanar edges: we consider only coplanar edges incident to one non-coplanar facet // for each such edge, add the corresponding nodes in the adjacency-list graph as well as // the edge for(const edge_descriptor ed : all_coplanar_edges) { if ( face(halfedge(ed, m_tmesh), m_tmesh)==graph_traits::null_face() || opposite_face(ed)==graph_traits::null_face() || !all_coplanar_edges.count( next_edge(ed) ) || !all_coplanar_edges.count( next_of_opposite_edge(ed) ) ) { typename Vertices_map::iterator it_insert1, it_insert2; bool is_new; // Each coplanar edge is connecting two nodes // handle source std::tie(it_insert1, is_new) = vertices.insert( Vertex_pair( source(ed,m_tmesh), AL_graph::null_vertex() ) ); if (is_new) { it_insert1->second=add_vertex(al_graph); al_graph[it_insert1->second]=it_insert1->first; } // handle target std::tie(it_insert2, is_new) = vertices.insert( Vertex_pair( target(ed,m_tmesh), AL_graph::null_vertex() ) ); if (is_new) { it_insert2->second=add_vertex(al_graph); al_graph[it_insert2->second]=it_insert2->first; } // add the edge into the adjacency-list graph CGAL_assertion( it_insert1->second!=AL_graph::null_vertex() ); CGAL_assertion( it_insert2->second!=AL_graph::null_vertex() ); add_edge(it_insert1->second, it_insert2->second, al_graph); } } // for each edge intersected in its interior, creates a node in // an adjacency-list graph and put an edge between two such nodes // when the corresponding edges shares a common face for(edge_descriptor ed : iedges) { AL_vertex_descriptor vd=add_vertex(al_graph); al_graph[vd]=ed; update_al_graph_connectivity(ed, vd, al_edge_map, al_graph); } // If one of the node above is not connected in its two incident faces // then it must be connected to a vertex (including those in the set // of coplanar edges) typedef std::pair Halfedge_and_vertices; for(Halfedge_and_vertices hnv :al_edge_map) { if (hnv.second.second==AL_graph::null_vertex()) { //get the edge and test opposite vertices (if the edge is not on the boundary) vertex_descriptor vd = target( next(hnv.first, m_tmesh), m_tmesh); typename Vertices_map::iterator itv=vertices.find(vd); CGAL_assertion( itv!=vertices.end() ); add_edge(itv->second, hnv.second.first, al_graph); } } CGAL_assertion(num_vertices(al_graph)==iedges.size()+vertices.size()); // now assemble the edges of al_graph to define polylines, // putting them in the output iterator if (!UseParallelPlaneOptimization || app_info.first==-1) { Polyline_visitor visitor(m_tmesh, al_graph, plane, m_vpmap, m_traits, out); split_graph_into_polylines(al_graph, visitor); return visitor.out; } else { typedef Polygon_mesh_slicer_::Axis_parallel_plane_traits App_traits; App_traits app_traits(app_info.first, app_info.second, m_traits); Polyline_visitor visitor (m_tmesh, al_graph, plane, m_vpmap, app_traits, out); split_graph_into_polylines(al_graph, visitor); return visitor.out; } } ~Polygon_mesh_slicer() { if (m_own_tree) delete m_tree_ptr; } }; }// end of namespace CGAL #endif //CGAL_POLYGON_MESH_SLICER_H