mirror of https://github.com/CGAL/cgal
932 lines
35 KiB
C++
932 lines
35 KiB
C++
// Copyright (c) 2019 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+
|
|
//
|
|
// Author(s) : Laurent Rineau
|
|
|
|
#ifndef CGAL_CONFORMING_DELAUNAY_TRIANGULATION_3_H
|
|
#define CGAL_CONFORMING_DELAUNAY_TRIANGULATION_3_H
|
|
|
|
#include <CGAL/Triangulation_3/internal/CDT_3_config.h>
|
|
|
|
#include <CGAL/license/Triangulation_3.h>
|
|
|
|
#include <CGAL/Base_with_time_stamp.h>
|
|
#include <CGAL/Triangulation_2/internal/Polyline_constraint_hierarchy_2.h>
|
|
#include <CGAL/Triangulation_segment_traverser_3.h>
|
|
|
|
#include <CGAL/Mesh_3/io_signature.h>
|
|
#include <CGAL/IO/File_binary_mesh_3.h>
|
|
|
|
#include <boost/container/flat_set.hpp>
|
|
#include <boost/container/small_vector.hpp> /// @TODO Requires Boost 1.66
|
|
|
|
#include <fstream>
|
|
#include <bitset>
|
|
|
|
namespace CGAL {
|
|
|
|
enum class CDT_3_vertex_type { FREE, CORNER, STEINER_ON_EDGE, STEINER_IN_FACE };
|
|
|
|
enum class CDT_3_vertex_marker {
|
|
CLEAR = 0,
|
|
REGION_BORDER,
|
|
REGION_INSIDE,
|
|
CAVITY,
|
|
CAVITY_ABOVE,
|
|
CAVITY_BELOW,
|
|
nb_of_markers
|
|
};
|
|
|
|
using CDT_3_face_index = int; // must be signed
|
|
|
|
template <typename Gt, typename Vb = Triangulation_vertex_base_3<Gt> >
|
|
class Conforming_Delaunay_triangulation_vertex_base_3 : public Base_with_time_stamp<Vb> {
|
|
CDT_3_vertex_type m_vertex_type = CDT_3_vertex_type::FREE;
|
|
std::bitset<static_cast<int>(CDT_3_vertex_marker::nb_of_markers)> mark;
|
|
union U {
|
|
struct On_edge {
|
|
int nb_of_incident_constraints = 0;
|
|
void* c_id = nullptr;
|
|
} on_edge;
|
|
struct On_face{
|
|
CDT_3_face_index face_index = 0;
|
|
} on_face;
|
|
} u;
|
|
public:
|
|
// To get correct vertex type in TDS
|
|
template < class TDS3 >
|
|
struct Rebind_TDS {
|
|
typedef typename Vb::template Rebind_TDS<TDS3>::Other Vb3;
|
|
typedef Conforming_Delaunay_triangulation_vertex_base_3 <Gt, Vb3> Other;
|
|
};
|
|
|
|
using Base = Base_with_time_stamp<Vb>;
|
|
using Base::Base;
|
|
|
|
Conforming_Delaunay_triangulation_vertex_base_3()
|
|
: Base{}
|
|
, u{typename U::On_edge()}
|
|
{}
|
|
|
|
void set_on_constraint(auto constraint_id) {
|
|
++u.on_edge.nb_of_incident_constraints;
|
|
u.on_edge.c_id = constraint_id.vl_ptr();
|
|
}
|
|
|
|
int number_of_incident_constraints() const {
|
|
CGAL_assertion(u.on_edge.nb_of_incident_constraints >= 0);
|
|
return u.on_edge.nb_of_incident_constraints;
|
|
}
|
|
|
|
void set_mark(CDT_3_vertex_marker marker) {
|
|
mark.set(static_cast<unsigned int>(marker));
|
|
}
|
|
|
|
void clear_marks() {
|
|
mark.reset();
|
|
}
|
|
|
|
void clear_mark(CDT_3_vertex_marker marker) {
|
|
mark.reset(static_cast<unsigned int>(marker));
|
|
}
|
|
|
|
bool is_marked(CDT_3_vertex_marker marker) const {
|
|
return mark.test(static_cast<unsigned int>(marker));
|
|
}
|
|
|
|
bool is_marked() const {
|
|
return mark.any();
|
|
}
|
|
|
|
template<typename Triangulation>
|
|
auto constraint_id(const Triangulation&) const {
|
|
CGAL_assertion(m_vertex_type != CDT_3_vertex_type::STEINER_IN_FACE);
|
|
using C_id = typename Triangulation::Constraint_id;
|
|
using Vertex_list_ptr = decltype(std::declval<C_id>().vl_ptr());
|
|
return C_id(static_cast<Vertex_list_ptr>(u.on_edge.c_id));
|
|
}
|
|
|
|
void set_Steiner_vertex_in_face(CDT_3_face_index face_index) {
|
|
m_vertex_type = CDT_3_vertex_type::STEINER_IN_FACE;
|
|
u.on_face = typename U::On_face{face_index};
|
|
}
|
|
|
|
CDT_3_face_index face_index() const {
|
|
CGAL_assertion(m_vertex_type == CDT_3_vertex_type::STEINER_IN_FACE);
|
|
return u.on_face.face_index;
|
|
}
|
|
|
|
CDT_3_vertex_type vertex_type() const { return m_vertex_type; }
|
|
void set_vertex_type(CDT_3_vertex_type type) { m_vertex_type = type; }
|
|
bool is_Steiner_vertex_on_edge() const { return m_vertex_type == CDT_3_vertex_type::STEINER_ON_EDGE; }
|
|
bool is_Steiner_vertex_in_face() const { return m_vertex_type == CDT_3_vertex_type::STEINER_IN_FACE; }
|
|
|
|
static std::string io_signature() {
|
|
return Get_io_signature<Vb>()();
|
|
}
|
|
};
|
|
|
|
template <typename T_3>
|
|
class Conforming_Delaunay_triangulation_3 : public T_3 {
|
|
public:
|
|
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 Point = typename T_3::Point;
|
|
using Line = typename T_3::Geom_traits::Line_3;
|
|
using Locate_type = typename T_3::Locate_type;
|
|
|
|
inline static With_offset_tag with_offset{};
|
|
inline static With_point_tag with_point{};
|
|
inline static With_point_and_info_tag with_point_and_info{};
|
|
|
|
protected:
|
|
struct Compare_vertex_handle {
|
|
const T_3* tr;
|
|
Compare_vertex_handle(const T_3* tr) : tr(tr) {}
|
|
bool operator()(const Vertex_handle va, const Vertex_handle vb) const {
|
|
return tr->compare_xyz(tr->point(va), tr->point(vb)) == SMALLER;
|
|
}
|
|
};
|
|
|
|
using Constraint_hierarchy =
|
|
Polyline_constraint_hierarchy_2<Vertex_handle, Compare_vertex_handle, const Point&>;
|
|
public:
|
|
using Constraint_id = typename Constraint_hierarchy::Constraint_id;
|
|
|
|
protected:
|
|
using Subconstraint = typename Constraint_hierarchy::Subconstraint;
|
|
|
|
auto display_vert(Vertex_handle v) const{
|
|
std::stringstream os;
|
|
os.precision(17);
|
|
os << IO::oformat(v, with_point);
|
|
return os.str();
|
|
}
|
|
|
|
auto display_subcstr(Subconstraint subconstraint) const {
|
|
auto [va, vb] = subconstraint;
|
|
std::stringstream os;
|
|
os << "(" << IO::oformat(va, with_offset) << ", " << IO::oformat(vb, with_offset) << ")"
|
|
<< ": [ " << display_vert(va) << " - " << display_vert(vb) << " ]";
|
|
return os.str();
|
|
}
|
|
|
|
class Insert_in_conflict_visitor
|
|
{
|
|
Conforming_Delaunay_triangulation_3<T_3>& self;
|
|
public:
|
|
Insert_in_conflict_visitor(Conforming_Delaunay_triangulation_3& self) : self(self) {}
|
|
|
|
template <class InputIterator>
|
|
void process_cells_in_conflict(InputIterator cell_it, InputIterator end) {
|
|
for( ; cell_it != end; ++cell_it )
|
|
for( int i = 0; i < self.tr.dimension(); ++i )
|
|
for( int j = i+1; j < self.tr.dimension()+1; ++j ) {
|
|
auto v1 = (*cell_it)->vertex(i);
|
|
auto v2 = (*cell_it)->vertex(j);
|
|
if(self.tr.is_infinite(v1) || self.tr.is_infinite(v2)) continue;
|
|
auto [contexts_begin, contexts_end] =
|
|
self.constraint_hierarchy.contexts_range(v1, v2);
|
|
if(contexts_begin != contexts_end) {
|
|
self.add_to_subconstraints_to_conform(v1, v2,
|
|
contexts_begin->id());
|
|
}
|
|
}
|
|
}
|
|
void reinsert_vertices(Vertex_handle) const {}
|
|
Vertex_handle replace_vertex(Cell_handle c, int index, const Point& ) const
|
|
{
|
|
return c->vertex(index);
|
|
}
|
|
void hide_point(Cell_handle, const Point& ) const {}
|
|
|
|
void insert_Steiner_point_on_constraint([[maybe_unused]] Constraint_id constraint,
|
|
[[maybe_unused]] Vertex_handle va,
|
|
[[maybe_unused]] Vertex_handle vb,
|
|
[[maybe_unused]] Vertex_handle v_Steiner) const
|
|
{
|
|
}
|
|
|
|
Vertex_handle insert_in_triangulation(const Point& p, Locate_type lt, Cell_handle c, int li, int lj) {
|
|
return self.insert_impl_do_not_split(p, lt, c, li, lj, *this);
|
|
}
|
|
};
|
|
|
|
auto make_subconstraint(Vertex_handle va, Vertex_handle vb) {
|
|
return make_sorted_pair(va, vb);
|
|
}
|
|
|
|
void add_to_subconstraints_to_conform(Vertex_handle va, Vertex_handle vb,
|
|
Constraint_id id) {
|
|
const auto pair = make_subconstraint(va, vb);
|
|
#if CGAL_DEBUG_CDT_3 & 32
|
|
std::cerr << "tr.subconstraints_to_conform.push("
|
|
<< display_subcstr(pair) << ")\n";
|
|
#endif // CGAL_DEBUG_CDT_3
|
|
subconstraints_to_conform.push({pair, id});
|
|
}
|
|
|
|
template <typename Visitor>
|
|
Constraint_id insert_constrained_edge_impl(Vertex_handle va, Vertex_handle vb,
|
|
Visitor&) {
|
|
if(va != vb) {
|
|
if(segment_vertex_epsilon != 0.) {
|
|
auto [min_dist, min_vertex] = min_distance_and_vertex_between_constraint_and_encroaching_vertex(va, vb);
|
|
check_segment_vertex_distance_or_throw(va, vb, min_vertex, CGAL::to_double(min_dist),
|
|
Check_distance::NON_SQUARED_DISTANCE);
|
|
}
|
|
const Constraint_id c_id = constraint_hierarchy.insert_constraint(va, vb);
|
|
pair_of_vertices_to_cid.emplace(make_sorted_pair(va, vb), c_id);
|
|
// traverse all the vertices along [va, vb] and add pairs of consecutive
|
|
// vertices as sub-constraints.
|
|
std::for_each(tr.segment_traverser_simplices_begin(va, vb), tr.segment_traverser_simplices_end(),
|
|
[&, prev = Vertex_handle{}](auto simplex) mutable {
|
|
// std::cerr << "- " << oformat(simplex, With_point_tag{}) << '\n';
|
|
if(simplex.dimension() == 0) {
|
|
const auto v = static_cast<Vertex_handle>(simplex);
|
|
v->set_on_constraint(c_id);
|
|
if(prev != Vertex_handle{}) {
|
|
if(v != vb) {
|
|
v->set_vertex_type(CDT_3_vertex_type::STEINER_ON_EDGE);
|
|
constraint_hierarchy.add_Steiner(prev, vb, v);
|
|
}
|
|
add_to_subconstraints_to_conform(prev, v, c_id);
|
|
}
|
|
prev = v;
|
|
}
|
|
});
|
|
return c_id;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
template <typename Visitor>
|
|
Vertex_handle insert_impl_do_not_split(const Point &p, Locate_type lt, Cell_handle c,
|
|
int li, int lj, Visitor& visitor)
|
|
{
|
|
switch (tr.dimension()) {
|
|
case 3: {
|
|
typename T_3::Conflict_tester_3 tester(p, this);
|
|
return tr.insert_in_conflict(p, lt, c, li, lj, tester,
|
|
visitor);
|
|
} // dim 3
|
|
case 2: {
|
|
typename T_3::Conflict_tester_2 tester(p, this);
|
|
return tr.insert_in_conflict(p, lt, c, li, lj, tester,
|
|
visitor);
|
|
} // dim 2
|
|
default:
|
|
// dimension <= 1
|
|
// Do not use the generic insert.
|
|
return tr.insert(p, c);
|
|
}
|
|
}
|
|
|
|
template <typename Visitor>
|
|
Vertex_handle insert_impl(const Point &p, Locate_type lt, Cell_handle c,
|
|
int li, int lj, Visitor& visitor)
|
|
{
|
|
Vertex_handle v1, v2;
|
|
bool split_constrained_edge = false;
|
|
if(lt == T_3::EDGE) {
|
|
v1 = c->vertex(li);
|
|
v2 = c->vertex(lj);
|
|
if(constraint_hierarchy.is_subconstrained_edge(v1, v2)) {
|
|
split_constrained_edge = true;
|
|
}
|
|
}
|
|
Vertex_handle new_vertex = insert_impl_do_not_split(p, lt, c, li, lj, visitor);
|
|
if(split_constrained_edge) {
|
|
constraint_hierarchy.split_constraint(v1, v2, new_vertex);
|
|
}
|
|
return new_vertex;
|
|
}
|
|
|
|
void update_bbox(const Point& p) {
|
|
bbox = bbox + p.bbox();
|
|
if(max_bbox_edge_length) {
|
|
update_max_bbox_edge_length();
|
|
}
|
|
}
|
|
|
|
void update_max_bbox_edge_length() {
|
|
double d_x = bbox.xmax() - bbox.xmin();
|
|
double d_y = bbox.ymax() - bbox.ymin();
|
|
double d_z = bbox.zmax() - bbox.zmin();
|
|
|
|
max_bbox_edge_length = (std::max)(d_x, (std::max)(d_y, d_z));
|
|
}
|
|
|
|
public:
|
|
void set_segment_vertex_epsilon(double epsilon) {
|
|
segment_vertex_epsilon = epsilon;
|
|
}
|
|
|
|
bool debug_Steiner_points() const {
|
|
return debug_flags[static_cast<int>(Debug_flags::Steiner_points)];
|
|
}
|
|
|
|
void debug_Steiner_points(bool b) {
|
|
debug_flags.set(static_cast<int>(Debug_flags::Steiner_points), b);
|
|
}
|
|
|
|
bool debug_missing_region() const {
|
|
return debug_flags[static_cast<int>(Debug_flags::missing_region)];
|
|
}
|
|
|
|
void debug_missing_region(bool b) {
|
|
debug_flags.set(static_cast<int>(Debug_flags::missing_region), b);
|
|
}
|
|
|
|
bool debug_regions() const {
|
|
return debug_flags[static_cast<int>(Debug_flags::regions)];
|
|
}
|
|
|
|
void debug_regions(bool b) {
|
|
debug_flags.set(static_cast<int>(Debug_flags::regions), b);
|
|
}
|
|
|
|
bool debug_copy_triangulation_into_hole() const {
|
|
return debug_flags[static_cast<int>(Debug_flags::copy_triangulation_into_hole)];
|
|
}
|
|
|
|
void debug_copy_triangulation_into_hole(bool b) {
|
|
debug_flags.set(static_cast<int>(Debug_flags::copy_triangulation_into_hole), b);
|
|
}
|
|
|
|
bool debug_validity() const {
|
|
return debug_flags[static_cast<int>(Debug_flags::validity)];
|
|
}
|
|
|
|
void debug_validity(bool b) {
|
|
debug_flags.set(static_cast<int>(Debug_flags::validity), b);
|
|
}
|
|
|
|
bool use_older_cavity_algorithm() const {
|
|
return debug_flags[static_cast<int>(Debug_flags::use_older_cavity_algorithm)];
|
|
}
|
|
|
|
void use_older_cavity_algorithm(bool b) {
|
|
debug_flags.set(static_cast<int>(Debug_flags::use_older_cavity_algorithm), b);
|
|
}
|
|
|
|
Vertex_handle insert(const Point &p, Locate_type lt, Cell_handle c,
|
|
int li, int lj)
|
|
{
|
|
update_bbox(p);
|
|
auto v = insert_impl(p, lt, c, li, lj, insert_in_conflict_visitor);
|
|
restore_Delaunay(insert_in_conflict_visitor);
|
|
return v;
|
|
}
|
|
|
|
Vertex_handle insert(const Point &p, Cell_handle start = {}) {
|
|
Locate_type lt;
|
|
int li, lj;
|
|
|
|
Cell_handle c = tr.locate(p, lt, li, lj, start);
|
|
return insert(p, lt, c, li, lj);
|
|
}
|
|
|
|
Constraint_id insert_constrained_edge(Vertex_handle va, Vertex_handle vb)
|
|
{
|
|
const auto id = insert_constrained_edge_impl(va, vb, insert_in_conflict_visitor);
|
|
restore_Delaunay(insert_in_conflict_visitor);
|
|
return id;
|
|
}
|
|
|
|
bool is_conforming() const {
|
|
return std::all_of(constraint_hierarchy.sc_begin(),
|
|
constraint_hierarchy.sc_end(),
|
|
[this](const auto &sc) {
|
|
const auto va = sc.first.first;
|
|
const auto vb = sc.first.second;
|
|
const auto is_edge = this->tr.tds().is_edge(va, vb);
|
|
#if CGAL_DEBUG_CDT_3 & 128 && __has_include(<format>)
|
|
std::cerr << std::format("is_conforming>> Edge is 3D: {} ({} , {})\n",
|
|
is_edge,
|
|
CGAL::IO::oformat(va, with_point_and_info),
|
|
CGAL::IO::oformat(vb, with_point_and_info));
|
|
#endif // CGAL_DEBUG_CDT_3
|
|
return is_edge;
|
|
});
|
|
}
|
|
|
|
enum class Check_distance { SQUARED_DISTANCE, NON_SQUARED_DISTANCE };
|
|
|
|
void check_segment_vertex_distance_or_throw(Vertex_handle va,
|
|
Vertex_handle vb,
|
|
Vertex_handle min_vertex,
|
|
double min_dist,
|
|
Check_distance option)
|
|
{
|
|
if(!max_bbox_edge_length) {
|
|
update_max_bbox_edge_length();
|
|
}
|
|
if((option == Check_distance::NON_SQUARED_DISTANCE && min_dist < segment_vertex_epsilon * *max_bbox_edge_length) ||
|
|
(option == Check_distance::SQUARED_DISTANCE &&
|
|
min_dist < CGAL::square(segment_vertex_epsilon * *max_bbox_edge_length)))
|
|
{
|
|
std::stringstream ss;
|
|
ss.precision(std::cerr.precision());
|
|
ss << "A constrained segment is too close to a vertex.\n";
|
|
ss << " -> vertex " << display_vert(min_vertex) << '\n';
|
|
ss << " -> constrained segment " << display_vert(va) << " - " << display_vert(vb) << '\n';
|
|
ss << " -> distance = " << min_dist << '\n';
|
|
ss << " -> max_bbox_edge_length = " << *max_bbox_edge_length << '\n';
|
|
throw std::runtime_error(ss.str());
|
|
}
|
|
}
|
|
|
|
auto ancestors_of_Steiner_vertex_on_edge(Vertex_handle v) const {
|
|
std::pair<Vertex_handle, Vertex_handle> result;
|
|
CGAL_precondition(v->is_Steiner_vertex_on_edge());
|
|
CGAL_assertion(v->number_of_incident_constraints() == 1);
|
|
const auto v_time_stamp = v->time_stamp();
|
|
const auto constraint_id = v->constraint_id(*this);
|
|
const auto first = this->constraint_hierarchy.vertices_in_constraint_begin(constraint_id);
|
|
const auto end = this->constraint_hierarchy. vertices_in_constraint_end(constraint_id);
|
|
std::cerr << "ancestors_of_Steiner_vertex_on_edge " << display_vert(v) << '\n';
|
|
for(auto it = first; it != end; ++it) {
|
|
std::cerr << " - " << display_vert(*it) << '\n';
|
|
}
|
|
CGAL_assertion(first != end);
|
|
const auto last = std::prev(this->constraint_hierarchy.vertices_in_constraint_end(constraint_id));
|
|
CGAL_assertion(first != last);
|
|
CGAL_assertion((*first)->time_stamp() < v_time_stamp);
|
|
CGAL_assertion((*last)->time_stamp() < v_time_stamp);
|
|
for(auto it = first; (*it) != v; ++it) {
|
|
if((*it)->time_stamp() < v_time_stamp) {
|
|
result.first = *it;
|
|
}
|
|
CGAL_assertion(it != last);
|
|
}
|
|
for(auto it = last; (*it) != v; --it) {
|
|
if((*it)->time_stamp() < v_time_stamp) {
|
|
result.second = *it;
|
|
}
|
|
CGAL_assertion(it != first);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
auto min_distance_and_vertex_between_constraint_and_encroaching_vertex(Vertex_handle va, Vertex_handle vb) const {
|
|
struct Result {
|
|
typename T_3::Geom_traits::FT min_dist = std::numeric_limits<double>::max();
|
|
Vertex_handle v = {};
|
|
} result;
|
|
const auto vector_of_encroaching_vertices = encroaching_vertices(va, vb);
|
|
for(auto v: vector_of_encroaching_vertices) {
|
|
const auto dist = CGAL::approximate_sqrt(squared_distance(tr.point(v), Line{tr.point(va), tr.point(vb)}));
|
|
if(dist < result.min_dist) {
|
|
result = Result{dist, v};
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool write_missing_segments_file(std::ostream &out) {
|
|
bool any_missing_segment = false;
|
|
std::for_each(
|
|
constraint_hierarchy.sc_begin(), constraint_hierarchy.sc_end(),
|
|
[this, &out, &any_missing_segment](const auto &sc) {
|
|
if (!tr.tds().is_edge(sc.first.first, sc.first.second)) {
|
|
const auto v0 = sc.first.first;
|
|
const auto v1 = sc.first.second;
|
|
out << "2 " << this->tr.point(v0) << " " << this->tr.point(v1)
|
|
<< '\n';
|
|
any_missing_segment = true;
|
|
}
|
|
});
|
|
return any_missing_segment;
|
|
}
|
|
|
|
void write_all_segments_file(std::ostream &out) {
|
|
std::for_each(
|
|
constraint_hierarchy.sc_begin(), constraint_hierarchy.sc_end(),
|
|
[this, &out](const auto &sc) {
|
|
const auto v0 = sc.first.first;
|
|
const auto v1 = sc.first.second;
|
|
out << "2 " << this->tr.point(v0) << " " << this->tr.point(v1) << '\n';
|
|
});
|
|
}
|
|
|
|
/// @{
|
|
/// remove functions cannot be called
|
|
void remove(Vertex_handle) = delete;
|
|
void remove_cluster() = delete;
|
|
/// @}
|
|
|
|
static std::string io_signature() {
|
|
return Get_io_signature<T_3>()();
|
|
}
|
|
protected:
|
|
void debug_dump(std::string filename) const {
|
|
{
|
|
std::ofstream dump(filename + ".binary.cgal", std::ios::binary);
|
|
CGAL::IO::save_binary_file(dump, *this);
|
|
}
|
|
{
|
|
std::ofstream dump(filename + "_point.xyz");
|
|
dump.precision(17);
|
|
for(auto vh: this->finite_vertex_handles()){
|
|
dump << this->point(vh) << '\n';
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Visitor>
|
|
void restore_Delaunay(Visitor& visitor) {
|
|
while(!subconstraints_to_conform.empty()) {
|
|
const auto [subconstraint, constraint_id] = subconstraints_to_conform.top();
|
|
subconstraints_to_conform.pop();
|
|
const auto [va, vb] = subconstraint;
|
|
if(!constraint_hierarchy.is_subconstrained_edge(va, vb)) {
|
|
continue;
|
|
}
|
|
#if CGAL_DEBUG_CDT_3 & 32
|
|
std::cerr << "tr.subconstraints_to_conform.pop()="
|
|
<< display_subcstr(subconstraint) << "\n";
|
|
#endif // CGAL_DEBUG_CDT_3
|
|
conform_subconstraint(subconstraint, constraint_id, visitor);
|
|
}
|
|
}
|
|
|
|
template <typename Visitor>
|
|
Vertex_handle insert_Steiner_point_on_subconstraint(
|
|
Point steiner_pt, Cell_handle hint,
|
|
Subconstraint subconstraint, Constraint_id constraint, Visitor& visitor)
|
|
{
|
|
const Vertex_handle va = subconstraint.first;
|
|
const Vertex_handle vb = subconstraint.second;
|
|
Locate_type lt;
|
|
int li, lj;
|
|
const Cell_handle c = tr.locate(steiner_pt, lt, li, lj, hint);
|
|
const Vertex_handle v = visitor.insert_in_triangulation(steiner_pt, lt, c, li, lj);
|
|
v->set_vertex_type(CDT_3_vertex_type::STEINER_ON_EDGE);
|
|
if(lt != T_3::VERTEX) {
|
|
v->set_on_constraint(constraint);
|
|
}
|
|
constraint_hierarchy.add_Steiner(va, vb, v);
|
|
visitor.insert_Steiner_point_on_constraint(constraint, va, vb, v);
|
|
add_to_subconstraints_to_conform(va, v, constraint);
|
|
add_to_subconstraints_to_conform(v, vb, constraint);
|
|
return v;
|
|
}
|
|
|
|
/// Return `true` if a Steiner point was inserted
|
|
template <typename Visitor>
|
|
bool conform_subconstraint(Subconstraint subconstraint,
|
|
Constraint_id constraint,
|
|
Visitor& visitor)
|
|
{
|
|
const Vertex_handle va = subconstraint.first;
|
|
const Vertex_handle vb = subconstraint.second;
|
|
CGAL_assertion(va != vb);
|
|
if (!tr.tds().is_edge(va, vb)) {
|
|
const auto& [steiner_pt, hint, ref_vertex] = construct_Steiner_point(constraint, subconstraint);
|
|
[[maybe_unused]] const auto v =
|
|
insert_Steiner_point_on_subconstraint(steiner_pt, hint, subconstraint, constraint, visitor);
|
|
if(debug_Steiner_points()) {
|
|
const auto [c_start, c_end] = constraint_extremities(constraint);
|
|
std::cerr << "(" << IO::oformat(va, with_offset) << ", " << IO::oformat(vb, with_offset) << ")";
|
|
std::cerr << ": [ " << display_vert(c_start) << " - " << display_vert(c_end) << " ] ";
|
|
std::cerr << " new vertex " << display_vert(v) << '\n';
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Constraint_id constraint_from_extremities(Vertex_handle va, Vertex_handle vb) const {
|
|
if (va->number_of_incident_constraints() == 0 || vb->number_of_incident_constraints() == 0)
|
|
{
|
|
return {};
|
|
}
|
|
auto it = pair_of_vertices_to_cid.find(make_sorted_pair(va, vb));
|
|
if(it != pair_of_vertices_to_cid.end()) {
|
|
return it->second;
|
|
}
|
|
return {};
|
|
// @TODO: cleanup the rest of the function, and `constraint_around`
|
|
Constraint_id c_id = constraint_around(va, vb, false);
|
|
if(c_id != Constraint_id{}) return c_id;
|
|
c_id = constraint_around(vb, va, false);
|
|
if(c_id != Constraint_id{}) return c_id;
|
|
c_id = constraint_around(va, vb, true);
|
|
return c_id;
|
|
}
|
|
|
|
auto constraint_extremities(Constraint_id c_id) const {
|
|
CGAL_assertion(std::find(this->constraint_hierarchy.c_begin(),
|
|
this->constraint_hierarchy.c_end(), c_id) != this->constraint_hierarchy.c_end());
|
|
CGAL_assertion(this->constraint_hierarchy.vertices_in_constraint_begin(c_id) !=
|
|
this->constraint_hierarchy.vertices_in_constraint_end(c_id));
|
|
#if CGAL_DEBUG_CDT_3 & 8
|
|
std::cerr << "constraint " << (void*) c_id.vl_ptr() << " has "
|
|
<< c_id.vl_ptr()->skip_size() << " vertices\n";
|
|
#endif // CGAL_DEBUG_CDT_3
|
|
const auto begin = this->constraint_hierarchy.vertices_in_constraint_begin(c_id);
|
|
const auto end = this->constraint_hierarchy.vertices_in_constraint_end(c_id);
|
|
const auto c_va = *begin;
|
|
const auto c_vb = *std::prev(end);
|
|
return std::make_pair(c_va, c_vb);
|
|
}
|
|
|
|
Constraint_id constraint_around(Vertex_handle va, Vertex_handle vb, bool expensive = true) const {
|
|
auto constraint_id_goes_to_vb = [this, va, vb](Constraint_id c_id) {
|
|
const auto [c_va, c_vb] = constraint_extremities(c_id);
|
|
if (va == c_va && vb == c_vb)
|
|
return true;
|
|
if (vb == c_va && va == c_vb)
|
|
return true;
|
|
return false;
|
|
}; // end lambda constraint_id_goes_to_vb
|
|
if (va->number_of_incident_constraints() == 1)
|
|
{
|
|
const Constraint_id c_id = va->constraint_id(*this);
|
|
CGAL_assertion(c_id != Constraint_id{});
|
|
if(constraint_id_goes_to_vb(c_id)) return c_id;
|
|
} else if (expensive == true && va->number_of_incident_constraints() > 1) {
|
|
boost::container::small_vector<Vertex_handle, 64> adj_vertices;
|
|
this->finite_adjacent_vertices(va, std::back_inserter(adj_vertices));
|
|
for(auto other_v: adj_vertices) {
|
|
for(auto context: this->constraint_hierarchy.contexts_range(va, other_v)) {
|
|
const Constraint_id c_id = context.id();
|
|
if(constraint_id_goes_to_vb(c_id)) return c_id;
|
|
}
|
|
}
|
|
}
|
|
return Constraint_id{};
|
|
}
|
|
|
|
struct Construct_Steiner_point_return_type {
|
|
typename T_3::Geom_traits::Point_3 point;
|
|
Cell_handle hint;
|
|
Vertex_handle reference_vertex;
|
|
};
|
|
|
|
auto encroaching_vertices(Vertex_handle va, Vertex_handle vb) const {
|
|
auto& gt = tr.geom_traits();
|
|
auto angle_functor = gt.angle_3_object();
|
|
|
|
const auto& pa = tr.point(va);
|
|
const auto& pb = tr.point(vb);
|
|
|
|
namespace bc = boost::container;
|
|
bc::flat_set<Vertex_handle, std::less<Vertex_handle>,
|
|
bc::small_vector<Vertex_handle, 256>>
|
|
encroaching_vertices;
|
|
auto register_vertex = [this,&encroaching_vertices](Vertex_handle v) {
|
|
if(tr.is_infinite(v)) return;
|
|
// std::cerr << "register_vertex " << display_vert(v) << '\n';
|
|
encroaching_vertices.insert(v);
|
|
};
|
|
auto fill_encroaching_vertices = [&](const auto simplex) {
|
|
#if CGAL_DEBUG_CDT_3 & 0x10
|
|
std::cerr << " - " << IO::oformat(simplex, With_point_tag{}) << '\n';
|
|
#endif // CGAL_DEBUG_CDT_3
|
|
auto visit_cell = [&](Cell_handle cell) {
|
|
for(int i = 0, end = this->tr.dimension() + 1; i < end; ++i) {
|
|
const auto v = cell->vertex(i);
|
|
register_vertex(v);
|
|
}
|
|
};
|
|
switch(simplex.dimension()) {
|
|
case 3: {
|
|
const auto cell = static_cast<Cell_handle>(simplex);
|
|
visit_cell(cell);
|
|
} break;
|
|
case 2: {
|
|
const auto [cell, facet_index] = static_cast<Facet>(simplex);
|
|
visit_cell(cell);
|
|
if(tr.dimension() > 2) {
|
|
const auto [other_cell, other_index] = tr.mirror_facet({cell, facet_index});
|
|
register_vertex(other_cell->vertex(other_index));
|
|
}
|
|
break;
|
|
}
|
|
case 1: {
|
|
auto edge = static_cast<Edge>(simplex);
|
|
if(tr.dimension() < 3) {
|
|
auto [cell, i, j] = edge;
|
|
visit_cell(cell);
|
|
if(tr.dimension() < 2) break;
|
|
auto neighbor_cell = cell->neighbor(3 - i - j);
|
|
visit_cell(neighbor_cell);
|
|
break;
|
|
}
|
|
auto circ = tr.incident_cells(edge);
|
|
CGAL_assertion(circ != nullptr);
|
|
const auto end = circ;
|
|
do {
|
|
visit_cell(circ);
|
|
} while(++circ != end);
|
|
} break;
|
|
case 0: {
|
|
const auto v = static_cast<Vertex_handle>(simplex);
|
|
if(v != va && v != vb) {
|
|
std::cerr << "!! The constraint passes through a vertex!\n";
|
|
std::cerr << " -> constraint " << display_vert(va) << " " << display_vert(vb) << '\n';
|
|
std::cerr << " -> vertex " << display_vert(v) << '\n';
|
|
debug_dump("bug-through-vertex");
|
|
CGAL_error();
|
|
}
|
|
} break;
|
|
default: CGAL_unreachable();
|
|
} // end switch
|
|
};
|
|
std::for_each(tr.segment_traverser_simplices_begin(va, vb), tr.segment_traverser_simplices_end(),
|
|
fill_encroaching_vertices);
|
|
auto vector_of_encroaching_vertices = encroaching_vertices.extract_sequence();
|
|
#if CGAL_DEBUG_CDT_3 & 0x10
|
|
std::cerr << " -> vector_of_encroaching_vertices (before filter):\n";
|
|
std::for_each(vector_of_encroaching_vertices.begin(),
|
|
vector_of_encroaching_vertices.end(),
|
|
[this](Vertex_handle v){
|
|
std::cerr << " " << this->display_vert(v) << '\n';
|
|
});
|
|
#endif // CGAL_DEBUG_CDT_3
|
|
auto end = std::remove_if(vector_of_encroaching_vertices.begin(),
|
|
vector_of_encroaching_vertices.end(),
|
|
[va, vb, pa, pb, &angle_functor, this](Vertex_handle v) {
|
|
if(va == v || vb == v) return true;
|
|
return angle_functor(pa,
|
|
this->tr.point(v),
|
|
pb) == ACUTE;
|
|
});
|
|
#if CGAL_DEBUG_CDT_3 & 0x10
|
|
std::cerr << " -> vector_of_encroaching_vertices (after filter):\n";
|
|
std::for_each(vector_of_encroaching_vertices.begin(), end, [&](Vertex_handle v) {
|
|
std::cerr << " " << this->display_vert(v) << " angle " << approximate_angle(pa, this->tr.point(v), pb)
|
|
<< '\n';
|
|
});
|
|
#endif // CGAL_DEBUG_CDT_3
|
|
vector_of_encroaching_vertices.erase(end, vector_of_encroaching_vertices.end());
|
|
return vector_of_encroaching_vertices;
|
|
}
|
|
|
|
Construct_Steiner_point_return_type
|
|
construct_Steiner_point(Constraint_id constraint_id, Subconstraint subconstraint)
|
|
{
|
|
auto& gt = tr.geom_traits();
|
|
auto compare_angle_functor = gt.compare_angle_3_object();
|
|
auto vector_functor = gt.construct_vector_3_object();
|
|
auto midpoint_functor = gt.construct_midpoint_3_object();
|
|
auto scaled_vector_functor = gt.construct_scaled_vector_3_object();
|
|
auto sq_length_functor = gt.compute_squared_length_3_object();
|
|
auto sc_product_functor = gt.compute_scalar_product_3_object();
|
|
auto translate_functor = gt.construct_translated_point_3_object();
|
|
|
|
const Vertex_handle va = subconstraint.first;
|
|
const Vertex_handle vb = subconstraint.second;
|
|
const auto& pa = tr.point(va);
|
|
const auto& pb = tr.point(vb);
|
|
const auto [orig_va, orig_vb] = constraint_extremities(constraint_id);
|
|
const auto& orig_pa = tr.point(orig_va);
|
|
const auto& orig_pb = tr.point(orig_vb);
|
|
|
|
if(this->dimension() < 2) {
|
|
std::cerr << "dim < 2: midpoint\n";
|
|
return {midpoint_functor(pa, pb), va->cell(), va};
|
|
}
|
|
|
|
#if CGAL_DEBUG_CDT_3 & 0x10
|
|
std::cerr << "construct_Steiner_point( " << display_vert(va) << " , "
|
|
<< display_vert(vb) << " )\n";
|
|
#endif // CGAL_DEBUG_CDT_3
|
|
|
|
const auto vector_of_encroaching_vertices = encroaching_vertices(va, vb);
|
|
CGAL_assertion(vector_of_encroaching_vertices.size() > 0);
|
|
|
|
const auto reference_vertex_it = std::max_element(
|
|
vector_of_encroaching_vertices.begin(), vector_of_encroaching_vertices.end(),
|
|
[pa, pb, &compare_angle_functor, this](Vertex_handle v1,
|
|
Vertex_handle v2) {
|
|
return compare_angle_functor(pa, this->tr.point(v1), pb,
|
|
pa, this->tr.point(v2), pb) == SMALLER;
|
|
});
|
|
CGAL_assertion(reference_vertex_it != vector_of_encroaching_vertices.end());
|
|
#if CGAL_CDT_3_DEBUG_CONFORMING
|
|
std::cerr << " -> reference point: " << display_vert(*reference_vertex_it)
|
|
<< '\n';
|
|
#endif // CGAL_CDT_3_DEBUG_CONFORMING
|
|
const auto reference_vertex = *reference_vertex_it;
|
|
const auto& reference_point = tr.point(reference_vertex);
|
|
|
|
const auto vector_ab = vector_functor(pa, pb);
|
|
|
|
if(reference_vertex->is_Steiner_vertex_on_edge()) {
|
|
CGAL_assertion(reference_vertex->number_of_incident_constraints() == 1);
|
|
const auto ref_constraint_id = reference_vertex->constraint_id(*this);
|
|
const auto [ref_va, ref_vb] = constraint_extremities(ref_constraint_id);
|
|
#if CGAL_CDT_3_DEBUG_CONFORMING
|
|
std::cerr << " reference point is on constraint: " << display_vert(ref_va)
|
|
<< " " << display_vert(ref_vb) << '\n'
|
|
<< " original constraint: " << display_vert(orig_va)
|
|
<< " " << display_vert(orig_vb) << '\n';
|
|
#endif // CGAL_CDT_3_DEBUG_CONFORMING
|
|
const auto vector_orig_ab = vector_functor(orig_pa, orig_pb);
|
|
const auto length_ab = CGAL::approximate_sqrt(sq_length_functor(vector_ab));
|
|
auto return_orig_result_point =
|
|
[&](auto lambda, Point orig_pa, Point orig_pb)
|
|
-> Construct_Steiner_point_return_type
|
|
{
|
|
const auto vector_orig_ab = vector_functor(orig_pa, orig_pb);
|
|
const auto inter_point = translate_functor(orig_pa, scaled_vector_functor(vector_orig_ab, lambda));
|
|
const auto dist_a_result = CGAL::approximate_sqrt(sq_length_functor(vector_functor(pa, inter_point)));
|
|
const auto ratio = dist_a_result / length_ab;
|
|
const auto result_point = (ratio < 0.2 || ratio > 0.8)
|
|
? midpoint_functor(pa, pb)
|
|
: inter_point;
|
|
|
|
#if CGAL_CDT_3_DEBUG_CONFORMING
|
|
std::cerr << " ref ratio = " << ratio << '\n';
|
|
std::cerr << " -> Steiner point: " << result_point << '\n';
|
|
#endif // CGAL_CDT_3_DEBUG_CONFORMING
|
|
return {result_point, reference_vertex->cell(), reference_vertex};
|
|
};
|
|
|
|
const auto length_orig_ab = CGAL::approximate_sqrt(sq_length_functor(vector_orig_ab));
|
|
if(ref_va == orig_va || ref_vb == orig_va) {
|
|
const auto vector_orig_a_ref = vector_functor(orig_pa, reference_point);
|
|
const auto length_orig_a_ref = CGAL::approximate_sqrt(sq_length_functor(vector_orig_a_ref));
|
|
const auto lambda = length_orig_a_ref / length_orig_ab;
|
|
return return_orig_result_point(lambda, orig_pa, orig_pb);
|
|
} else if(ref_va == orig_vb || ref_vb == orig_vb) {
|
|
const auto vector_orig_b_ref = vector_functor(orig_pb, reference_point);
|
|
const auto length_orig_b_ref = CGAL::approximate_sqrt(sq_length_functor(vector_orig_b_ref));
|
|
const auto lambda = length_orig_b_ref / length_orig_ab;
|
|
return return_orig_result_point(lambda, orig_pb, orig_pa);
|
|
}
|
|
} else {
|
|
if(segment_vertex_epsilon > 0) {
|
|
if(!max_bbox_edge_length) {
|
|
update_max_bbox_edge_length();
|
|
}
|
|
auto sq_dist = squared_distance(reference_point, Line{orig_pa, orig_pb});
|
|
check_segment_vertex_distance_or_throw(orig_va, orig_vb, reference_vertex, CGAL::to_double(sq_dist),
|
|
Check_distance::SQUARED_DISTANCE);
|
|
}
|
|
}
|
|
// compute the projection of the reference point
|
|
const auto vector_a_ref = vector_functor(pa, reference_point);
|
|
const auto lambda = sc_product_functor(vector_a_ref, vector_ab) / sq_length_functor(vector_ab);
|
|
const auto result_point = (lambda < 0.2 || lambda > 0.8)
|
|
? midpoint_functor(pa, pb)
|
|
: translate_functor(pa, scaled_vector_functor(vector_ab, lambda));
|
|
|
|
#if CGAL_CDT_3_DEBUG_CONFORMING
|
|
std::cerr << " lambda = " << lambda << '\n';
|
|
std::cerr << " -> Steiner point: " << result_point << '\n';
|
|
#endif // CGAL_CDT_3_DEBUG_CONFORMING
|
|
return {result_point, reference_vertex->cell(), reference_vertex};
|
|
}
|
|
|
|
protected:
|
|
T_3& tr = *this;
|
|
Compare_vertex_handle comp = {this};
|
|
Constraint_hierarchy constraint_hierarchy = {comp};
|
|
Bbox_3 bbox{};
|
|
double segment_vertex_epsilon = 1e-8;
|
|
std::optional<double> max_bbox_edge_length;
|
|
std::map<std::pair<Vertex_handle, Vertex_handle>, Constraint_id> pair_of_vertices_to_cid;
|
|
Insert_in_conflict_visitor insert_in_conflict_visitor = {*this};
|
|
|
|
std::stack<std::pair<Subconstraint, Constraint_id> >
|
|
subconstraints_to_conform;
|
|
|
|
enum class Debug_flags {
|
|
Steiner_points = 0,
|
|
conforming,
|
|
missing_region,
|
|
regions,
|
|
copy_triangulation_into_hole,
|
|
validity,
|
|
use_older_cavity_algorithm,
|
|
nb_of_flags
|
|
};
|
|
std::bitset<static_cast<int>(Debug_flags::nb_of_flags)> debug_flags{};
|
|
};
|
|
|
|
} // end CGAL
|
|
|
|
#endif // CGAL_CONFORMING_DELAUNAY_TRIANGULATION_3_H
|