// Copyright (c) 2019-2023 GeometryFactory Sarl (France). // All rights reserved. // // This file is part of CGAL (www.cgal.org). // You can redistribute it and/or modify it under the terms of the GNU // General Public License as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // // Licensees holding a valid commercial license may use this file in // accordance with the commercial license agreement provided with the software. // // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. // // $URL$ // $Id$ // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial // // Author(s) : Laurent Rineau #ifndef CGAL_CONSTRAINED_DELAUNAY_TRIANGULATION_3_H #define CGAL_CONSTRAINED_DELAUNAY_TRIANGULATION_3_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __has_include() # include # include #elif CGAL_DEBUG_CDT_3 # error "Compiler needs " #endif #if DOXYGEN_RUNNING namespace CGAL { /*! * \brief The class Constrained_Delaunay_triangulation_3 represents a 3D constrained Delaunay triangulation. * * This class is derived from the Triangulation_3 class and provides additional functionality for handling * polygonal constraints during the triangulation process. * * \todo Explain what is a CDT. Why a given input, like a polyhedron, might not admit a constrained Delaunay triangulation. * Explain "conforming to the faces of a polygon mesh". * * \tparam Traits is the geometric traits class and must be a model of `ConstrainedDelaunayTriangulationTraits_3`. * \tparam Triangulation_3 is the base triangulation class. * It must be a an instance of the `Triangulation_3` class template with the same `Traits` template parameter. * Its `Vertex` type must be a model of `ConstrainedDelaunayTriangulationVertexBase_3`, * and its `Cell` type must be a model of `ConstrainedDelaunayTriangulationCellBase_3`. *
* The default value is `Triangulation_3` where `TDS` is * `Triangulation_data_structure_3, Constrained_Delaunay_triangulation_cell_base_3>`. */ template class Constrained_Delaunay_triangulation_3 : public Triangulation_3 { public: /*! * \brief Create a 3D constrained Delaunay triangulation conforming to the faces of a polygon mesh. * * This constructor initializes a Constrained_Delaunay_triangulation_3 object using a polygon mesh. * The polygon mesh represents the polygonal constraints that will be enforced during the triangulation process. * * By default, each face of the polygon mesh is considered as a polygonal constraint for the triangulation. The * named parameter `face_patch_map` can be used to describe bigger polygonal constraints, possibly with holes. If * used, the argument of that parameter must be a property map that maps each face of the polygon mesh to a patch * identifier. Faces with the same patch identifier are considered as part of the same surface patch. Each of those * surface patches (defined as the union of the mesh faces with a given patch id) is supposed to be a polygon or a * polygon with holes, with coplanar vertices (or almost coplanar up to the precision of the number type used). * * The triangulation will be constrained to conform to the faces of the polygon mesh, or to the surface patches * described by the `face_patch_map` property map if provided. * * \tparam PolygonMesh a model of `FaceListGraph` * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * * \param mesh The polygon mesh representing the constraints. * \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 `mesh`} * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits::%vertex_descriptor` * as key type and `%Traits::Point_3` as value type} * \cgalParamDefault{`boost::get(CGAL::vertex_point, mesh)`} * \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{`Traits`} * \cgalParamDefault{the default constructed traits object `Traits{}`} * \cgalParamNEnd * \cgalParamNBegin{face_patch_map} * \cgalParamDescription{a property map associating a patch identifier to each face of `mesh`} * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits::%face_descriptor` * as key type and with any value type that is a *regular* type} * \cgalParamExtra{If this parameter is omitted, each face of the mesh is considered as a separate patch.} * \cgalParamExtra{Faces with the same patch identifier are considered as part of the same surface patch.} * \cgalParamNEnd * \cgalNamedParamsEnd * * \todo Create a documentation page to describe the concept *regular*, and link it to https://en.cppreference.com/w/cpp/concepts/regular */ template Constrained_Delaunay_triangulation_3(const PolygonMesh& mesh, const NamedParams& np = parameters::default_values()) { // Implementation of the constructor with variadic named parameters // ... } }; } // end namespace CGAL #else // not DOXYGEN_RUNNING namespace CGAL { template typename K::Boolean does_first_triangle_intersect_second_triangle_interior(const typename K::Triangle_3& t1, const typename K::Triangle_3& t2, const K& k) { typedef typename K::Point_3 Point_3; CGAL_kernel_precondition(!k.is_degenerate_3_object() (t1) ); CGAL_kernel_precondition(!k.is_degenerate_3_object() (t2) ); typename K::Construct_vertex_3 vertex_on = k.construct_vertex_3_object(); typename K::Orientation_3 orientation = k.orientation_3_object(); const Point_3& p = vertex_on(t1,0); const Point_3& q = vertex_on(t1,1); const Point_3& r = vertex_on(t1,2); const Point_3& a = vertex_on(t2,0); const Point_3& b = vertex_on(t2,1); const Point_3& c = vertex_on(t2,2); // TODO: use boost::containers::static_vector std::array t1_vertices_in_the_line; int nb_of_t1_vertices_in_the_line = 0; auto push_to_t1_vertices_in_the_line = [&t1_vertices_in_the_line, &nb_of_t1_vertices_in_the_line](const Point_3* p) { t1_vertices_in_the_line.at(nb_of_t1_vertices_in_the_line++) = p; }; std::array t2_vertices_in_the_line; int nb_of_t2_vertices_in_the_line = 0; auto push_to_t2_vertices_in_the_line = [&t2_vertices_in_the_line, &nb_of_t2_vertices_in_the_line](const Point_3* p) { t2_vertices_in_the_line.at(nb_of_t2_vertices_in_the_line++) = p; }; const Point_3* s_min1; const Point_3* t_min1; const Point_3* s_max1; const Point_3* t_max1; // Compute distance signs of p, q and r to the plane of triangle(a,b,c) const Orientation dp = make_certain(orientation(a,b,c,p)); const Orientation dq = make_certain(orientation(a,b,c,q)); const Orientation dr = make_certain(orientation(a,b,c,r)); if(dp == COPLANAR) push_to_t1_vertices_in_the_line(&p); if(dq == COPLANAR) push_to_t1_vertices_in_the_line(&q); if(dr == COPLANAR) push_to_t1_vertices_in_the_line(&r); auto comp = k.compare_xyz_3_object(); auto sort_ptrs = [&comp](const Point_3* p1, const Point_3* p2) { return comp(*p1, *p2) == SMALLER; }; auto intersection_is_a_vertex_or_a_common_edge = [&]() { std::sort(t1_vertices_in_the_line.data(), t1_vertices_in_the_line.data() + nb_of_t1_vertices_in_the_line, sort_ptrs); std::sort(t2_vertices_in_the_line.data(), t2_vertices_in_the_line.data() + nb_of_t2_vertices_in_the_line, sort_ptrs); std::size_t nb_of_common_vertices = 0; std::set_intersection( t1_vertices_in_the_line.data(), t1_vertices_in_the_line.data() + nb_of_t1_vertices_in_the_line, t2_vertices_in_the_line.data(), t2_vertices_in_the_line.data() + nb_of_t2_vertices_in_the_line, CGAL::Counting_output_iterator(&nb_of_common_vertices), sort_ptrs); return nb_of_common_vertices == 1 || nb_of_common_vertices == 2; }; switch(dp) { case POSITIVE: if(dq == POSITIVE) { if(dr == POSITIVE) // pqr lies in the open positive halfspace induced by // the plane of triangle(a,b,c) return false; // r is isolated on the negative side of the plane s_min1 = &q; t_min1 = &r; s_max1 = &r; t_max1 = &p; } else { if(dr == POSITIVE) { // q is isolated on the negative side of the plane s_min1 = &p; t_min1 = &q; s_max1 = &q; t_max1 = &r; } else { // p is isolated on the positive side of the plane s_min1 = &p; t_min1 = &q; s_max1 = &r; t_max1 = &p; } } break; case NEGATIVE: if(dq == NEGATIVE) { if(dr == NEGATIVE) // pqr lies in the open negative halfspace induced by // the plane of triangle(a,b,c) return false; // r is isolated on the positive side of the plane s_min1 = &r; t_min1 = &p; s_max1 = &q; t_max1 = &r; } else { if(dr == NEGATIVE) { // q is isolated on the positive side of the plane s_min1 = &q; t_min1 = &r; s_max1 = &p; t_max1 = &q; } else { // p is isolated on the negative side of the plane s_min1 = &r; t_min1 = &p; s_max1 = &p; t_max1 = &q; } } break; case COPLANAR: switch(dq) { case POSITIVE: if(dr == POSITIVE ) { // p is isolated on the negative side of the plane s_min1 = &r; t_min1 = &p; s_max1 = &p; t_max1 = &q; } else { // q is isolated on the positive side of the plane s_min1 = &q; t_min1 = &r; s_max1 = &p; t_max1 = &q; } break; case NEGATIVE: if(dr == NEGATIVE ) { // p is isolated on the positive side of the plane s_min1 = &p; t_min1 = &q; s_max1 = &r; t_max1 = &p; } else { // q is isolated on the negative side of the plane s_min1 = &p; t_min1 = &q; s_max1 = &q; t_max1 = &r; } break; case COPLANAR: switch(dr) { case POSITIVE: // r is isolated on the positive side of the plane s_min1 = &r; t_min1 = &p; s_max1 = &q; t_max1 = &r; break; case NEGATIVE: // r is isolated on the negative side of the plane s_min1 = &q; t_min1 = &r; s_max1 = &r; t_max1 = &p; break; case COPLANAR: { push_to_t2_vertices_in_the_line(&a); push_to_t2_vertices_in_the_line(&b); push_to_t2_vertices_in_the_line(&c); if(intersection_is_a_vertex_or_a_common_edge()) return false; return CGAL::Intersections::internal::do_intersect_coplanar(t1,t2,k); } default: // should not happen. CGAL_kernel_assertion(false); return false; } break; default: // should not happen. CGAL_kernel_assertion(false); return false; } break; default: // should not happen. CGAL_kernel_assertion(false); return false; } const Point_3* s_min2; const Point_3* t_min2; const Point_3* s_max2; const Point_3* t_max2; // Compute distance signs of a, b and c to the plane of triangle(p,q,r) const Orientation da = make_certain(orientation(p,q,r,a)); const Orientation db = make_certain(orientation(p,q,r,b)); const Orientation dc = make_certain(orientation(p,q,r,c)); if(da == COPLANAR) push_to_t2_vertices_in_the_line(&a); if(db == COPLANAR) push_to_t2_vertices_in_the_line(&b); if(dc == COPLANAR) push_to_t2_vertices_in_the_line(&c); if(intersection_is_a_vertex_or_a_common_edge()) return false; switch(da) { case POSITIVE: if(db == POSITIVE) { if(dc == POSITIVE) // abc lies in the open positive halfspace induced by // the plane of triangle(p,q,r) return false; // c is isolated on the negative side of the plane s_min2 = &b; t_min2 = &c; s_max2 = &c; t_max2 = &a; } else { if(dc == POSITIVE) { // b is isolated on the negative side of the plane s_min2 = &a; t_min2 = &b; s_max2 = &b; t_max2 = &c; } else { // a is isolated on the positive side of the plane s_min2 = &a; t_min2 = &b; s_max2 = &c; t_max2 = &a; } } break; case NEGATIVE: if(db == NEGATIVE) { if(dc == NEGATIVE) // abc lies in the open negative halfspace induced by // the plane of triangle(p,q,r) return false; // c is isolated on the positive side of the plane s_min2 = &c; t_min2 = &a; s_max2 = &b; t_max2 = &c; } else { if(dc == NEGATIVE) { // b is isolated on the positive side of the plane s_min2 = &b; t_min2 = &c; s_max2 = &a; t_max2 = &b; } else { // a is isolated on the negative side of the plane s_min2 = &c; t_min2 = &a; s_max2 = &a; t_max2 = &b; } } break; case COPLANAR: switch(db) { case POSITIVE: if(dc == POSITIVE) { // a is isolated on the negative side of the plane s_min2 = &c; t_min2 = &a; s_max2 = &a; t_max2 = &b; } else { // b is isolated on the positive side of the plane s_min2 = &b; t_min2 = &c; s_max2 = &a; t_max2 = &b; } break; case NEGATIVE: if(dc == NEGATIVE) { // a is isolated on the positive side of the plane s_min2 = &a; t_min2 = &b; s_max2 = &c; t_max2 = &a; } else { // b is isolated on the negative side of the plane s_min2 = &a; t_min2 = &b; s_max2 = &b; t_max2 = &c; } break; case COPLANAR: switch(dc) { case POSITIVE: // c is isolated on the positive side of the plane s_min2 = &c; t_min2 = &a; s_max2 = &b; t_max2 = &c; break; case NEGATIVE: // c is isolated on the negative side of the plane s_min2 = &b; t_min2 = &c; s_max2 = &c; t_max2 = &a; break; case COPLANAR: // Supposed unreachable code // since the triangles are assumed to be non-flat return CGAL::Intersections::internal::do_intersect_coplanar(t1,t2,k); default: // should not happen. CGAL_kernel_assertion(false); return false; } break; default: // should not happen. CGAL_kernel_assertion(false); return false; } break; default: // should not happen. CGAL_kernel_assertion(false); return false; } return orientation(*s_min1, *t_min1, *s_min2, *t_min2) == NEGATIVE && orientation(*s_max1, *t_max1, *t_max2, *s_max2) == NEGATIVE; } template bool does_tetrahedron_intersect_triangle_interior(typename Kernel::Tetrahedron_3 tet, typename Kernel::Triangle_3 tr, const Kernel& k) { CGAL_kernel_precondition(!k.is_degenerate_3_object()(tr)); CGAL_kernel_precondition(!k.is_degenerate_3_object()(tet)); for (int i = 0; i < 4; ++i) { if(does_first_triangle_intersect_second_triangle_interior( tr, k.construct_triangle_3_object()(tet[i], tet[(i + 1) % 4], tet[(i + 2) % 4]), k)) return true; } // decltype(auto) p = k.construct_vertex_3_object()(tr, 0); // if(k.has_on_bounded_side_3_object()(tet, p)) // return true; return false; } } // namespace CGAL namespace CGAL { #if __cpp_lib_concepts >= 201806L template concept Polygon_3 = std::ranges::common_range && (std::is_convertible_v, typename Kernel::Point_3>); template concept Range_of_polygon_3 = std::ranges::common_range && Polygon_3, Kernel>; #endif // concepts template > class Constrained_Delaunay_triangulation_vertex_base_3 : public Conforming_Delaunay_triangulation_vertex_base_3 { using Base = Conforming_Delaunay_triangulation_vertex_base_3; public: // To get correct vertex type in TDS template < class TDS3 > struct Rebind_TDS { typedef typename Vb::template Rebind_TDS::Other Vb3; typedef Constrained_Delaunay_triangulation_vertex_base_3 Other; }; using Base::Base; static std::string io_signature() { return Get_io_signature()(); } }; enum class CDT_3_cell_marker { CLEAR = 0, IN_REGION = 1, VISITED = 1, ON_REGION_BOUNDARY = 2, nb_of_markers }; template > class Constrained_Delaunay_triangulation_cell_base_3 : public Base_with_time_stamp { using Base = Base_with_time_stamp; std::array face_id = { -1, -1, -1, -1 }; std::array facet_2d = {nullptr, nullptr, nullptr, nullptr}; std::bitset(CDT_3_cell_marker::nb_of_markers)> markers; public: // To get correct cell type in TDS template < class TDS3 > struct Rebind_TDS { typedef typename Cb::template Rebind_TDS::Other Cb3; typedef Constrained_Delaunay_triangulation_cell_base_3 Other; }; // Constructor using Base::Base; bool is_marked() const { return markers.any(); } bool is_marked(CDT_3_cell_marker m) const { return markers.test(static_cast(m)); } void set_mark(CDT_3_cell_marker m) { markers.set(static_cast(m)); } void clear_mark(CDT_3_cell_marker m) { markers.reset(static_cast(m)); } void clear_marks() { markers.reset(); } bool is_facet_constrained(int i) const { return face_id[unsigned(i)] >= 0; } template void set_facet_constraint(int i, CDT_3_face_index face_id, Facet_handle facet_2d) { this->face_id[unsigned(i)] = face_id; this->facet_2d[unsigned(i)] = static_cast(facet_2d == Facet_handle{} ? nullptr : std::addressof(*facet_2d)); } CDT_3_face_index face_constraint_index(int i) const { return face_id[unsigned(i)]; } template auto face_2 (const CDT_2& cdt, int i) const { using Face = typename CDT_2::Face; auto ptr = static_cast(facet_2d[unsigned(i)]); return cdt.tds().faces().iterator_to(*ptr); } static std::string io_signature() { return Get_io_signature()() + "+(" + Get_io_signature()() + ")[4]"; } friend std::ostream& operator<<(std::ostream& os, const Constrained_Delaunay_triangulation_cell_base_3& c) { os << static_cast(c); for( unsigned li = 0; li < 4; ++li ) { if(IO::is_ascii(os)) { os << " " << c.face_id[li]; } else { CGAL::write(os, c.face_id[li]); } } return os; } friend std::istream& operator>>(std::istream& is, Constrained_Delaunay_triangulation_cell_base_3& c) { is >> static_cast(c); if(!is) return is; for( int li = 0; li < 4; ++li ) { int i; if(IO::is_ascii(is)) { is >> i; } else { CGAL::read(is, i); } if(!is) return is; c.face_id[li] = i; } return is; } }; template struct Output_rep, With_point_and_info_tag> : public Output_rep> { int offset = 0; using Base = Output_rep>; using CC_iterator = CGAL::internal::CC_iterator; using Compact_container = typename CC_iterator::CC; using Time_stamper = typename Compact_container::Time_stamper; using Base::Base; Output_rep(const CC_iterator& it, With_point_and_info_tag tag) : Base(it), offset(tag.offset) { } std::ostream& operator()(std::ostream& out) const { out << Time_stamper::display_id(this->it.operator->(), offset); if(this->it.operator->() != nullptr) { out << (this->it->is_Steiner_vertex_on_edge() ? "(Steiner)" : "") << (this->it->is_Steiner_vertex_in_face() ? "(Steiner in face)" : "") << "= " << this->it->point(); if(this->it->is_marked(CDT_3_vertex_marker::REGION_BORDER)) out << " (region border)"; if(this->it->is_marked(CDT_3_vertex_marker::REGION_INSIDE)) out << " (inside region)"; if(this->it->is_marked(CDT_3_vertex_marker::CAVITY)) out << " (cavity vertex)"; if(this->it->is_marked(CDT_3_vertex_marker::CAVITY_ABOVE)) out << " (vertex above)"; if(this->it->is_marked(CDT_3_vertex_marker::CAVITY_BELOW)) out << " (vertex below)"; return out; } else return out; } }; template class Constrained_Delaunay_triangulation_3 : public Conforming_Delaunay_triangulation_3 { public: using Conforming_Dt = Conforming_Delaunay_triangulation_3; using Vertex_handle = typename T_3::Vertex_handle; using Edge = typename T_3::Edge; using Facet = typename T_3::Facet; using Cell_handle = typename T_3::Cell_handle; using Geom_traits = typename T_3::Geom_traits; using Point_3 = typename T_3::Point; using Segment_3 = typename Geom_traits::Segment_3; using Vector_3 = typename Geom_traits::Vector_3; using Locate_type = typename T_3::Locate_type; using size_type = typename T_3::size_type; using Vertex_marker = CDT_3_vertex_marker; using Cell_marker = CDT_3_cell_marker; using Face_index = CDT_3_face_index; static std::string io_signature() { return Get_io_signature()(); } private: struct CDT_2_types { struct Projection_traits : public Projection_traits_3 { // struct Side_of_oriented_circle_2 : public Geom_traits::Coplanar_side_of_bounded_circle_3 { // using result_type = Oriented_side; // template auto operator()(Arg&&... arg) const // { // return CGAL::enum_cast( // Geom_traits::Coplanar_side_of_bounded_circle_3::operator()(std::forward(arg)...)); // } // }; // Side_of_oriented_circle_2 side_of_oriented_circle_2_object() const { return {}; } // using Compare_xy_2 = typename Geom_traits::Compare_xyz_3; // Compare_xy_2 compare_xy_2_object() const { return {}; } using Projection_traits_3::Projection_traits_3; // inherit cstr }; static_assert(std::is_nothrow_move_constructible::value, "move cstr is missing"); struct Vertex_info { Vertex_handle vertex_handle_3d = {}; }; using Color_value_type = std::int8_t; struct Face_info { Color_value_type is_outside_the_face = -1; Color_value_type is_in_region = 0; std::bitset<3> is_edge_also_in_3d_triangulation = 0; bool missing_subface = true; Facet facet_3d = {}; }; using Vb1 = Triangulation_vertex_base_with_info_2; using Vb = Base_with_time_stamp; using Fb1 = Triangulation_face_base_with_info_2; using Fb = Constrained_triangulation_face_base_2; using TDS = Triangulation_data_structure_2; using Itag = No_constraint_intersection_requiring_constructions_tag; using CDT_base = Constrained_Delaunay_triangulation_2; using CDT = CDT_base; template struct CDT_2_dual_color_map { using category = boost::read_write_property_map_tag; using reference = Color_value_type&; using value_type = Color_value_type; using key_type = typename CDT::Face_handle; friend reference get(CDT_2_dual_color_map, key_type fh) { return fh->info().*member_ptr; } friend void put(CDT_2_dual_color_map, key_type fh, value_type value) { fh->info().*member_ptr = value; } }; using Color_map_is_outside_the_face = CDT_2_dual_color_map<&Face_info::is_outside_the_face>; using Color_map_is_in_region = CDT_2_dual_color_map<&Face_info::is_in_region>; }; // CDT_2_types using CDT_2 = typename CDT_2_types::CDT; using CDT_2_traits = typename CDT_2_types::Projection_traits; using CDT_2_face_handle = typename CDT_2::Face_handle; using CDT_2_edge = typename CDT_2::Edge; static_assert(std::is_nothrow_move_constructible::value, "move cstr is missing"); static_assert(std::is_nothrow_move_assignable::value, "move assignment is missing"); protected: struct PLC_error : Error_exception { int face_index; int region_index; PLC_error(std::string msg, std::string file, int line, int face_index, int region_index) : Error_exception("CGAL CDT_3", msg, file, line), face_index(face_index), region_index(region_index) { } }; using Constraint_hierarchy = typename Conforming_Dt::Constraint_hierarchy; using Constraint_id = typename Constraint_hierarchy::Constraint_id; using Subconstraint = typename Constraint_hierarchy::Subconstraint; void register_facet_to_be_constrained(Cell_handle cell, int facet_index) { const auto face_id = static_cast(cell->face_constraint_index(facet_index)); this->face_constraint_misses_subfaces.set(face_id); auto fh_2 = cell->face_2(this->face_cdt_2[face_id], facet_index); fh_2->info().facet_3d = {}; fh_2->info().missing_subface = true; this->set_facet_constrained({cell, facet_index}, -1, {}); } void register_facet_to_be_constrained(Facet f) { const auto [cell, facet_index] = f; register_facet_to_be_constrained(cell, facet_index); } class Insert_in_conflict_visitor { Constrained_Delaunay_triangulation_3 &self; typename Conforming_Dt::Insert_in_conflict_visitor conforming_dt_visitor; public: Insert_in_conflict_visitor(Constrained_Delaunay_triangulation_3 &self) : self(self), conforming_dt_visitor(self) {} template void process_cells_in_conflict(const InputIterator cell_it_begin, const InputIterator end) { CGAL_assertion(self.dimension() >= 2); if(self.cdt_2_are_initialized) { const int first_li = self.dimension() == 2 ? 3 : 0; for(auto cell_it = cell_it_begin; cell_it != end; ++cell_it) { auto c = *cell_it; for(int li = first_li; li < 4; ++li) { if(c->is_facet_constrained(li)) { self.register_facet_to_be_constrained(c, li); #if CGAL_CDT_3_DEBUG_MISSING_TRIANGLES std::cerr << "Add missing triangle (from visitor), face #F" << face_id << ": \n"; self.write_2d_triangle(std::cerr, fh_2); #endif // CGAL_CDT_3_DEBUG_MISSING_TRIANGLES } } } } conforming_dt_visitor.process_cells_in_conflict(cell_it_begin, end); } void after_insertion(Vertex_handle v) { conforming_dt_visitor.after_insertion(v); } void reinsert_vertices(Vertex_handle v) { after_insertion(v); } Vertex_handle replace_vertex(Cell_handle c, int index, const Point_3 &) const { return c->vertex(index); } void hide_point(Cell_handle, const Point_3 &) const {} void insert_Steiner_point_on_constraint(Constraint_id constraint, Vertex_handle va, Vertex_handle vb, Vertex_handle v_Steiner) const { const auto point = self.point(v_Steiner); if(!self.cdt_2_are_initialized) return; for(const auto [_, poly_id] : CGAL::make_range(self.constraint_to_faces.equal_range(constraint))) { auto& non_const_cdt_2 = self.face_cdt_2[poly_id]; const auto& cdt_2 = non_const_cdt_2; auto opt_edge = self.edge_of_cdt_2(cdt_2, va, vb); CGAL_assume(opt_edge != std::nullopt); CGAL_assertion(cdt_2.is_constrained(*opt_edge)); auto [fh_2d, edge_index]= *opt_edge; const auto va_2d = fh_2d->vertex(cdt_2.cw(edge_index)); const auto vb_2d = fh_2d->vertex(cdt_2.ccw(edge_index)); const auto [mirror_fh_2d, mirror_edge_index] = cdt_2.mirror_edge(*opt_edge); const auto outside_on_right = fh_2d->info().is_outside_the_face; const auto outside_on_left = mirror_fh_2d->info().is_outside_the_face; const auto in_region_on_right = fh_2d->info().is_in_region; const auto in_region_on_left = mirror_fh_2d->info().is_in_region; fh_2d->set_constraint(edge_index, false); mirror_fh_2d->set_constraint(mirror_edge_index, false); const auto v_Steiner_2d = non_const_cdt_2.insert(point, fh_2d); v_Steiner_2d->info().vertex_handle_3d = v_Steiner; non_const_cdt_2.insert_constraint(va_2d, v_Steiner_2d); non_const_cdt_2.insert_constraint(vb_2d, v_Steiner_2d); // update the edge {fh_2d, edge_index} const bool is_edge = cdt_2.is_edge(va_2d, v_Steiner_2d, fh_2d, edge_index); CGAL_assume(is_edge); auto fc = cdt_2.incident_faces(v_Steiner_2d, fh_2d), fc_begin(fc); // circulators are counter-clockwise, so we start at the right of [va,v] do { fc->info().is_outside_the_face = outside_on_right; fc->info().is_in_region = in_region_on_right; ++fc; } while ( fc->vertex(cdt_2.ccw(fc->index(v_Steiner_2d))) != vb_2d ); do { fc->info().is_outside_the_face = outside_on_left; fc->info().is_in_region = in_region_on_left; } while(++fc != fc_begin); fc = cdt_2.incident_faces(v_Steiner_2d, fh_2d); fc_begin = fc; do { fc->info().missing_subface = true; const auto v_Steiner_index = fc->index(v_Steiner_2d); const auto other_edge = cdt_2.mirror_edge({fc, v_Steiner_index}); fc->info().is_edge_also_in_3d_triangulation.set(other_edge.first->info().is_edge_also_in_3d_triangulation.test(other_edge.second)); } while(++fc != fc_begin); self.face_constraint_misses_subfaces.set(poly_id); } conforming_dt_visitor.insert_Steiner_point_on_constraint(constraint, va, vb, v_Steiner); } Vertex_handle insert_in_triangulation(const Point_3& p, Locate_type lt, Cell_handle c, int li, int lj) { if(self.is_Delaunay) return self.insert_impl_do_not_split(p, lt, c, li, lj, *this); else return self.insert_in_cdt_3(p, lt, c, li, lj, *this); } }; public: Vertex_handle insert(const Point_3 &p, Locate_type lt, Cell_handle c, int li, int lj, bool restore_Delaunay = true) { this->update_bbox(p); auto v = Conforming_Dt::insert_impl(p, lt, c, li, lj, insert_in_conflict_visitor); if(restore_Delaunay) { Conforming_Dt::restore_Delaunay(insert_in_conflict_visitor); } return v; } template Vertex_handle insert_in_cdt_3(const Point_3& p, [[maybe_unused]] Locate_type lt, Cell_handle ch, int, int, Visitor& visitor) { CGAL_assertion(lt != Locate_type::VERTEX); boost::container::small_vector cells_of_original_cavity; boost::container::small_vector exterior_border_facets_of_original_cavity; auto output_iterator_to_facets = boost::make_function_output_iterator( [&](Facet f) { exterior_border_facets_of_original_cavity.push_back(tr.mirror_facet(f)); }); auto triple_of_output_iterators = make_triple( output_iterator_to_facets, std::back_inserter(cells_of_original_cavity), Emptyset_iterator{}); switch(tr.dimension()) { case 3: { typename T_3::Conflict_tester_3 tester(p, this); this->find_conflicts(ch, tester, triple_of_output_iterators); break; } // dim 3 case 2: { typename T_3::Conflict_tester_2 tester(p, this); this->find_conflicts(ch, tester, triple_of_output_iterators); break; } // dim 2 default: CGAL_error(); } // cleanup of tds_data after T_3::find_conflicts for(Cell_handle ch : cells_of_original_cavity) { ch->tds_data().clear(); } for(auto [ch, _] : exterior_border_facets_of_original_cavity) { ch->tds_data().clear(); } bool the_infinite_vertex_is_in_the_cavity = false; std::set vertices_of_original_cavity; for(Cell_handle ch : cells_of_original_cavity) { for(int i = 0; i < 4; ++i) { auto v = ch->vertex(i); if(tr.is_infinite(v)) { the_infinite_vertex_is_in_the_cavity = true; } vertices_of_original_cavity.insert(v); } } // add one extra vertex for p: auto p_vh = this->tds().create_vertex(); p_vh->set_point(p); vertices_of_original_cavity.insert(p_vh); // vertices of the border of the cavity should point to cells outside of it for(auto [c, index]: exterior_border_facets_of_original_cavity) { for(int i = 0; i < 3; ++i) { auto v = c->vertex(tr.vertex_triple_index(index, i)); v->set_cell(c); } } const auto [cavity_triangulation, vertices_of_cavity, map_cavity_vertices_to_ambient_vertices, facets_of_cavity, interior_constrained_faces, cells_of_cavity] = triangulate_cavity(cells_of_original_cavity, exterior_border_facets_of_original_cavity, vertices_of_original_cavity); for(auto f: interior_constrained_faces) { this->register_facet_to_be_constrained(f); } visitor.process_cells_in_conflict(cells_of_cavity.begin(), cells_of_cavity.end()); typename T_3::Vertex_triple_Facet_map outer_map; for(auto f: facets_of_cavity) { typename T_3::Vertex_triple vt = this->make_vertex_triple(f); this->make_canonical_oriented_triple(vt); outer_map[vt] = f; } const auto inner_map = tr.create_triangulation_inner_map( cavity_triangulation, map_cavity_vertices_to_ambient_vertices, the_infinite_vertex_is_in_the_cavity); this->copy_triangulation_into_hole(map_cavity_vertices_to_ambient_vertices, std::move(outer_map), inner_map, this->new_cells_output_iterator()); for(auto outside_facet : facets_of_cavity) { const auto [outside_cell, outside_face_index] = outside_facet; const auto mirror_facet = this->mirror_facet(outside_facet); if(outside_cell->is_facet_constrained(outside_face_index)) { const auto poly_id = outside_cell->face_constraint_index(outside_face_index); const CDT_2& cdt_2 = face_cdt_2[poly_id]; const auto f2d = outside_cell->face_2(cdt_2, outside_face_index); set_facet_constrained(mirror_facet, poly_id, f2d); } } for(auto c: cells_of_cavity) { this->tds().delete_cell(c); } this->new_vertex(p_vh); CGAL_assume(!this->debug_validity() || this->is_valid(true)); return p_vh; } Vertex_handle insert(const Point_3 &p, Cell_handle start = {}, bool restore_Delaunay = true) { Locate_type lt; int li, lj; Cell_handle c = tr.locate(p, lt, li, lj, start); return insert(p, lt, c, li, lj, restore_Delaunay); } Constraint_id insert_constrained_edge(Vertex_handle va, Vertex_handle vb, bool restore_Delaunay = true) { const auto id = this->insert_constrained_edge_impl(va, vb, insert_in_conflict_visitor); if(restore_Delaunay) { this->restore_Delaunay(); } return id; } void restore_Delaunay() { Conforming_Dt::restore_Delaunay(insert_in_conflict_visitor); } bool is_constrained(Facet f) const { return f.first->is_facet_constrained(f.second); } bool same_triangle(Facet f, CDT_2_face_handle fh) const { return true; const auto [c, facet_index] = f; std::array f_vertices{c->vertex(tr.vertex_triple_index(facet_index,0)), c->vertex(tr.vertex_triple_index(facet_index,1)), c->vertex(tr.vertex_triple_index(facet_index,2))}; std::sort(f_vertices.begin(), f_vertices.end()); std::array fh_vertices{fh->vertex(0)->info().vertex_handle_3d, fh->vertex(1)->info().vertex_handle_3d, fh->vertex(2)->info().vertex_handle_3d}; std::sort(fh_vertices.begin(), fh_vertices.end()); return (f_vertices == fh_vertices); } void set_facet_constrained(Facet f, CDT_3_face_index polygon_contraint_id, CDT_2_face_handle fh) { CGAL_assertion(fh == CDT_2_face_handle{} || same_triangle(f, fh)); const auto [c, facet_index] = f; c->set_facet_constraint(facet_index, polygon_contraint_id, fh); if(tr.dimension() > 2) { const auto [n, n_index] = tr.mirror_facet({c, facet_index}); n->set_facet_constraint(n_index, polygon_contraint_id, fh); } if(fh == CDT_2_face_handle{}) return; if(fh != CDT_2_face_handle{}) { fh->info().facet_3d = f; } } template ) Polygon> std::optional register_new_constrained_polygon(Polygon&& polygon) { return insert_constrained_polygon(std::forward(polygon), false); } template ) Polygon> std::optional insert_constrained_polygon(const Polygon& polygon, bool restore_Delaunay = true, Face_index face_index = -1) { std::vector handles; handles.reserve(polygon.size()); std::optional hint; for(const auto& p : polygon) { const auto v = this->insert(p, hint.value_or(Cell_handle{}), restore_Delaunay); handles.push_back(v); hint = v->cell(); } return insert_constrained_face(std::move(handles), restore_Delaunay, face_index); } template CGAL_CPP20_REQUIRE_CLAUSE(std::ranges::common_range && (std::is_convertible_v, Vertex_handle>)) std::optional insert_constrained_face(Vertex_handles&& vertex_handles, bool restore_Delaunay = true, Face_index face_index = -1) { #if CGAL_DEBUG_CDT_3 & 2 std::cerr << "insert_constrained_face (" << std::size(vertex_handles) << " vertices):\n"; for(auto v: vertex_handles) { std::cerr << " - " << this->display_vert(v) << '\n'; } #endif // CGAL_DEBUG_CDT_3 & 2 using std::begin; using std::endl; const auto first_it = begin(vertex_handles); const auto vend = end(vertex_handles); const auto size = std::distance(first_it, vend); if(size < 2) return {}; if(size == 2) { this->insert_constrained_edge(*first_it, *std::next(first_it)); return {}; } CGAL::Circulator_from_container> circ{&vertex_handles}; const auto circ_end{circ}; auto& borders = face_index < 0 ? this->face_borders.emplace_back() : this->face_borders[face_index]; auto& border = borders.emplace_back(); const auto polygon_contraint_id = face_index < 0 ? static_cast(this->face_borders.size() - 1) : face_index; do { const auto va = *circ; ++circ; const auto vb = *circ; const auto c_id = this->constraint_from_extremities(va, vb); if(c_id != Constraint_id{}) { const bool constraint_c_id_is_reversed = va != (*this->constraint_hierarchy.vertices_in_constraint_begin(c_id)); border.push_back(Face_edge{c_id, constraint_c_id_is_reversed}); constraint_to_faces.emplace(c_id, polygon_contraint_id); } else { const auto c_id = this->insert_constrained_edge(va, vb, restore_Delaunay); CGAL_assertion(c_id != Constraint_id{}); border.push_back(Face_edge{c_id}); constraint_to_faces.emplace(c_id, polygon_contraint_id); } } while(circ != circ_end); if(face_index < 0) { const auto accumulated_normal = std::invoke([&] { const auto last_it = std::next(first_it, size - 1); const auto &last_point = tr.point(*last_it); auto &&traits = tr.geom_traits(); auto &&cross_product = traits.construct_cross_product_vector_3_object(); auto &&vector = traits.construct_vector_3_object(); auto &&sum_vector = traits.construct_sum_of_vectors_3_object(); Vector_3 accumulated_normal = vector(CGAL::NULL_VECTOR); for (auto vit = first_it, next_it = std::next(first_it); next_it != last_it; ++vit, ++next_it) { accumulated_normal = sum_vector(accumulated_normal, cross_product(vector(last_point, tr.point(*vit)), vector(last_point, tr.point(*next_it)))); } if (accumulated_normal.x() < 0 || (accumulated_normal.x() == 0 && accumulated_normal.y() < 0) || (accumulated_normal.x() == 0 && accumulated_normal.y() == 0 && accumulated_normal.z() < 0) ) { accumulated_normal = - accumulated_normal; } return accumulated_normal; }); face_cdt_2.emplace_back(CDT_2_traits{accumulated_normal}); face_constraint_misses_subfaces.resize(face_cdt_2.size()); } #if CGAL_DEBUG_CDT_3 & 2 std::cerr << "insert_constrained_face return the polygon_contraint_id: " << polygon_contraint_id << '\n'; #endif return polygon_contraint_id; } std::optional> sequence_of_Steiner_vertices(Vertex_handle va, Vertex_handle vb) const { std::vector steiner_vertices; const auto c_id = this->constraint_from_extremities(va, vb); if(c_id != Constraint_id{}) { auto vit = this->constraint_hierarchy.vertices_in_constraint_begin(c_id); auto v_end = this->constraint_hierarchy.vertices_in_constraint_end(c_id); CGAL_assertion_code(const auto constraint_size = std::distance(vit, v_end);) if(vit != v_end) { const bool constraint_c_id_is_reversed = (*vit != va); CGAL_assertion(*vit == (constraint_c_id_is_reversed ? vb : va)); if(++vit != v_end && vit != --v_end) { CGAL_assertion(constraint_size == std::distance(vit, v_end) + 2); CGAL_assertion(*v_end == (constraint_c_id_is_reversed ? va : vb)); if(constraint_c_id_is_reversed) { using std::swap; swap(vit, v_end); --vit; --v_end; }; while(vit != v_end) { steiner_vertices.push_back(*vit); if(constraint_c_id_is_reversed) { --vit; } else { ++vit; }; } } } } else { return std::nullopt; } return {std::move(steiner_vertices)}; } private: void fill_cdt_2(CDT_2& cdt_2, CDT_3_face_index polygon_contraint_id) { #if CGAL_DEBUG_CDT_3 & 4 std::cerr << "Polygon #" << polygon_contraint_id << " normal is: " << cdt_2.geom_traits().normal() << '\n'; #endif // CGAL_DEBUG_CDT_3 const auto vec_of_handles = std::invoke([this, polygon_contraint_id]() { std::vector> vec_of_handles; for(const auto& border : this->face_borders[polygon_contraint_id]) { auto& handles = vec_of_handles.emplace_back(); for(const auto& face_edge : border) { const auto c_id = face_edge.constraint_id; const bool reversed = face_edge.is_reverse; const auto v_begin = this->constraint_hierarchy.vertices_in_constraint_begin(c_id); const auto v_end = this->constraint_hierarchy.vertices_in_constraint_end(c_id); CGAL_assertion(std::distance(v_begin, v_end) >= 2); auto va = *v_begin; auto vb = *std::prev(v_end); if(reversed) { using std::swap; swap(va, vb); } if(handles.empty()) { handles.push_back(va); } else { CGAL_assertion(handles.back() == va); } handles.push_back(vb); } CGAL_assertion(handles.front() == handles.back()); } return vec_of_handles; }); #if CGAL_DEBUG_CDT_3 & 4 std::cerr << " points\n"; for(const auto& handles : vec_of_handles) { for(auto it = handles.begin(), end = std::prev(handles.end()); it != end; ++it) { std::cerr << " " << tr.point(*it) << '\n'; } } #endif // create and fill the 2D triangulation { for(const auto& handles : vec_of_handles) { const auto first_2d = cdt_2.insert(tr.point(handles.front())); first_2d->info().vertex_handle_3d = handles.front(); auto previous_2d = first_2d; for(auto it = handles.begin(), end = std::prev(handles.end()); it != end; /* incremented in the loop */) { const auto va = *it; CGAL_assertion(previous_2d->info().vertex_handle_3d == va); ++it; const auto vb = *it; const auto c_id = this->constraint_from_extremities(va, vb); if(c_id != Constraint_id{}) { auto vit = this->constraint_hierarchy.vertices_in_constraint_begin(c_id); auto v_end = this->constraint_hierarchy.vertices_in_constraint_end(c_id); CGAL_assertion_code(const auto constraint_size = std::distance(vit, v_end);) if(vit != v_end) { const bool constraint_c_id_is_reversed = (*vit != va); CGAL_assertion(*vit == (constraint_c_id_is_reversed ? vb : va)); if(++vit != v_end && vit != --v_end) { CGAL_assertion(constraint_size == std::distance(vit, v_end) + 2); CGAL_assertion(*v_end == (constraint_c_id_is_reversed ? va : vb)); if(constraint_c_id_is_reversed) { using std::swap; swap(vit, v_end); --vit; --v_end; }; while(vit != v_end) { auto vh_2d = cdt_2.insert(tr.point(*vit)); vh_2d->info().vertex_handle_3d = *vit; #if CGAL_DEBUG_CDT_3 & 4 std::cerr << "cdt_2.insert_constraint (" << tr.point(previous_2d->info().vertex_handle_3d) << " , " << tr.point(vh_2d->info().vertex_handle_3d) << ")\n"; #endif // CGAL_DEBUG_CDT_3 cdt_2.insert_constraint(previous_2d, vh_2d); previous_2d = vh_2d; if(constraint_c_id_is_reversed) { --vit; } else { ++vit; }; } } } } auto vh_2d = it == end ? first_2d : cdt_2.insert(tr.point(vb)); if(it != end) { vh_2d->info().vertex_handle_3d = vb; } #if CGAL_DEBUG_CDT_3 & 4 std::cerr << "cdt_2.insert_constraint (" << tr.point(previous_2d->info().vertex_handle_3d) << " , " << tr.point(vh_2d->info().vertex_handle_3d) << ")\n"; #endif // CGAL_DEBUG_CDT_3 cdt_2.insert_constraint(previous_2d, vh_2d); previous_2d = vh_2d; } } { // mark all the faces outside/inside, starting from one infinite face for(auto fh: cdt_2.all_face_handles()) { fh->info().is_outside_the_face = -1; } struct Face_handle_and_outside { CDT_2_face_handle fh; bool outside; }; const auto d = cdt_2.dimension(); std::stack stack; stack.push({cdt_2.infinite_face(), true}); while(!stack.empty()) { const auto [fh, outside] = stack.top(); stack.pop(); if(fh->info().is_outside_the_face == -1) { fh->info().is_outside_the_face = outside; for(int i = 0; i <= d; ++i) { const auto neighbor = fh->neighbor(i); const auto new_outside = fh->is_constrained(i) ? !outside : outside; if(neighbor->info().is_outside_the_face == -1) { stack.push({neighbor, new_outside}); } } } } } // end of marking inside/outside #if CGAL_DEBUG_CDT_3 & 4 int counter = 0; for(const auto fh: cdt_2.finite_face_handles()) { if(!fh->info().is_outside_the_face) ++counter; } std::cerr << counter << " triangles(s) in the face\n"; #endif // CGAL_DEBUG_CDT_3 if(Algebraic_structure_traits::Is_exact::value && !std::all_of(cdt_2.finite_face_handles().begin(), cdt_2.finite_face_handles().end(), [=](const auto fh) { const auto p0 = cdt_2.point(fh->vertex(0)); const auto v1 = cdt_2.point(fh->vertex(1)) - p0; const auto v2 = cdt_2.point(fh->vertex(2)) - p0; return cross_product(cdt_2.geom_traits().normal(), cross_product(v1, v2)) == NULL_VECTOR; })) { std::cerr << std::string("Polygon #") + std::to_string(polygon_contraint_id) + " is not coplanar.\n"; } } // end of the construction of the CDT_2 if(cdt_2.number_of_vertices() == 4) { // for polygons with 4 vertices, 2 triangles for(auto [ch, index]: cdt_2.finite_edges()) { if(!ch->is_constrained(index)) { // here the edge {ch, index} is the diagonal [ac] of the polygon [abcd] const auto vb = ch->vertex(index); const auto [ch2, index2] = cdt_2.mirror_edge({ch, index}); const auto vd = ch2->vertex(index2); CGAL_assertion(!cdt_2.is_edge(vb, vd)); const auto vb_3d = vb->info().vertex_handle_3d; const auto vd_3d = vd->info().vertex_handle_3d; if(this->is_edge(vb_3d, vd_3d)) { // let's insert the diagonal [bd] in the CDT_2 cdt_2.insert_constraint(vb, vd); #if CGAL_DEBUG_CDT_3 & 64 std::cerr << "NOTE: CDT_2 has 4 vertices. Flip the diagonal\n"; #endif } } break; } } } void search_for_missing_subfaces(CDT_3_face_index polygon_contraint_id) { const CDT_2& cdt_2 = face_cdt_2[polygon_contraint_id]; for(const auto fh: cdt_2.all_face_handles()) { if(fh->info().is_outside_the_face) continue; const auto v0 = fh->vertex(0)->info().vertex_handle_3d; const auto v1 = fh->vertex(1)->info().vertex_handle_3d; const auto v2 = fh->vertex(2)->info().vertex_handle_3d; Cell_handle c; int i, j, k; if(!tr.is_facet(v0, v1, v2, c, i, j, k)) { fh->info().missing_subface = true; face_constraint_misses_subfaces.set(static_cast(polygon_contraint_id)); #if CGAL_CDT_3_DEBUG_MISSING_TRIANGLES std::cerr << std::format("Missing triangle in polygon #{}:\n", polygon_contraint_id); write_triangle(std::cerr, v0, v1, v2); #endif // CGAL_CDT_3_DEBUG_MISSING_TRIANGLES } else { fh->info().missing_subface = false; const int facet_index = 6 - i - j - k; set_facet_constrained({c, facet_index}, polygon_contraint_id, fh); } } } static auto region(const CDT_2& cdt_2, CDT_2_face_handle fh) { std::vector fh_region; const auto cdt_2_dual_graph = dual(cdt_2.tds()); const boost::filtered_graph dual( cdt_2_dual_graph, +[](CDT_2_edge edge) { // the `+` forces conversion of the lambda to a function pointer const auto face = edge.first; const auto i = unsigned(edge.second); return false == face->info().is_edge_also_in_3d_triangulation.test(i); }, +[](CDT_2_face_handle face_handle) { return false == face_handle->info().is_outside_the_face; }); boost::breadth_first_search(dual, fh, boost::color_map(typename CDT_2_types::Color_map_is_in_region()) .visitor(boost::make_bfs_visitor(boost::write_property( boost::typed_identity_property_map(), std::back_inserter(fh_region), boost::on_finish_vertex())))); CGAL_assertion(!fh_region.empty()); CGAL_assertion(fh == fh_region[0]); return fh_region; } auto brute_force_border_3_of_region([[maybe_unused]] CDT_3_face_index face_index, [[maybe_unused]] int region_index, [[maybe_unused]] const CDT_2& cdt_2, const std::vector& fh_region) { const std::set fh_region_set{fh_region.begin(), fh_region.end()}; std::vector border_edges; for(const auto fh: fh_region) { for(int index = 0; index < 3; ++index) { if(fh_region_set.count(fh->neighbor(index)) > 0) continue; // otherwise we have a border edge: fh->neighbor(i) is not in the region const auto va = fh->vertex(CDT_2::ccw(index))->info().vertex_handle_3d; const auto vb = fh->vertex(CDT_2:: cw(index))->info().vertex_handle_3d; Cell_handle c; int i, j; CGAL_assume_code(bool b =) this->tds().is_edge(va, vb, c, i, j); CGAL_assume(b); border_edges.emplace_back(c, i, j); } } if(this->debug_regions()) { std::cerr << "region size is: " << fh_region.size() << "\n"; std::cerr << "region border size is: " << border_edges.size() << "\n"; } return border_edges; } struct Intersection_error : public std::runtime_error { using Seg = typename Geom_traits::Segment_3; using Tri = typename Geom_traits::Triangle_3; Intersection_error(Seg s, Tri t, std::string what) : std::runtime_error(what), segment(s), triangle(t) {} Seg segment; Tri triangle; }; template int does_edge_intersect_region(Cell_handle cell, int index_vc, int index_vd, const CDT_2& cdt_2, const Fh_region& fh_region) { const auto vc = cell->vertex(index_vd); const auto vd = cell->vertex(index_vc); const auto pc = this->point(vc); const auto pd = this->point(vd); const typename Geom_traits::Segment_3 seg{pc, pd}; for(const auto fh_2d : fh_region) { const auto v0 = fh_2d->vertex(0)->info().vertex_handle_3d; const auto v1 = fh_2d->vertex(1)->info().vertex_handle_3d; const auto v2 = fh_2d->vertex(2)->info().vertex_handle_3d; if(vc == v0 || vc == v1 || vc == v2 || vd == v0 || vd == v1 || vd == v2) { continue; } const auto t0 = cdt_2.point(fh_2d->vertex(0)); const auto t1 = cdt_2.point(fh_2d->vertex(1)); const auto t2 = cdt_2.point(fh_2d->vertex(2)); const auto opc = CGAL::orientation(t0, t1, t2, pc); const auto opd = CGAL::orientation(t0, t1, t2, pd); if(opc == CGAL::COPLANAR || opd == CGAL::COPLANAR || opc == opd) { continue; } else { // otherwise the segment intersects the plane of the triangle if(CGAL::orientation(pc, pd, t0, t1) != opc && CGAL::orientation(pc, pd, t1, t2) != opc && CGAL::orientation(pc, pd, t2, t0) != opc) { return static_cast(opc); } } } return 0; } struct Search_first_intersection_result_type { Edge intersecting_edge; Edge border_edge; }; // Given a region and a border edge of it, returns an edge in the link of the // border edge that intersects the region. // The returned edge has its first vertex above the region. template std::optional search_first_intersection(CDT_3_face_index /*face_index*/, const CDT_2& cdt_2, const Fh_region& fh_region, const Edges_container& border_edges) { for(const auto border_edge: border_edges) { const auto [c, i, j] = border_edge; const Vertex_handle va_3d = c->vertex(i); const Vertex_handle vb_3d = c->vertex(j); // std::ofstream dump_edges_around("dump_edges_around.polylines.txt"); // dump_edges_around.precision(17); auto cell_circ = this->incident_cells(c, i, j), end = cell_circ; CGAL_assertion(cell_circ != nullptr); do { if(this->is_infinite(cell_circ)) { continue; } const auto index_va = cell_circ->index(va_3d); const auto index_vb = cell_circ->index(vb_3d); const auto index_vc = this->next_around_edge(index_va, index_vb); const auto index_vd = this->next_around_edge(index_vb, index_va); //write_segment(dump_edges_around, cell_circ->vertex(index_vc), cell_circ->vertex(index_vd)); if(cell_circ->vertex(index_vc)->is_marked(Vertex_marker::REGION_BORDER)) continue; if(cell_circ->vertex(index_vd)->is_marked(Vertex_marker::REGION_BORDER)) continue; int cd_intersects_region = does_edge_intersect_region(cell_circ, index_vc, index_vd, cdt_2, fh_region); if(cd_intersects_region == 1) { return Search_first_intersection_result_type{ Edge{cell_circ, index_vc, index_vd}, border_edge }; } if(cd_intersects_region == -1) { return Search_first_intersection_result_type{ Edge{cell_circ, index_vd, index_vc}, border_edge }; } } while(++cell_circ != end); } return {}; } struct Next_region : std::logic_error { using std::logic_error::logic_error; CDT_2_face_handle fh_2d; // create a new region Next_region(const std::string& what, CDT_2_face_handle fh) : std::logic_error(what), fh_2d(fh) {} }; template auto construct_cavities(CDT_3_face_index face_index, int region_index, const CDT_2& cdt_2, const Fh_region& fh_region, const Vertices_container& region_border_vertices, const Vertices_container& region_vertices, Edge first_intersecting_edge, Edges_container border_edges) { // outputs struct Outputs { std::vector intersecting_edges; std::set intersecting_cells; std::vector vertices_of_upper_cavity; std::vector vertices_of_lower_cavity; std::vector facets_of_upper_cavity; std::vector facets_of_lower_cavity; } outputs{ {}, {}, {region_vertices.begin(), region_vertices.end()}, {region_vertices.begin(), region_vertices.end()}, {}, {}}; auto& [intersecting_edges, intersecting_cells, vertices_of_upper_cavity, vertices_of_lower_cavity, facets_of_upper_cavity, facets_of_lower_cavity] = outputs; std::set> non_intersecting_edges_set; // marker for already visited elements std::set visited_vertices; std::map, bool> visited_edges; std::set visited_cells; auto make__new_element_functor = [](auto& visited_set) { return [&visited_set](auto... e) { const auto [_, not_already_visited] = visited_set.emplace(e...); return not_already_visited; }; }; auto new_vertex = make__new_element_functor(visited_vertices); auto new_cell = make__new_element_functor(visited_cells); auto new_edge = [&](Vertex_handle v0, Vertex_handle v1, bool does_intersect) { CGAL_assertion(v0 != Vertex_handle{}); return visited_edges.emplace(CGAL::make_sorted_pair(v0, v1), does_intersect); }; using Mesh = Surface_mesh; using Face_index = typename Mesh::Face_index; using EK = CGAL::Exact_predicates_exact_constructions_kernel; const auto to_exact = CGAL::Cartesian_converter(); const auto from_exact = CGAL::Cartesian_converter(); #if CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_regions()) { Mesh tets_intersect_region_mesh; auto [color_vpmap, _] = tets_intersect_region_mesh.template add_property_map("f:patch_id"); for(auto ch : tr.finite_cell_handles()) { auto tetrahedron = typename Geom_traits::Tetrahedron_3{tr.point(ch->vertex(0)), tr.point(ch->vertex(1)), tr.point(ch->vertex(2)), tr.point(ch->vertex(3))}; if(!std::any_of(fh_region.begin(), fh_region.end(), [&](auto fh) { const auto v0 = fh->vertex(0)->info().vertex_handle_3d; const auto v1 = fh->vertex(1)->info().vertex_handle_3d; const auto v2 = fh->vertex(2)->info().vertex_handle_3d; const auto triangle = typename Geom_traits::Triangle_3{tr.point(v0), tr.point(v1), tr.point(v2)}; return does_tetrahedron_intersect_triangle_interior(tetrahedron, triangle, tr.geom_traits()); })) { continue; } bool intersects = false; for(int i = 0; i < 4; ++i) { for(int j = i + 1; j < 4; ++j) { int intersects_region = does_edge_intersect_region(ch, i, j, cdt_2, fh_region); if(intersects_region != 0) { intersects = true; } } } if(!intersects) { std::cerr << "ERROR: tetrahedron #" << ch->time_stamp() << " has no edge intersecting the region\n"; } std::ofstream dump_tetrahedron( std::format("dump_intersecting_{}_{}_tetrahedron_{}.off", face_index, region_index, ch->time_stamp())); dump_tetrahedron.precision(17); Mesh mesh; CGAL::make_tetrahedron(tr.point(ch->vertex(0)), tr.point(ch->vertex(1)), tr.point(ch->vertex(2)), tr.point(ch->vertex(3)), mesh); dump_tetrahedron << mesh; dump_tetrahedron.close(); auto exact_tetrahedron = to_exact(tetrahedron); for(auto fh : fh_region) { auto v0 = fh->vertex(0)->info().vertex_handle_3d; auto v1 = fh->vertex(1)->info().vertex_handle_3d; auto v2 = fh->vertex(2)->info().vertex_handle_3d; auto triangle = typename Geom_traits::Triangle_3{tr.point(v0), tr.point(v1), tr.point(v2)}; auto exact_triangle = to_exact(triangle); auto tetrahedron_triangle_intersection_opt = CGAL::intersection(exact_tetrahedron, exact_triangle); if(!tetrahedron_triangle_intersection_opt) { continue; } if(const auto* tri = std::get_if(&tetrahedron_triangle_intersection_opt.value())) { exact(*tri); auto v0 = tets_intersect_region_mesh.add_vertex(from_exact((*tri)[0])); auto v1 = tets_intersect_region_mesh.add_vertex(from_exact((*tri)[1])); auto v2 = tets_intersect_region_mesh.add_vertex(from_exact((*tri)[2])); std::array arr{v0, v1, v2}; auto f = CGAL::Euler::add_face(arr, tets_intersect_region_mesh); put(color_vpmap, f, static_cast(ch->time_stamp())); } if(const auto* vec = std::get_if>(&tetrahedron_triangle_intersection_opt.value())) { std::vector vec_of_indices; for(const auto& p : *vec) { exact(p); vec_of_indices.push_back(tets_intersect_region_mesh.add_vertex(from_exact(p))); } CGAL::Euler::add_face(vec_of_indices, tets_intersect_region_mesh); } } } std::ofstream tets_intersect_region_out( std::format("dump_tets_intersect_region_{}_{}.ply", face_index, region_index)); tets_intersect_region_out.precision(17); CGAL::IO::write_PLY(tets_intersect_region_out, tets_intersect_region_mesh); tets_intersect_region_out.close(); } #endif // CGAL_CDT_3_CAN_USE_CXX20_FORMAT intersecting_edges.push_back(first_intersecting_edge); const auto [v0, v1] = tr.vertices(first_intersecting_edge); (void)new_edge(v0, v1, true); for(std::size_t i = 0; i < intersecting_edges.size(); ++i) { const auto intersecting_edge = intersecting_edges[i]; const auto [v_above, v_below] = tr.vertices(intersecting_edge); #if CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_regions()) { std::cerr << std::format("restore_subface_region face index: {}, region #{}, intersecting edge #{}: ({} {})\n", face_index, region_index, i, IO::oformat(v_above, with_point_and_info), IO::oformat(v_below, with_point_and_info)); dump_region(face_index, region_index, cdt_2); } #endif // CGAL_CDT_3_CAN_USE_CXX20_FORMAT #if CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_regions()) { const auto p_above = this->point(v_above); const auto p_below = this->point(v_below); const auto edge_segment = typename Geom_traits::Segment_3{p_above, p_below}; const auto exact_edge_segment = to_exact(edge_segment); std::ofstream intersect_out("dump_edge_region_intersection.xyz"); intersect_out.precision(17); for(auto fh: fh_region) { auto v0 = fh->vertex(0)->info().vertex_handle_3d; auto v1 = fh->vertex(1)->info().vertex_handle_3d; auto v2 = fh->vertex(2)->info().vertex_handle_3d; auto triangle = typename Geom_traits::Triangle_3{tr.point(v0), tr.point(v1), tr.point(v2)}; auto exact_triangle = to_exact(triangle); if(auto edge_intersection_opt = CGAL::intersection(exact_edge_segment, exact_triangle)) { const auto& edge_intersection = *edge_intersection_opt; if(const auto* p = std::get_if(&edge_intersection)) { exact(*p); intersect_out << *p << '\n'; } } } intersect_out.close(); auto cells_around_intersecting_edge = Container_from_circulator{this->incident_cells(intersecting_edge)}; for(const auto& cell: cells_around_intersecting_edge) { CGAL_assertion(!cell.has_vertex(tr.infinite_vertex())); auto tetrahedron = typename Geom_traits::Tetrahedron_3{tr.point(cell.vertex(0)), tr.point(cell.vertex(1)), tr.point(cell.vertex(2)), tr.point(cell.vertex(3))}; for(auto fh: fh_region) { auto v0 = fh->vertex(0)->info().vertex_handle_3d; auto v1 = fh->vertex(1)->info().vertex_handle_3d; auto v2 = fh->vertex(2)->info().vertex_handle_3d; auto triangle = typename Geom_traits::Triangle_3{tr.point(v0), tr.point(v1), tr.point(v2)}; auto exact_triangle = to_exact(triangle); } std::cerr << std::format("Test tetrahedron (#{}):\n {}\n {}\n {}\n {}\n", cell.time_stamp(), IO::oformat(cell.vertex(0), with_point_and_info), IO::oformat(cell.vertex(1), with_point_and_info), IO::oformat(cell.vertex(2), with_point_and_info), IO::oformat(cell.vertex(3), with_point_and_info)); if(!std::any_of(fh_region.begin(), fh_region.end(), [&](const auto fh) { auto v0 = fh->vertex(0)->info().vertex_handle_3d; auto v1 = fh->vertex(1)->info().vertex_handle_3d; auto v2 = fh->vertex(2)->info().vertex_handle_3d; auto triangle = typename Geom_traits::Triangle_3{tr.point(v0), tr.point(v1), tr.point(v2)}; bool b = does_tetrahedron_intersect_triangle_interior(tetrahedron, triangle, tr.geom_traits()); if(b) { std::cerr << " intersects the region\n"; } return b; })) { std::cerr << std::format( "ERROR: The following tetrahedron (#{}) does not intersect the region:\n {}\n {}\n {}\n {}\n", cell.time_stamp(), IO::oformat(cell.vertex(0), with_point_and_info), IO::oformat(cell.vertex(1), with_point_and_info), IO::oformat(cell.vertex(2), with_point_and_info), IO::oformat(cell.vertex(3), with_point_and_info)); } } } #endif // CGAL_CDT_3_CAN_USE_CXX20_FORMAT auto test_edge = [&](Cell_handle cell, Vertex_handle v0, int index_v0, Vertex_handle v1, int index_v1, [[maybe_unused]] int expected) { auto value_returned = [this](bool b) { #if CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_regions()) { std::cerr << std::format(" return {}\n", b); } #endif // CGAL_CDT_3_CAN_USE_CXX20_FORMAT return b; }; #if CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_regions()) { std::cerr << std::format(" test_edge {} {} ", IO::oformat(v0, with_point_and_info), IO::oformat(v1, with_point_and_info)); } #endif // CGAL_CDT_3_CAN_USE_CXX20_FORMAT auto [cached_value_it, not_visited] = new_edge(v0, v1, false); if(!not_visited) return value_returned(cached_value_it->second); int v0v1_intersects_region = (v0->is_marked(Vertex_marker::REGION_INSIDE) || v1->is_marked(Vertex_marker::REGION_INSIDE)) ? expected : does_edge_intersect_region(cell, index_v0, index_v1, cdt_2, fh_region); if(v0v1_intersects_region != 0) { if(this->use_older_cavity_algorithm()) { if(v0v1_intersects_region != expected) { throw PLC_error{"PLC error: v0v1_intersects_region != expected" , __FILE__, __LINE__, face_index, region_index}; } } // report the edge with first vertex above the region if(v0v1_intersects_region < 0) { std::swap(index_v0, index_v1); } intersecting_edges.emplace_back(cell, index_v0, index_v1); cached_value_it->second = true; return value_returned(true); } else { non_intersecting_edges_set.insert(make_sorted_pair(v0, v1)); cached_value_it->second = false; return value_returned(false); } }; if(this->use_older_cavity_algorithm()) { CGAL_assertion(0 == region_border_vertices.count(v_above)); CGAL_assertion(0 == region_border_vertices.count(v_below)); if(new_vertex(v_above)) { vertices_of_upper_cavity.push_back(v_above); } if(new_vertex(v_below)) { vertices_of_lower_cavity.push_back(v_below); } } auto facet_circ = this->incident_facets(intersecting_edge); const auto facet_circ_end = facet_circ; do { // loop facets around [v_above, v_below] CGAL_assertion(false == this->is_infinite(*facet_circ)); const auto cell = facet_circ->first; const auto facet_index = facet_circ->second; CGAL_assertion_msg(!cell->is_facet_constrained(facet_index), "intersecting polygons!"); if(new_cell(cell)) { intersecting_cells.insert(cell); } const auto index_v_above = cell->index(v_above); const auto index_v_below = cell->index(v_below); const auto index_vc = 6 - index_v_above - index_v_below - facet_index; const auto vc = cell->vertex(index_vc); if(region_border_vertices.count(vc) > 0) continue; // intersecting edges cannot touch the border if(!test_edge(cell, v_above, index_v_above, vc, index_vc, 1) && !test_edge(cell, v_below, index_v_below, vc, index_vc, -1) && this->use_older_cavity_algorithm()) { dump_triangulation(); dump_region(face_index, region_index, cdt_2); { std::ofstream out(std::string("dump_two_edges_") + std::to_string(face_index) + ".polylines.txt"); out.precision(17); write_segment(out, Edge{cell, index_v_above, index_vc}); write_segment(out, Edge{cell, index_v_below, index_vc}); } throw PLC_error{"PLC error: !test_edge(v_above..) && !test_edge(v_below..)" , __FILE__, __LINE__, face_index, region_index}; } } while(++facet_circ != facet_circ_end); if(!this->use_older_cavity_algorithm() && i + 1 == intersecting_edges.size()) { for(auto ch: intersecting_cells) { if(this->debug_regions()) { std::cerr << "tetrahedron #" << ch->time_stamp() << " intersects the region\n"; } for(int i = 0; i < 4; ++i) { for(int j = i + 1; j < 4; ++j) { test_edge(ch, ch->vertex(i), i, ch->vertex(j), j, 1); } } for(int i = 0; i < 4; ++i) { auto n_ch = ch->neighbor(i); if(tr.is_infinite(n_ch)) continue; if(new_cell(n_ch)) { auto tetrahedron = typename Geom_traits::Tetrahedron_3{tr.point(n_ch->vertex(0)), tr.point(n_ch->vertex(1)), tr.point(n_ch->vertex(2)), tr.point(n_ch->vertex(3))}; auto tet_bbox = tetrahedron.bbox(); if(std::any_of(fh_region.begin(), fh_region.end(), [&](auto fh) { const auto v0 = fh->vertex(0)->info().vertex_handle_3d; const auto v1 = fh->vertex(1)->info().vertex_handle_3d; const auto v2 = fh->vertex(2)->info().vertex_handle_3d; const auto triangle = typename Geom_traits::Triangle_3{tr.point(v0), tr.point(v1), tr.point(v2)}; const auto tri_bbox = triangle.bbox(); if(CGAL::do_overlap(tet_bbox, tri_bbox)) { return does_tetrahedron_intersect_triangle_interior(tetrahedron, triangle, tr.geom_traits()); } else { return false; } })) { intersecting_cells.insert(n_ch); if(this->debug_regions()) { std::cerr << "tetrahedron #" << n_ch->time_stamp() << " intersects the region\n"; } } else if(this->debug_regions()) { std::cerr << "NO, tetrahedron #" << n_ch->time_stamp() << " does not intersect the region\n"; } for(int i = 0; i < 4; ++i) { for(int j = i + 1; j < 4; ++j) { test_edge(n_ch, n_ch->vertex(i), i, n_ch->vertex(j), j, 1); } } } } } } // last intersecting edge, and new algorithm } // end loop on intersecting_edges if(this->use_older_cavity_algorithm()) { for(auto intersecting_edge: intersecting_edges) { const auto [v_above, v_below] = tr.vertices(intersecting_edge); auto cell_circ = this->incident_cells(intersecting_edge), end = cell_circ; CGAL_assume(cell_circ != nullptr); do { const Cell_handle cell = cell_circ; const auto index_v_above = cell->index(v_above); const auto index_v_below = cell->index(v_below); const auto cell_above = cell->neighbor(index_v_below); const auto cell_below = cell->neighbor(index_v_above); if(0 == intersecting_cells.count(cell_above)) { facets_of_upper_cavity.emplace_back(cell_above, cell_above->index(cell)); } if(0 == intersecting_cells.count(cell_below)) { facets_of_lower_cavity.emplace_back(cell_below, cell_below->index(cell)); } } while(++cell_circ != end); } } // older algorithm std::set facets_of_border; Union_find vertices_of_cavity_union_find; if(!this->use_older_cavity_algorithm()) { for(auto c: intersecting_cells) { for(int i = 0; i < 4; ++i) { auto n = c->neighbor(i); if(intersecting_cells.count(n) == 0) { facets_of_border.emplace(n, n->index(c)); } } } Unique_hash_map::handle> vertices_of_cavity_handles; for(auto c: intersecting_cells) { for(auto v : tr.vertices(c)) { if(!v->is_marked()) { v->set_mark(Vertex_marker::CAVITY); vertices_of_cavity_handles[v] = vertices_of_cavity_union_find.make_set(v); } } } for(auto facet: facets_of_border) { auto vertices = tr.vertices(facet); for(int i = 0; i < 3; ++i) { auto v1 = vertices[i]; auto v2 = vertices[(i + 1) % 3]; if(v1->is_marked(Vertex_marker::CAVITY) && v2->is_marked(Vertex_marker::CAVITY)) { vertices_of_cavity_union_find.unify_sets(vertices_of_cavity_handles[v1], vertices_of_cavity_handles[v2]); } } } if(vertices_of_cavity_union_find.number_of_sets() > 2) { for(auto c : intersecting_cells) { for(int i = 0; i < 4; ++i) { for(int j = i + 1; j < 4; ++j) { const auto v1 = c->vertex(i); const auto v2 = c->vertex(j); if(v1->is_marked(Vertex_marker::CAVITY) && v2->is_marked(Vertex_marker::CAVITY) && non_intersecting_edges_set.count(make_sorted_pair(v1, v2)) > 0) { vertices_of_cavity_union_find.unify_sets(vertices_of_cavity_handles[v1], vertices_of_cavity_handles[v2]); } } } if(vertices_of_cavity_union_find.number_of_sets() <= 2) break; } } Vertex_handle vertex_above{}; Edges_container all_border_edges{border_edges.begin(), border_edges.end()}; std::for_each(border_edges.begin(), border_edges.end(), [&](auto edge) { all_border_edges.emplace_back(edge.first, edge.third, edge.second); }); for(const auto& border_edge: all_border_edges) { const auto [border_edge_va, border_edge_vb] = tr.vertices(border_edge); auto circ = tr.incident_cells(border_edge); CGAL_assertion(circ != nullptr); const auto end = circ; do { const auto index_va = circ->index(border_edge_va); const auto index_vb = circ->index(border_edge_vb); const auto face_index = tr.next_around_edge(index_va, index_vb); if(facets_of_border.count(Facet{circ, face_index}) > 0) { const auto other_vertex_index = 6 - index_va - index_vb - face_index; const auto other_vertex = circ->vertex(other_vertex_index); if(other_vertex->is_marked(Vertex_marker::CAVITY)) { vertex_above = circ->vertex(other_vertex_index); break; } } } while(++circ != end); if(vertex_above != Vertex_handle{}) break; } CGAL_assume(vertex_above != Vertex_handle{}); const auto vertex_above_handle = vertices_of_cavity_handles[vertex_above]; auto it = vertices_of_cavity_union_find.begin(); while(it != vertices_of_cavity_union_find.end() && vertices_of_cavity_union_find.same_set(it, vertex_above_handle)) { ++it; } CGAL_assertion((it == vertices_of_cavity_union_find.end()) == (vertices_of_cavity_union_find.number_of_sets() == 1)); const auto vertex_below_handle = it; for(auto handle = vertices_of_cavity_union_find.begin(), end = vertices_of_cavity_union_find.end(); handle != end; ++handle) { auto v = *handle; v->clear_mark(Vertex_marker::CAVITY); if(vertices_of_cavity_union_find.same_set(handle, vertex_above_handle)) { vertices_of_upper_cavity.push_back(v); v->set_mark(Vertex_marker::CAVITY_ABOVE); } else if(vertices_of_cavity_union_find.same_set(handle, vertex_below_handle)) { vertices_of_lower_cavity.push_back(v); v->set_mark(Vertex_marker::CAVITY_BELOW); } else { CGAL_error(); } } while(std::any_of(intersecting_cells.begin(), intersecting_cells.end(), [&](Cell_handle c) { const auto vs = tr.vertices(c); return std::any_of(vs.begin(), vs.end(), [&](auto v) { if(!v->is_marked()) { std::cerr << "INFO: Vertex " << IO::oformat(v, with_point_and_info) << " is not marked\n"; return true; } return false; }); })) { std::for_each(intersecting_cells.begin(), intersecting_cells.end(), [&](Cell_handle c) { for(int i = 0; i < 4; ++i) { for(int j = i + 1; j < 4; ++j) { auto v1 = c->vertex(i); auto v2 = c->vertex(j); if(v1->is_marked() != v2->is_marked()) { if(v2->is_marked()) { std::swap(v1, v2); } // here v1 is marked and v2 is not if(v1->is_marked(Vertex_marker::CAVITY_ABOVE)) { vertices_of_upper_cavity.push_back(v2); v2->set_mark(Vertex_marker::CAVITY_ABOVE); } else if(v1->is_marked(Vertex_marker::CAVITY_BELOW)) { vertices_of_lower_cavity.push_back(v2); v2->set_mark(Vertex_marker::CAVITY_BELOW); } } } } }); } } // new algorithm if(this->debug_regions()) { std::stringstream ss_filename; ss_filename << "dump_facets_of_cavity_region_" << face_index << "_" << region_index << "_border.off"; std::ofstream out(ss_filename.str()); out.precision(17); write_facets(out, tr, facets_of_border); } if(this->debug_regions()) { for(auto edge : intersecting_edges) { auto [v1, v2] = tr.vertices(edge); std::cerr << std::format(" edge: {} {}\n", IO::oformat(v1, with_point_and_info), IO::oformat(v2, with_point_and_info)); } } if(!this->use_older_cavity_algorithm()) { for(auto facet: facets_of_border) { if(this->debug_regions()) { std::cerr << " facet: "; const auto facet_vertices = tr.vertices(facet); for(auto v: facet_vertices) { std::cerr << IO::oformat(v, with_point_and_info) << " "; } // This assertion is wrong, because there might be only one half-cavity and not a full cavity. // CGAL_assertion(!std::all_of(facet_vertices.begin(), facet_vertices.end(), // [](auto v) { return v->is_marked(Vertex_marker::REGION_BORDER); })); std::cerr << "\n"; } for(auto v: tr.vertices(facet)) { if(v->is_marked(Vertex_marker::CAVITY_ABOVE)) { facets_of_upper_cavity.push_back(facet); break; } if(v->is_marked(Vertex_marker::CAVITY_BELOW)) { facets_of_lower_cavity.push_back(facet); break; } } } for(auto v: vertices_of_upper_cavity) { v->clear_mark(Vertex_marker::CAVITY_ABOVE); } for(auto v: vertices_of_lower_cavity) { v->clear_mark(Vertex_marker::CAVITY_BELOW); } } if(this->debug_regions()) { std::stringstream ss_filename; ss_filename << "dump_facets_of_upper_cavity_region_" << face_index << "_" << region_index << "_border.off"; std::ofstream out(ss_filename.str()); out.precision(17); write_facets(out, tr, facets_of_upper_cavity); out.close(); ss_filename.str(""); ss_filename << "dump_facets_of_lower_cavity_region_" << face_index << "_" << region_index << "_border.off"; out.open(ss_filename.str()); write_facets(out, tr, facets_of_lower_cavity); out.close(); } return outputs; } template static void visit_convex_hull_of_triangulation(const Tr& tr, Function f) { const auto inf_vh = tr.infinite_vertex(); tr.incident_cells(inf_vh, boost::make_function_output_iterator([&](Cell_handle c) { const auto facet_index = c->index(inf_vh); f(Facet{c, facet_index}); return true; })); } using Conforming_Dt::with_offset; using Conforming_Dt::with_point; using Conforming_Dt::with_point_and_info; template void restore_subface_region(CDT_3_face_index face_index, int region_index, CDT_2& non_const_cdt_2, Fh_region& non_const_fh_region) { if(this->debug_regions()) { std::cerr << "restore_subface_region face index: " << face_index << ", region #" << region_index << "\n"; } const auto& cdt_2 = non_const_cdt_2; const auto& fh_region = non_const_fh_region; const auto border_edges = brute_force_border_3_of_region(face_index, region_index, cdt_2, fh_region); const auto region_vertices = std::invoke([&]() { std::set vertices; for(const auto fh_2d: fh_region) { for(int i = 0; i < 3; ++i) { vertices.insert(fh_2d->vertex(i)->info().vertex_handle_3d); } } return vertices; }); const auto region_border_vertices = std::invoke([&]() { std::set vertices; for(const auto& [c, i, j]: border_edges) { vertices.insert(c->vertex(i)); vertices.insert(c->vertex(j)); } return vertices; }); #if CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_regions()) { std::cerr << "region_border_vertices.size() = " << region_border_vertices.size() << "\n"; for(auto v : region_border_vertices) { std::cerr << std::format(" {}\n", IO::oformat(v, with_point)); } } #endif // CGAL_CDT_3_CAN_USE_CXX20_FORMAT for(auto v: region_border_vertices) { v->set_mark(Vertex_marker::REGION_BORDER); } const auto found_edge_opt = search_first_intersection(face_index, cdt_2, fh_region, border_edges); for(auto v: region_border_vertices) { v->clear_mark(Vertex_marker::REGION_BORDER); } [[maybe_unused]] auto try_flip_region_size_4 = [&] { if(region_border_vertices.size() == 4) { std::set vertices; std::set diagonal; for(auto fh : fh_region) { for(int i = 0; i < 3; ++i) { auto [it, new_vertex] = vertices.insert(fh->vertex(i)->info().vertex_handle_3d); if(!new_vertex) { diagonal.insert(*it); } } } std::set other_diagonal; std::set_difference(region_border_vertices.begin(), region_border_vertices.end(), diagonal.begin(), diagonal.end(), std::inserter(other_diagonal, other_diagonal.begin())); CGAL_assertion(diagonal.size() == 2); CGAL_assertion(other_diagonal.size() == 2); const auto diagonal_index = fh_region[0]->index(fh_region[1]); CGAL_assertion(diagonal_index >= 0 && diagonal_index < 3); const auto v0 = fh_region[0]->vertex(diagonal_index)->info().vertex_handle_3d; const auto v1 = fh_region[0]->vertex(cdt_2.ccw(diagonal_index))->info().vertex_handle_3d; const auto v2 = fh_region[0]->vertex(cdt_2.cw(diagonal_index))->info().vertex_handle_3d; const auto v3 = fh_region[1]->vertex(fh_region[1]->index(fh_region[0]))->info().vertex_handle_3d; if(tr.is_facet(v0, v1, v3) && tr.is_facet(v0, v3, v2)) { if(cdt_2.orientation(v0->point(), v1->point(), v3->point()) == CGAL::POSITIVE && cdt_2.orientation(v0->point(), v3->point(), v2->point()) == CGAL::POSITIVE) { std::cerr << "NOTE: the other diagonal is in the 3D triangulation: flip the edge\n"; non_const_cdt_2.flip(non_const_fh_region[0], diagonal_index); for(auto fh : fh_region) { for(int i = 0; i < 3; ++i) { const auto mirror_edge = cdt_2.mirror_edge({fh, i}); fh->set_constraint(i, mirror_edge.first->is_constrained(mirror_edge.second)); } int i, j, k; Cell_handle c; [[maybe_unused]] bool fh_is_3d_facet = tr.is_facet(fh->vertex(0)->info().vertex_handle_3d, fh->vertex(1)->info().vertex_handle_3d, fh->vertex(2)->info().vertex_handle_3d, c, i, j, k); CGAL_assertion(fh_is_3d_facet); set_facet_constrained({c, 6-i-j-k}, face_index, fh); fh->info().missing_subface = false; } return true; } else { std::cerr << "NOTE: the other diagonal is in the 3D triangulation BUT the edge is not flippable!\n"; std::cerr << " The region " << region_index << " of face #F" << face_index << " has four points:\n"; std::cerr << " v0: " << v0->point() << '\n'; std::cerr << " v1: " << v1->point() << '\n'; std::cerr << " v2: " << v2->point() << '\n'; std::cerr << " v3: " << v3->point() << '\n'; } } #if CGAL_CAN_USE_CXX20_FORMAT std::cerr << std::format ("NOTE: diagonal: {:.6} {:.6} {} in tr\n", IO::oformat(*diagonal.begin(), with_point), IO::oformat(*std::next(diagonal.begin()), with_point), this->is_edge(*diagonal.begin(), *std::next(diagonal.begin())) ? "IS" : "is NOT"); std::cerr << std::format( "NOTE: the other diagonal: {:.6} {:.6} {} in tr\n", IO::oformat(*other_diagonal.begin(), with_point), IO::oformat(*std::next(other_diagonal.begin()), with_point), this->is_edge(*other_diagonal.begin(), *std::next(other_diagonal.begin())) ? "IS" : "is NOT"); if(cdt_2.geom_traits().side_of_oriented_circle_2_object()( (*region_border_vertices.begin())->point(), (*std::next(region_border_vertices.begin()))->point(), (*std::next(region_border_vertices.begin(), 2))->point(), (*std::next(region_border_vertices.begin(), 3))->point()) == CGAL::ZERO) { std::cerr << std::format( "NOTE: In polygon #{}, region {}, the 4 vertices are co-circular in the 2D triangulation\n", face_index, region_index); } if(CGAL::coplanar( (*region_border_vertices.begin())->point(), (*std::next(region_border_vertices.begin()))->point(), (*std::next(region_border_vertices.begin(), 2))->point(), (*std::next(region_border_vertices.begin(), 3))->point())) { std::cerr << std::format("NOTE: In polygon #{}, region {}, the 4 vertices are coplanar\n", face_index, region_index); if(CGAL::coplanar_side_of_bounded_circle( (*region_border_vertices.begin())->point(), (*std::next(region_border_vertices.begin()))->point(), (*std::next(region_border_vertices.begin(), 2))->point(), (*std::next(region_border_vertices.begin(), 3))->point()) == CGAL::ON_BOUNDARY) { std::cerr << std::format( "NOTE: In polygon #{}, region {}, the 4 vertices are co-circular in the 3D triangulation\n", face_index, region_index); } } #endif // CGAL_CAN_USE_CXX20_FORMAT } return false; }; if(!found_edge_opt) { if(try_flip_region_size_4()) { return; } // { // Constrained_Delaunay_triangulation_3 new_tr; // for(const auto v : region_border_vertices) { // new_tr.insert(v->point()); // } // std::cerr << "new_tr.dimension() = " << new_tr.dimension() << '\n'; // std::ofstream out(std::string("dump_polygon_") + std::to_string(face_index) + "_tr.off"); // out.precision(17); // if(new_tr.dimension() == 2) { // write_facets(out, new_tr, new_tr.finite_facets()); // } // else { // write_facets(out, new_tr, std::views::filter(new_tr.finite_facets(), [&](auto f) { // return new_tr.is_infinite(f.first) || new_tr.is_infinite(f.first->neighbor(f.second)); // })); // } // } // { // dump_edge_link(std::string("dump_around_edge_") + std::to_string(face_index) + "_" + // std::to_string(region_index) + ".polylines.txt", border_edges[0]); // std::ofstream dump(std::string("dump_no_segment_found_") + std::to_string(face_index) + "_" + // std::to_string(region_index) + ".binary.cgal"); // CGAL::IO::save_binary_file(dump, *this); dump_region(face_index, region_index, cdt_2); // } throw Next_region{"No segment found", fh_region[0]}; } CGAL_assertion(found_edge_opt != std::nullopt); for(auto v : region_border_vertices) { v->set_mark(Vertex_marker::REGION_BORDER); } for(auto v : region_vertices) { if(v->is_marked(Vertex_marker::REGION_BORDER)) continue; v->set_mark(Vertex_marker::REGION_INSIDE); } Scope_exit guard{[&] { for(auto v : region_vertices) { v->clear_mark(Vertex_marker::REGION_BORDER); v->clear_mark(Vertex_marker::REGION_INSIDE); } }}; const auto [first_intersecting_edge, _] = *found_edge_opt; const auto [intersecting_edges, original_intersecting_cells, original_vertices_of_upper_cavity, original_vertices_of_lower_cavity, original_facets_of_upper_cavity, original_facets_of_lower_cavity] = construct_cavities(face_index, region_index, cdt_2, fh_region, region_border_vertices, region_vertices, first_intersecting_edge, border_edges); const std::set polygon_points = std::invoke([&](){ std::set polygon_points; for(auto vh : region_vertices) { polygon_points.insert(this->point(vh)); } return polygon_points; }); auto is_facet_of_polygon = [&](const auto& tr, Facet f) { const auto [c, facet_index] = f; for(int i = 0; i < 3; ++i) { const auto vh = c->vertex(T_3::vertex_triple_index(facet_index, i)); if(0 == polygon_points.count(tr.point(vh))) { return false; } } return true; }; #if CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_regions()) { std::cerr << std::format("Cavity has {} cells and {} edges, " "{} vertices in upper cavity and {} in lower, " "{} facets in upper cavity and {} in lower\n", original_intersecting_cells.size(), intersecting_edges.size(), original_vertices_of_upper_cavity.size(), original_vertices_of_lower_cavity.size(), original_facets_of_upper_cavity.size(), original_facets_of_lower_cavity.size()); if(original_intersecting_cells.size() > 3 || intersecting_edges.size() > 1) { std::cerr << "!! Interesting case !!\n"; // dump_region(face_index, region_index, cdt_2); // { // std::ofstream out(std::string("dump_intersecting_edges_") + std::to_string(face_index) + "_" + // std::to_string(region_index) + ".polylines.txt"); // out.precision(17); // for(auto edge: intersecting_edges) { // write_segment(out, edge); // } // } // dump_facets_of_cavity(face_index, region_index, "lower", original_facets_of_lower_cavity); // dump_facets_of_cavity(face_index, region_index, "upper", original_facets_of_upper_cavity); } } #endif // CGAL_CDT_3_CAN_USE_CXX20_FORMAT auto register_internal_constrained_facet = [this](Facet f) { this->register_facet_to_be_constrained(f); }; if(this->debug_copy_triangulation_into_hole()) { std::cerr << "# upper cavity\n"; } [[maybe_unused]] const auto [upper_cavity_triangulation, vertices_of_upper_cavity, map_upper_cavity_vertices_to_ambient_vertices, facets_of_upper_cavity, interior_constrained_faces_upper, cells_of_upper_cavity] = triangulate_cavity(original_intersecting_cells, original_facets_of_upper_cavity, original_vertices_of_upper_cavity); std::for_each(interior_constrained_faces_upper.begin(), interior_constrained_faces_upper.end(), register_internal_constrained_facet); if(this->debug_copy_triangulation_into_hole()) { std::cerr << "# lower cavity\n"; } [[maybe_unused]] const auto [lower_cavity_triangulation, vertices_of_lower_cavity, map_lower_cavity_vertices_to_ambient_vertices, facets_of_lower_cavity, interior_constrained_faces_lower, cells_of_lower_cavity] = triangulate_cavity(original_intersecting_cells, original_facets_of_lower_cavity, original_vertices_of_lower_cavity); std::for_each(interior_constrained_faces_lower.begin(), interior_constrained_faces_lower.end(), register_internal_constrained_facet); // the following transform_reduce is like `std::any_of` but without the fast-exit if(std::transform_reduce(fh_region.begin(), fh_region.end(), false, std::logical_or{}, [&](auto fh) { const auto v0 = fh->vertex(0)->info().vertex_handle_3d; const auto v1 = fh->vertex(1)->info().vertex_handle_3d; const auto v2 = fh->vertex(2)->info().vertex_handle_3d; auto is_fh_facet_of = [&](const auto& tr) -> std::optional { return this->vertex_triple_is_facet_of_other_triangulation(*this, v0, v1, v2, tr); }; const bool fail_upper = !is_fh_facet_of(upper_cavity_triangulation); const bool fail_lower = !is_fh_facet_of(lower_cavity_triangulation); if(fail_upper || fail_lower) { fh->info().is_in_region = 1; auto display_face = [&]() { std::stringstream s; s.precision(std::cerr.precision()); s << "(" << IO::oformat(v0, this->with_offset) << ", " << IO::oformat(v1, this->with_offset) << ", " << IO::oformat(v2, this->with_offset) << ") = ( " << tr.point(v0) << " " << tr.point(v1) << " " << tr.point(v2) << " )"; return s.str(); }; if(fail_upper) { std::cerr << "NOTE: Face " << display_face() << " is not a facet of the upper cavity\n"; } if(fail_lower) { std::cerr << "NOTE: Face " << display_face() << " is not a facet of the lower cavity\n"; } return true; } return false; })) { if(this->debug_missing_region()) { // debug_region_size_4(); dump_region(face_index, region_index, cdt_2); std::for_each(fh_region.begin(), fh_region.end(), [](auto fh) { fh->info().is_in_region = 3; }); // dump_3d_triangulation(face_index, region_index, "lower", lower_cavity_triangulation); // dump_3d_triangulation(face_index, region_index, "upper", upper_cavity_triangulation); auto dump_facets_of_cavity_border = [&](CDT_3_face_index face_index, int region_index, std::string type, const auto& cavity_triangulation) { std::ofstream out(std::string("dump_plane_facets_of_region_") + std::to_string(face_index) + "_" + std::to_string(region_index) + "_" + type + ".off"); std::ofstream other_out(std::string("dump_non_plane_facets_of_region_") + std::to_string(face_index) + "_" + std::to_string(region_index) + "_" + type + ".off"); out.precision(17); other_out.precision(17); std::vector border_faces; std::vector non_border_faces; visit_convex_hull_of_triangulation(cavity_triangulation, [&](Facet f) { if(is_facet_of_polygon(cavity_triangulation, f)) border_faces.push_back(f); else non_border_faces.push_back(f); }); CGAL_warning(!border_faces.empty()); write_facets(out, cavity_triangulation, border_faces); write_facets(other_out, cavity_triangulation, non_border_faces); }; dump_facets_of_cavity_border(face_index, region_index, "lower", lower_cavity_triangulation); dump_facets_of_cavity_border(face_index, region_index, "upper", upper_cavity_triangulation); } throw Next_region{"missing facet in polygon", fh_region[0]}; } insert_in_conflict_visitor.process_cells_in_conflict(cells_of_upper_cavity.begin(), cells_of_upper_cavity.end()); insert_in_conflict_visitor.process_cells_in_conflict(cells_of_lower_cavity.begin(), cells_of_lower_cavity.end()); if(this->debug_copy_triangulation_into_hole()) { std::cerr << "# glu the upper triangulation of the cavity\n"; if(cells_of_lower_cavity.size() > original_intersecting_cells.size() || cells_of_upper_cavity.size() > original_intersecting_cells.size()) { std::cerr << std::format("!! Cavity has grown and has now " "{} vertices in upper cavity and {} in lower, " "{} facets in upper cavity and {} in lower\n", vertices_of_upper_cavity.size(), vertices_of_lower_cavity.size(), facets_of_upper_cavity.size(), facets_of_lower_cavity.size()); } } typename T_3::Vertex_triple_Facet_map outer_map; auto add_to_outer_map = [this, &outer_map](typename T_3::Vertex_triple vt, Facet f, [[maybe_unused]] std::string_view extra = {}) { outer_map[vt] = f; #if CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_copy_triangulation_into_hole()) { CGAL_assertion(vt[0] != vt[1]); CGAL_assertion(vt[0] != vt[2]); CGAL_assertion(vt[1] != vt[2]); std::cerr << std::format("outer map: Adding {}triple ({:.6}, {:.6}, {:.6})\n", extra, IO::oformat(vt[0], with_point), IO::oformat(vt[1], with_point), IO::oformat(vt[2], with_point)); std::ofstream out("dump_upper_outer_map.off"); out.precision(17); write_facets(out, *this, std::ranges::views::values(outer_map)); out.close(); } #endif // CGAL_CDT_3_CAN_USE_CXX20_FORMAT }; auto fill_outer_map_of_cavity = [&](const auto&, const auto& facets) { for(auto f : facets) { typename T_3::Vertex_triple vt = this->make_vertex_triple(f); this->make_canonical_oriented_triple(vt); add_to_outer_map(vt, f); } }; fill_outer_map_of_cavity(upper_cavity_triangulation, facets_of_upper_cavity); auto add_pseudo_cells_to_outer_map = [&](const auto& tr, const auto& map_cavity_vertices_to_ambient_vertices, bool is_upper_cavity) { // @TODO: comment this piece of code // @TODO: this should not be a lambda std::vector> pseudo_cells; std::vector facets_of_polygon; for(auto f : tr.finite_facets()) { if(!is_facet_of_polygon(tr, f)) continue; const auto is_facet = facet_is_facet_of_cdt_2(tr, f, cdt_2); if(!is_facet) continue; // we might be in a sliver in the plane of the polygon const auto [fh_2d, reverse_orientation] = *is_facet; if(this->debug_regions()) facets_of_polygon.push_back(f); const auto vt_aux = this->make_vertex_triple(f); typename T_3::Vertex_triple vt{map_cavity_vertices_to_ambient_vertices[vt_aux[0]], map_cavity_vertices_to_ambient_vertices[vt_aux[1]], map_cavity_vertices_to_ambient_vertices[vt_aux[2]]}; this->make_canonical_oriented_triple(vt); if(reverse_orientation == is_upper_cavity) { std::swap(vt[1], vt[2]); } auto new_cell = this->tds().create_cell(); pseudo_cells.emplace_back(new_cell, fh_2d); new_cell->set_vertices(vt[0], vt[1], vt[2], this->infinite_vertex()); CGAL_assertion(static_cast(facet_is_facet_of_cdt_2(*this, {new_cell, 3}, cdt_2))); add_to_outer_map(vt, {new_cell, 3}, "extra "); } if(this->debug_regions()) { std::ofstream out(std::format("dump_{}_pseudo_cells_region_{}_{}.off", is_upper_cavity ? "upper" : "lower", face_index, region_index)); out.precision(17); write_facets(out, tr, facets_of_polygon); } return pseudo_cells; }; const auto pseudo_cells = add_pseudo_cells_to_outer_map(upper_cavity_triangulation, map_upper_cavity_vertices_to_ambient_vertices, true); { // #if CGAL_DEBUG_CDT_3 & 64 // std::ofstream out("dump_upper_outer_map.off"); // out.precision(17); // write_facets(out, *this, std::ranges::views::values(outer_map)); // out.close(); // #endif // CGAL_DEBUG_CDT_3 const auto upper_inner_map = tr.create_triangulation_inner_map( upper_cavity_triangulation, map_upper_cavity_vertices_to_ambient_vertices, false); #if CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_copy_triangulation_into_hole()) { std::cerr << "upper_inner_map:\n"; for(auto [vt, _] : upper_inner_map) { std::cerr << std::format(" {:.6}, {:.6}, {:.6})\n", IO::oformat(vt[0], with_point), IO::oformat(vt[1], with_point), IO::oformat(vt[2], with_point)); } } #endif // CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_copy_triangulation_into_hole()) { std::cerr << "# glu the lower triangulation of the cavity\n"; } this->copy_triangulation_into_hole(map_upper_cavity_vertices_to_ambient_vertices, std::move(outer_map), upper_inner_map, this->new_cells_output_iterator()); } #if CGAL_DEBUG_CDT_3 & 64 std::cerr << "# glu the lower triangulation of the cavity\n"; #endif // CGAL_DEBUG_CDT_3 outer_map.clear(); std::vector> new_constrained_facets; new_constrained_facets.reserve(pseudo_cells.size()); for(const auto& [c, fh_2d] : pseudo_cells) { const Facet f = this->mirror_facet({c, 3}); new_constrained_facets.emplace_back(f, fh_2d); CGAL_assertion(static_cast(facet_is_facet_of_cdt_2(*this, f, cdt_2))); auto vt = this->make_vertex_triple(f); this->make_canonical_oriented_triple(vt); add_to_outer_map(vt, f); this->tds().delete_cell(c); } fill_outer_map_of_cavity(lower_cavity_triangulation, facets_of_lower_cavity); { const auto lower_inner_map = tr.create_triangulation_inner_map( lower_cavity_triangulation, map_lower_cavity_vertices_to_ambient_vertices, false); #if CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_copy_triangulation_into_hole()) { std::cerr << "outer_map:\n"; for(auto [vt, _] : outer_map) { std::cerr << std::format(" {:.6}, {:.6}, {:.6})\n", IO::oformat(vt[0], with_point), IO::oformat(vt[1], with_point), IO::oformat(vt[2], with_point)); } std::ofstream out("dump_lower_outer_map.off"); out.precision(17); write_facets(out, *this, std::ranges::views::values(outer_map)); out.close(); } #endif // CGAL_CDT_3_CAN_USE_CXX20_FORMAT this->copy_triangulation_into_hole(map_lower_cavity_vertices_to_ambient_vertices, std::move(outer_map), lower_inner_map, this->new_cells_output_iterator()); } std::set cells_to_remove{cells_of_lower_cavity.begin(), cells_of_lower_cavity.end()}; cells_to_remove.insert(cells_of_upper_cavity.begin(), cells_of_upper_cavity.end()); for(auto c : cells_to_remove) { this->tds().delete_cell(c); } auto restore_markers = [&](Facet outside_facet) { const auto [outside_cell, outside_face_index] = outside_facet; const auto mirror_facet = this->mirror_facet(outside_facet); if(outside_cell->is_facet_constrained(outside_face_index)) { const auto poly_id = outside_cell->face_constraint_index(outside_face_index); const CDT_2& cdt_2 = face_cdt_2[poly_id]; const auto f2d = outside_cell->face_2(cdt_2, outside_face_index); set_facet_constrained(mirror_facet, poly_id, f2d); } }; std::for_each(facets_of_lower_cavity.begin(), facets_of_lower_cavity.end(), restore_markers); std::for_each(facets_of_upper_cavity.begin(), facets_of_upper_cavity.end(), restore_markers); for(const auto& [f, f2d] : new_constrained_facets) { set_facet_constrained(f, face_index, f2d); f2d->info().missing_subface = false; } CGAL_assume(!this->debug_validity() || this->is_valid(true)); }; struct Oriented_face_of_cdt_2 { CDT_2_face_handle fh; bool reversed_orientation = false; }; static auto vertex_of_cdt_2_functor(const CDT_2& cdt_2) { return [&, hint = CDT_2_face_handle{}](const auto& p) mutable { int i; typename CDT_2::Locate_type lt; const auto fh = cdt_2.locate(p, lt, i, hint); CGAL_assume(lt == CDT_2::VERTEX); hint = fh; return fh->vertex(i); }; } template static auto facet_is_facet_of_cdt_2(const Tr& tr, typename Tr::Facet f, const CDT_2& cdt_2) -> std::optional { const auto [c, facet_index] = f; const auto v0 = c->vertex(Tr::vertex_triple_index(facet_index, 0)); const auto v1 = c->vertex(Tr::vertex_triple_index(facet_index, 1)); const auto v2 = c->vertex(Tr::vertex_triple_index(facet_index, 2)); auto v = vertex_of_cdt_2_functor(cdt_2); const auto cdt_2_v0 = v(tr.point(v0)); const auto cdt_2_v1 = v(tr.point(v1)); const auto cdt_2_v2 = v(tr.point(v2)); CDT_2_face_handle fh; const bool is_face = cdt_2.is_face(cdt_2_v0, cdt_2_v1, cdt_2_v2, fh); if(is_face && fh->info().is_in_region != 0) { const int index_v0 = fh->index(cdt_2_v0); const bool reverse_orientation = (cdt_2_v2 == fh->vertex(T_3::ccw(index_v0))); return Oriented_face_of_cdt_2{fh, reverse_orientation}; } else return std::nullopt; } auto edge_of_cdt_2(const CDT_2& cdt_2, const Vertex_handle va, const Vertex_handle vb) const -> std::optional { auto v = vertex_of_cdt_2_functor(cdt_2); const auto cdt_2_v0 = v(this->point(va)); const auto cdt_2_v1 = v(this->point(vb)); CDT_2_face_handle fh; int edge_index; const bool is_edge = cdt_2.is_edge(cdt_2_v0, cdt_2_v1, fh, edge_index); if(is_edge) { typename CDT_2::Edge edge{fh, edge_index}; // if(fh->vertex(cdt_2.cw(edge_index)) != cdt_2_v0) { // edge = cdt_2.mirror_edge(edge); // } CGAL_assertion(edge.first->vertex(cdt_2.cw(edge.second)) == cdt_2_v0); CGAL_assertion(edge.first->vertex(cdt_2.ccw(edge.second)) == cdt_2_v1); return edge; } else return std::nullopt; } template static auto vertex_triple_is_facet_of_other_triangulation( const Tr1& tr, Vertex_handle1 v0, Vertex_handle1 v1, Vertex_handle1 v2, const Tr2& other_tr) -> std::optional { const auto p0 = tr.point(v0); const auto p1 = tr.point(v1); const auto p2 = tr.point(v2); auto v = [&, hint = typename Tr2::Cell_handle{}](const auto& p) mutable { int i, j; Locate_type lt; const auto c = other_tr.locate(p, lt, i, j, hint); if(lt != T_3::VERTEX) { std::cerr << std::format("vertex_triple_is_facet_of_other_triangulation: point {} lt = {}\n", IO::oformat(p), int(lt)); } CGAL_assume(lt == T_3::VERTEX); hint = c; return c->vertex(i); }; typename Tr2::Cell_handle c; int i, j, k; const bool ok = other_tr.is_facet(v(p0), v(p1), v(p2), c, i, j, k); if(ok) return {typename Tr2::Facet(c, 6 - i - j - k)}; else return {std::nullopt}; }; template auto triangulate_cavity(const Cell_range& orig_cells_of_cavity, const Facets_range& orig_facets_of_cavity_border, const Vertices_range& orig_vertices_of_cavity) const ///@TODO: not deterministic, without time stamps { using Vertex_map = typename T_3::Vertex_handle_unique_hash_map; struct { T_3 cavity_triangulation; std::set vertices; Vertex_map vertices_to_ambient_vertices; std::set facets_of_cavity_border_; std::vector interior_constrained_faces; std::set cell_of_cavity_; } result{ {}, {orig_vertices_of_cavity.begin(), orig_vertices_of_cavity.end()}, {}, {orig_facets_of_cavity_border.begin(), orig_facets_of_cavity_border.end()}, {}, {orig_cells_of_cavity.begin(), orig_cells_of_cavity.end()} }; auto& cavity_triangulation = result.cavity_triangulation; auto& map_cavity_vertices_to_ambient_vertices = result.vertices_to_ambient_vertices; auto& vertices_of_cavity = result.vertices; auto& facets_of_cavity_border = result.facets_of_cavity_border_; auto& cells_of_cavity = result.cell_of_cavity_; CGAL::Unique_hash_map map_ambient_vertices_to_cavity_vertices; auto insert_new_vertex = [&](Vertex_handle v, [[maybe_unused]] std::string_view extra = "") { const auto cavity_v = tr.is_infinite(v) ? cavity_triangulation.infinite_vertex() : cavity_triangulation.insert(this->point(v)); map_ambient_vertices_to_cavity_vertices[v] = cavity_v; map_cavity_vertices_to_ambient_vertices[cavity_v] = v; #if CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_regions()) { std::cerr << std::format("inserted {}cavity vertex {:.6} -> {:.6}\n", extra, IO::oformat(cavity_v, with_point_and_info), IO::oformat(v, with_point_and_info)); } #endif // CGAL_CDT_3_CAN_USE_CXX20_FORMAT return cavity_v; }; for(const auto v : vertices_of_cavity) { insert_new_vertex(v); } boost::container::small_vector missing_faces; do { missing_faces.clear(); boost::container::small_vector internal_facets; for(auto f : facets_of_cavity_border) { if(cells_of_cavity.count(f.first) > 0) { // internal facet, due to cavity growing internal_facets.push_back(f); continue; } const auto [v0, v1, v2] = this->make_vertex_triple(f); Cell_handle c; int i, j, k; if(!cavity_triangulation.is_facet(map_ambient_vertices_to_cavity_vertices[v0], map_ambient_vertices_to_cavity_vertices[v1], map_ambient_vertices_to_cavity_vertices[v2], c, i, j, k)) { missing_faces.push_back(f); } } for(auto f : internal_facets) { facets_of_cavity_border.erase(f); } for(auto [cell, facet_index] : missing_faces) { facets_of_cavity_border.erase({cell, facet_index}); if(cell->is_facet_constrained(facet_index)) { result.interior_constrained_faces.emplace_back(cell, facet_index); } auto is_new_cell = cells_of_cavity.insert(cell).second; if(!is_new_cell) continue; const auto v3 = cell->vertex(facet_index); auto v3_is_new_vertex = vertices_of_cavity.insert(v3).second; if(v3_is_new_vertex) { insert_new_vertex(v3, "extra "); } for(int i = 0; i < 3; ++i) { Facet other_f{cell, this->vertex_triple_index(facet_index, i)}; Facet mirror_f = this->mirror_facet(other_f); if(cells_of_cavity.count(mirror_f.first) == 0) { facets_of_cavity_border.insert(mirror_f); } } } } while(!missing_faces.empty()); CGAL_assertion(std::all_of(facets_of_cavity_border.begin(), facets_of_cavity_border.end(), [&](const auto& f) { const auto [v0, v1, v2] = this->make_vertex_triple(f); Cell_handle c; int i, j, k; return cavity_triangulation.is_facet(map_ambient_vertices_to_cavity_vertices[v0], map_ambient_vertices_to_cavity_vertices[v1], map_ambient_vertices_to_cavity_vertices[v2], c, i, j, k); })); return result; } std::optional> return_encroached_constrained_edge([[maybe_unused]] CDT_3_face_index face_index, const CDT_2& cdt_2, Point_3 steiner_pt) const { for(auto [other_fh, index] : cdt_2.finite_edges()) { if(!other_fh->is_constrained(index)) continue; const auto va = other_fh->vertex(cdt_2.cw(index)); const auto vb = other_fh->vertex(cdt_2.ccw(index)); const auto a = cdt_2.point(va); const auto b = cdt_2.point(vb); // std::cerr << std::format("Test candidate Steiner point {} with edge ( {} {} ), result is: {}", IO::oformat(steiner_pt), // IO::oformat(a), IO::oformat(b), IO::oformat(CGAL::angle(a, steiner_pt, b))) // << '\n'; if(CGAL::angle(a, steiner_pt, b) != CGAL::ACUTE) { const auto va_3d = va->info().vertex_handle_3d; const auto vb_3d = vb->info().vertex_handle_3d; return std::make_pair(va_3d, vb_3d); } } return std::nullopt; } std::optional> try_to_insert_circumcenter_in_face_or_return_encroached_edge(CDT_3_face_index face_index, CDT_2& non_const_cdt_2, CDT_2_face_handle fh_2d) { const auto& cdt_2 = non_const_cdt_2; auto steiner_pt = CGAL::centroid(cdt_2.triangle(fh_2d)); #if CGAL_DEBUG_CDT_3 & 64 && CGAL_CAN_USE_CXX20_FORMAT std::cerr << std::format("Trying to insert Steiner (centroid) point {} in non-coplanar face {}.\n", IO::oformat(steiner_pt), IO::oformat(cdt_2.triangle(fh_2d))); #endif // CGAL_DEBUG_CDT_3 auto encroached_edge_opt = return_encroached_constrained_edge(face_index, cdt_2, steiner_pt); if(encroached_edge_opt) { return encroached_edge_opt; } if constexpr (cdt_3_can_use_cxx20_format()) if(this->debug_Steiner_points()) { std::cerr << std::format("Inserting Steiner (centroid) point {} in non-coplanar face {}: {}.\n", IO::oformat(steiner_pt), face_index, IO::oformat(cdt_2.triangle(fh_2d))); } Locate_type lt; int li, lj; const auto ch = this->locate(steiner_pt, lt, li, lj); boost::container::small_vector cells; boost::container::small_vector facets; auto cleanup = [&cells, &facets] { for(Cell_handle ch : cells) { ch->tds_data().clear(); } for(Facet& f : facets) { f.first->neighbor(f.second)->tds_data().clear(); } }; switch(tr.dimension()) { case 3: { typename T_3::Conflict_tester_3 tester(steiner_pt, this); this->find_conflicts(ch, tester, make_triple( std::back_inserter(facets), std::back_inserter(cells), Emptyset_iterator())); break; } // dim 3 case 2: { typename T_3::Conflict_tester_2 tester(steiner_pt, this); this->find_conflicts(ch, tester, make_triple( std::back_inserter(facets), std::back_inserter(cells), Emptyset_iterator())); break; } // dim 2 default: CGAL_error(); } std::set> visited_edges; for(auto c : cells) { for(int i = 0; i < 4; ++i) { for(int j = i + 1; j < 4; ++j) { auto pair = make_sorted_pair(c->vertex(i), c->vertex(j)); auto is_a_new_edge = visited_edges.insert(pair).second; if(!is_a_new_edge) continue; auto [va, vb] = pair; Constraint_id c_id = this->constraint_around(va, vb); if(c_id != Constraint_id{}) { if(CGAL::angle(this->point(va), steiner_pt, this->point(vb)) != CGAL::ACUTE) { cleanup(); return std::make_pair(va, vb); } } } } } cleanup(); // assert(is_valid(true)); // this->study_bug = true; const auto v = this->insert_in_cdt_3(steiner_pt, lt, ch, li, lj, insert_in_conflict_visitor);// TODO: use "insert in hole" // this->study_bug = false; // assert(is_valid(true)); if constexpr (cdt_3_can_use_cxx20_format()) if(this->debug_Steiner_points()) { std::cerr << " -> " << IO::oformat(v, with_offset) << '\n'; } v->set_Steiner_vertex_in_face(face_index); [[maybe_unused]] typename CDT_2::Locate_type lt_2; int i; auto fh = cdt_2.locate(steiner_pt, lt_2, i, fh_2d); CGAL_assertion(!fh->info().is_outside_the_face); CGAL_USE(fh); const auto v_2d = non_const_cdt_2.insert(steiner_pt, fh_2d); v_2d->info().vertex_handle_3d = v; auto f_circ = cdt_2.incident_faces(v_2d); const auto end = f_circ; do { f_circ->info().is_outside_the_face = false; } while(++f_circ != end); search_for_missing_subfaces(face_index); return std::nullopt; } void insert_mid_point_in_constrained_edge(Vertex_handle va_3d, Vertex_handle vb_3d) { const auto a = this->point(va_3d); const auto b = this->point(vb_3d); const auto mid = CGAL::midpoint(a, b); if constexpr (cdt_3_can_use_cxx20_format()) if(this->debug_Steiner_points()) { std::cerr << std::format("Inserting Steiner (midpoint) point {} of constrained edge ({:.6} , {:.6})\n", IO::oformat(mid), IO::oformat(va_3d, with_point_and_info), IO::oformat(vb_3d, with_point_and_info)); } auto&& contexts = this->constraint_hierarchy.contexts_range(va_3d, vb_3d); #if CGAL_DEBUG_CDT_3 & 64 && CGAL_CAN_USE_CXX20_FORMAT if(std::next(contexts.begin()) != contexts.end()) { std::cerr << "ERROR: Edge is constrained by more than one constraint\n"; for(const auto& c : contexts) { std::cerr << std::format(" - {} with {} vertices\n", IO::oformat(c.id().vl_ptr()), c.number_of_vertices()); for(auto vh_it = c.vertices_begin(), end = c.vertices_end(), current = c.current(); vh_it != end; ++vh_it) { std::cerr << std::format(" {} {}\n", (vh_it == current) ? '>' : '-', IO::oformat(*vh_it, with_point_and_info)); } } } #endif // CGAL_DEBUG_CDT_3 & 64 CGAL_assertion(std::next(contexts.begin()) == contexts.end()); const auto& context = *contexts.begin(); const auto constraint_id = context.id(); CGAL_assertion(constraint_id != Constraint_id{}); // this->study_bug = true; Locate_type mid_lt; int mid_li, min_lj; Cell_handle mid_c = tr.locate(mid, mid_lt, mid_li, min_lj, va_3d->cell()); CGAL_assertion(mid_lt != Locate_type::VERTEX); [[maybe_unused]] auto v = this->insert_Steiner_point_on_subconstraint(mid, mid_c, {va_3d, vb_3d}, constraint_id, insert_in_conflict_visitor); if constexpr (cdt_3_can_use_cxx20_format()) if(this->debug_Steiner_points()) { std::cerr << " -> " << IO::oformat(v, with_offset) << '\n'; } // this->study_bug = false; // assert(is_valid(true)); } bool restore_face(CDT_3_face_index face_index) { CDT_2& non_const_cdt_2 = face_cdt_2[face_index]; const CDT_2& cdt_2 = non_const_cdt_2; #if CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_copy_triangulation_into_hole()) { std::cerr << std::format("restore_face({}): CDT_2 has {} vertices\n", face_index, cdt_2.number_of_vertices()); } #endif // CGAL_CDT_3_CAN_USE_CXX20_FORMAT for(const auto& edge : cdt_2.finite_edges()) { const auto fh = edge.first; const auto i = edge.second; const auto va_3d = fh->vertex(cdt_2.cw(i))->info().vertex_handle_3d; const auto vb_3d = fh->vertex(cdt_2.ccw(i))->info().vertex_handle_3d; const bool is_3d = this->is_edge(va_3d, vb_3d); #if CGAL_CDT_3_CAN_USE_CXX20_FORMAT if(this->debug_copy_triangulation_into_hole()) { std::cerr << std::format("Edge is 3D: {:6} ({} , {})\n", is_3d, IO::oformat(va_3d, with_point_and_info), IO::oformat(vb_3d, with_point_and_info)); } #endif // CGAL_CDT_3_CAN_USE_CXX20_FORMAT CGAL_assertion(is_3d || !cdt_2.is_constrained(edge)); fh->info().is_edge_also_in_3d_triangulation[unsigned(i)] = is_3d; const auto reverse_edge = cdt_2.mirror_edge(edge); reverse_edge.first->info().is_edge_also_in_3d_triangulation[unsigned(reverse_edge.second)] = is_3d; } std::set processed_faces; auto& region_index = faces_region_numbers[face_index]; for(const CDT_2_face_handle fh : cdt_2.finite_face_handles()) { if(fh->info().is_outside_the_face) continue; if(false == fh->info().missing_subface) { continue; } Cell_handle c; int i, j, k; if(tr.is_facet(fh->vertex(0)->info().vertex_handle_3d, fh->vertex(1)->info().vertex_handle_3d, fh->vertex(2)->info().vertex_handle_3d, c, i, j, k)) { const int facet_index = 6 - i - j - k; set_facet_constrained({c, facet_index}, face_index, fh); fh->info().missing_subface = false; continue; } if(processed_faces.count(fh)> 0) continue; auto fh_region = region(cdt_2, fh); processed_faces.insert(fh_region.begin(), fh_region.end()); auto handle_error_with_region = [&](const char* what, CDT_2_face_handle fh_2d) { std::cerr << "NOTE: " << what << " in sub-region " << (region_index - 1) << " of face #F" << face_index << '\n'; #if CGAL_DEBUG_CDT_3 & 64 && CGAL_CAN_USE_CXX20_FORMAT std::cerr << " constrained edges are:\n"; for(auto [c, index]: cdt_2.constrained_edges()) { const auto va = c->vertex(cdt_2.cw(index)); const auto vb = c->vertex(cdt_2.ccw(index)); const auto va_3d = va->info().vertex_handle_3d; const auto vb_3d = vb->info().vertex_handle_3d; std::cerr << std::format(" ({:.6} , {:.6})\n", IO::oformat(va_3d, with_point_and_info), IO::oformat(vb_3d, with_point_and_info)); } #endif // CGAL_DEBUG_CDT_3 const auto encroach_edge_opt = try_to_insert_circumcenter_in_face_or_return_encroached_edge(face_index, non_const_cdt_2, fh_2d); if(encroach_edge_opt) { const auto [va_3d, vb_3d] = *encroach_edge_opt; insert_mid_point_in_constrained_edge(va_3d, vb_3d); } }; try { restore_subface_region(face_index, region_index++, non_const_cdt_2, fh_region); } catch(Next_region& e) { handle_error_with_region(e.what(), e.fh_2d); return false; } // catch(PLC_error& e) { // handle_error_with_region(e.what(), fh_region[0]); // return false; // } } return true; } public: bool is_valid(bool verbose = false, int level = 0) const { if(!this->tds().is_valid(verbose, level)) { if(verbose) std::cerr << "invalid data structure" << std::endl; CGAL_assertion(false); return false; } if(this->infinite_vertex() == Vertex_handle()) { if(verbose) std::cerr << "no infinite vertex" << std::endl; CGAL_assertion(false); return false; } bool result = true; switch(this->dimension()) { case 3: { for(auto it = this->finite_cells_begin(), end = this->finite_cells_end(); it != end; ++it) { result = result && this->is_valid_finite(it, verbose, level); for(int i = 0; i < 4; i++) { const auto n = it->neighbor(i); const auto n_index = n->index(it); if(!this->is_infinite(n->vertex(n_index))) { if(!it->is_facet_constrained(i) && this->side_of_sphere(it, n->vertex(n_index)->point()) == ON_BOUNDED_SIDE) { if(verbose) { const auto v = tr.vertices(it); std::cerr << "non-empty sphere at non-constrained facet (" << IO::oformat(Cell_handle(it)) << ", " << i << ") the cell is:\n " << IO::oformat(v[0], with_point) << "\n " << IO::oformat(v[1], with_point) << "\n " << IO::oformat(v[2], with_point) << "\n " << IO::oformat(v[3], with_point) << "\ncontains:\n " << IO::oformat(n->vertex(n_index), with_point_and_info) << '\n'; using EK = CGAL::Exact_predicates_exact_constructions_kernel; const auto to_exact = CGAL::Cartesian_converter(); const auto from_exact = CGAL::Cartesian_converter(); const auto exact_circ = CGAL::circumcenter(to_exact(tr.point(v[0])), to_exact(tr.point(v[1])), to_exact(tr.point(v[2])), to_exact(tr.point(v[3]))); const auto exact_sq_circumradius = CGAL::squared_distance(to_exact(tr.point(v[0])), exact_circ); const auto exact_sq_distance = CGAL::squared_distance(exact_circ, to_exact(tr.point(n->vertex(n_index)))); std::cerr << "exact squared circumradius: " << exact_sq_circumradius << '\n'; std::cerr << "exact squared distance: " << exact_sq_distance << '\n'; std::cerr << "ratio (non-squared): " << CGAL::sqrt(CGAL::to_double(from_exact(exact_sq_distance / exact_sq_circumradius))) << '\n'; } result = false; } } } } break; } case 2: { for(auto it = this->finite_facets_begin(), end = this->finite_facets_end(); it != end; ++it) { const auto c = it->first; result = result && this->is_valid_finite(c, verbose, level); for(int i = 0; i < 3; i++) { const auto n = c->neighbor(i); const auto n_index = n->index(c); if(!this->is_infinite(n->vertex(n_index))) { if(this->side_of_circle(c, 3, n->vertex(n_index)->point()) == ON_BOUNDED_SIDE) { if(verbose) std::cerr << "non-empty circle " << std::endl; result = false; } } } } break; } case 1: { for(auto it = this->finite_edges_begin(), end = this->finite_edges_end(); it != end; ++it) { result = result && this->is_valid_finite((*it).first, verbose, level); } break; } } if(this->use_finite_edges_map()) { const auto number_of_elements_in_finite_edges_map = std::accumulate(this->all_finite_edges.begin(), this->all_finite_edges.end(), size_type(0), [&](size_type res, const auto& hash_map) { return res + hash_map.size(); }); bool test = number_of_elements_in_finite_edges_map == this->number_of_finite_edges(); result = result && test; if(!test && verbose) { std::cerr << "all_finite_edges.size() = " << number_of_elements_in_finite_edges_map << " != number_of_finite_edges() = " << this->number_of_finite_edges() << std::endl; } for(auto v1: this->finite_vertex_handles()) { for(auto v2: this->all_finite_edges[v1->time_stamp()]) { test = this->is_edge(v1, v2); result = result && test; if(!test && verbose) { std::cerr << "edge (" << IO::oformat(v1, with_point_and_info) << ", " << IO::oformat(v2, with_point_and_info) << ") is not an edge" << std::endl; } } } for(auto e : this->finite_edges()) { auto [v1, v2] = make_sorted_pair(this->vertices(e)); auto v1_index = v1->time_stamp(); test = this->all_finite_edges[v1_index].find(v2)!= this->all_finite_edges[v1_index].end(); result = result && test; if(!test && verbose) { std::cerr << "finite edge (" << IO::oformat(v1, with_point_and_info) << ", " << IO::oformat(v2, with_point_and_info) << ") is not in the set all_finite_edges" << std::endl; } } } if(result && verbose) std::cerr << "valid constrained Delaunay triangulation" << std::endl; return result; } void recheck_constrained_Delaunay() { for(int i = 0, end = face_constraint_misses_subfaces.size(); i < end; ++i) { search_for_missing_subfaces(i); } } void restore_constrained_Delaunay() { this->is_Delaunay = false; faces_region_numbers.resize(face_constraint_misses_subfaces.size()); for(int i = 0, end = face_constraint_misses_subfaces.size(); i < end; ++i) { CDT_2& cdt_2 = face_cdt_2[i]; fill_cdt_2(cdt_2, i); search_for_missing_subfaces(i); } cdt_2_are_initialized = true; const auto npos = face_constraint_misses_subfaces.npos; auto i = face_constraint_misses_subfaces.find_first(); bool the_process_made_progress = false; while(i != npos) { try { if(restore_face(i)) { face_constraint_misses_subfaces.reset(i); } else { std::cerr << "restore_face(" << i << ") incomplete, back to conforming...\n"; Conforming_Dt::restore_Delaunay(insert_in_conflict_visitor); } the_process_made_progress = true; } catch(PLC_error& e) { std::cerr << std::string("ERROR: PLC error with face #F") << std::to_string(e.face_index) + "\n"; i = face_constraint_misses_subfaces.find_next(i); if(i == npos) { std::cerr << "ERROR: No more missing face to restore after a PLC error\n"; dump_region(e.face_index, e.region_index); throw; } std::cerr << "Next face is face #F " << i << '\n'; continue; } i = face_constraint_misses_subfaces.find_next(i); // If we have made progress, we start again from the beginning. // Otherwise, either we are done, or there was a full loop with // only PLC errors. if(i == npos && true == the_process_made_progress) { i = face_constraint_misses_subfaces.find_first(); the_process_made_progress = false; } } } static void write_region_to_OFF(std::ostream& out, const CDT_2& cdt_2) { out.precision(17); auto color_fn = [](CDT_2_face_handle fh_2d) -> CGAL::IO::Color { if(fh_2d->info().is_outside_the_face) return CGAL::IO::gray(); if(fh_2d->info().is_in_region) { if(fh_2d->info().is_in_region == 1) return CGAL::IO::violet(); else return CGAL::IO::red(); } return CGAL::IO::green(); }; auto color_pmap = boost::make_function_property_map(color_fn); CGAL::IO::write_OFF(out, cdt_2, CGAL::parameters::face_color_map(color_pmap)); } template void write_region(std::ostream& out, const Region& region) { for(const auto fh_2d : region) { write_2d_triangle(out, fh_2d); } } void write_3d_triangulation_to_OFF(std::ostream& out, const Constrained_Delaunay_triangulation_3& tr) { write_facets(out, tr, tr.finite_facets()); } void dump_3d_triangulation(CDT_3_face_index face_index, int region_index, std::string type, const Constrained_Delaunay_triangulation_3& tr) { std::ofstream dump(std::string("dump_") + type + "_cavity_" + std::to_string(face_index) + "_" + std::to_string(region_index) + ".off"); dump.precision(17); write_3d_triangulation_to_OFF(dump, tr); } void dump_triangulation() const { std::ofstream dump("dump.binary.cgal", std::ios::binary); CGAL::IO::save_binary_file(dump, *this); } void dump_region(CDT_3_face_index face_index, int region_index, const CDT_2& cdt_2) { std::ofstream dump_region(std::string("dump_region_") + std::to_string(face_index) + "_" + std::to_string(region_index) + ".off"); dump_region.precision(17); write_region_to_OFF(dump_region, cdt_2); } void dump_region(CDT_3_face_index face_index, int region_index) { const auto& cdt_2 = face_cdt_2[face_index]; dump_region(face_index, region_index, cdt_2); } void write_triangle(std::ostream &out, Vertex_handle v0, Vertex_handle v1, Vertex_handle v2) { out.precision(17); out << "4" << " " << tr.point(v0) << " " << tr.point(v1) << " " << tr.point(v2) << " " << tr.point(v0) << '\n'; } static void write_segment(std::ostream &out, Point_3 p0, Point_3 p1) { out.precision(17); out << "2" << " " << p0 << " " << p1 << '\n'; } static void write_segment(std::ostream &out, Segment_3 seg) { write_segment(out, seg.source(), seg.target()); } void write_segment(std::ostream& out, Vertex_handle v0, Vertex_handle v1) { write_segment(out, tr.point(v0), tr.point(v1)); } void write_segment(std::ostream& out, Edge edge) { const auto [c, i, j] = edge; write_segment(out, c->vertex(i), c->vertex(j)); } void dump_edge_link(std::string filename, Edge edge) { std::ofstream out(filename); out.precision(17); const auto [c, i, j] = edge; const Vertex_handle va = c->vertex(i); const Vertex_handle vb = c->vertex(j); auto cell_circ = this->incident_cells(edge), end = cell_circ; CGAL_assertion(cell_circ != nullptr); do { if(this->is_infinite(cell_circ)) { continue; } const auto index_va = cell_circ->index(va); const auto index_vb = cell_circ->index(vb); const auto index_vc = this->next_around_edge(index_va, index_vb); const auto index_vd = this->next_around_edge(index_vb, index_va); write_segment(out, cell_circ->vertex(index_vc), cell_circ->vertex(index_vd)); } while(++cell_circ != end); } template void dump_segment(std::string filename, Args&& ...args) { std::ofstream out(filename); out.precision(17); write_segment(out, std::forward(args)...); } template static auto export_facets_to_surface_mesh(const Tr& tr, Facets&& facets_range) { return CGAL::export_facets_to_surface_mesh(tr, std::forward(facets_range)); } template static void write_facets(std::ostream& out, const Tr& tr, Facets&& facets_range) { return CGAL::write_facets(out, tr, std::forward(facets_range)); } template void dump_facets_of_cavity(CDT_3_face_index face_index, int region_index, std::string type, const Facets_range& facets_range) { std::ofstream out(std::string("dump_facets_of_region_") + std::to_string(face_index) + "_" + std::to_string(region_index) + "_" + type + ".off"); out.precision(17); write_facets(out, *this, facets_range); } void write_2d_triangle(std::ostream &out, const CDT_2_face_handle fh) { const auto v0 = fh->vertex(0)->info().vertex_handle_3d; const auto v1 = fh->vertex(1)->info().vertex_handle_3d; const auto v2 = fh->vertex(2)->info().vertex_handle_3d; write_triangle(out, v0, v1, v2); } bool write_missing_subfaces_file(std::ostream& out) { const auto npos = face_constraint_misses_subfaces.npos; auto i = face_constraint_misses_subfaces.find_first(); bool has_missing_subfaces = i != npos; while(i != npos) { const CDT_2& cdt = face_cdt_2[i]; for(const auto fh: cdt.finite_face_handles()) { if (false == fh->info().is_outside_the_face && true == fh->info().missing_subface) { write_2d_triangle(out, fh); } } i = face_constraint_misses_subfaces.find_next(i); } return has_missing_subfaces; } /// @{ /// remove functions cannot be called void remove(Vertex_handle) = delete; void remove_cluster() = delete; /// @} protected: T_3 &tr = *this; Conforming_Dt &conforming_dt = *this; Insert_in_conflict_visitor insert_in_conflict_visitor = {*this}; std::vector face_cdt_2; bool cdt_2_are_initialized = false; struct Face_edge { Constraint_id constraint_id; bool is_reverse = false; }; std::vector>> face_borders; std::multimap constraint_to_faces; boost::dynamic_bitset<> face_constraint_misses_subfaces; std::vector faces_region_numbers; }; } // end CGAL #endif // not DOXYGEN_RUNNING #endif // CGAL_CONSTRAINED_DELAUNAY_TRIANGULATION_3_H