1125 lines
39 KiB
C++
Executable File
1125 lines
39 KiB
C++
Executable File
// Copyright (c) 2009 INRIA Sophia-Antipolis (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 so
|
|
//
|
|
// 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) : Sebastien Loriot
|
|
//
|
|
|
|
#ifndef CGAL_FIXED_ALPHA_SHAPE_3_H
|
|
#define CGAL_FIXED_ALPHA_SHAPE_3_H
|
|
|
|
#include <CGAL/license/Alpha_shapes_3.h>
|
|
|
|
|
|
#include <CGAL/basic.h>
|
|
|
|
#include <set>
|
|
#include <map>
|
|
#include <list>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <utility>
|
|
#include <iostream>
|
|
#include <queue>
|
|
#include <boost/next_prior.hpp>
|
|
|
|
#include <CGAL/Triangulation_utils_3.h>
|
|
#include <CGAL/Unique_hash_map.h>
|
|
#include <CGAL/iterator.h>
|
|
#ifdef CGAL_USE_GEOMVIEW
|
|
#include <CGAL/IO/Geomview_stream.h> // TBC
|
|
#endif
|
|
|
|
#include <CGAL/internal/Classification_type.h>
|
|
|
|
#include <CGAL/Triangulation_3.h>
|
|
|
|
|
|
namespace CGAL{
|
|
|
|
namespace internal{
|
|
//small utility to select the correct predicate in the weighted case
|
|
template <class One_alpha,class Weighted_tag>
|
|
struct Simplex_classif_predicate;
|
|
|
|
template <class One_alpha>
|
|
struct Simplex_classif_predicate<One_alpha,Tag_false>{
|
|
static
|
|
typename One_alpha::Triangulation::Geom_traits::Compare_squared_radius_3
|
|
predicate(const typename One_alpha::Triangulation& T){
|
|
return T.geom_traits().compare_squared_radius_3_object();
|
|
}
|
|
};
|
|
|
|
template <class One_alpha>
|
|
struct Simplex_classif_predicate<One_alpha,Tag_true>{
|
|
static
|
|
typename One_alpha::Triangulation::Geom_traits::Compare_weighted_squared_radius_3
|
|
predicate(const typename One_alpha::Triangulation& T){
|
|
return T.geom_traits().compare_weighted_squared_radius_3_object();
|
|
}
|
|
};
|
|
|
|
//small utility to insert hidden vertices after a removal (in non-weighted case do nothing)
|
|
template <class One_alpha,class Weighted_tag=::CGAL::Tag_true>
|
|
struct Hidden_inserter
|
|
{
|
|
typedef typename One_alpha::Triangulation Dt;
|
|
|
|
template <class Vertex_remover>
|
|
static void insert(One_alpha& one_alpha, Vertex_remover& remover)
|
|
{
|
|
typename One_alpha::Cell_handle c;
|
|
for (typename Vertex_remover::Hidden_points_iterator
|
|
hi = remover.hidden_points_begin();
|
|
hi != remover.hidden_points_end(); ++hi)
|
|
{
|
|
typename One_alpha::Vertex_handle hv = one_alpha.insert (*hi, c);
|
|
if (hv != typename One_alpha::Vertex_handle()) c = hv->cell();
|
|
}
|
|
}
|
|
};
|
|
|
|
template <class One_alpha>
|
|
struct Hidden_inserter<One_alpha,::CGAL::Tag_false>{
|
|
typedef typename One_alpha::Triangulation Dt;
|
|
template <class Vertex_remover>
|
|
static void insert(const One_alpha&,const Vertex_remover&){}
|
|
};
|
|
|
|
|
|
} //internal
|
|
|
|
|
|
template < class Dt >
|
|
class Fixed_alpha_shape_3 : public Dt
|
|
{
|
|
// DEFINITION The class Fixed_alpha_shape_3<Dt> represents the
|
|
// alpha-shape for a set of points (or a set of weighted points)
|
|
// for a given value of alpha. The alphashape is defined through
|
|
// the Delaunay tetrahedralization of the points
|
|
// (or the Regular tetrahedralization in case of weighted points)
|
|
// and depends on the value of a parameter called alpha.
|
|
// The alpha_shape is the domain of a subcomplex of this triangulation
|
|
// called the Alpha_complex. The alpha_complex includes any simplex
|
|
// having a circumscribing sphere (an orthogonal sphere
|
|
// in case of weighted points) empty of other points
|
|
// (or suborthogonal to other sites in case of weighted points)
|
|
// with squared radius equal or less than alpha
|
|
|
|
// In each k-dimensional simplex of the triangulation
|
|
// for (k=0,1,2)
|
|
// can be classified as EXTERIOR, SINGULAR, REGULAR
|
|
// or INTERIOR with respect to the alpha shape.
|
|
// A $k$ simplex is REGULAR if it is on the boundary
|
|
// of the alpha_complex and belongs to a $k+1$ simplex in the complex
|
|
// and it is SINGULAR simplex if it is a boundary simplex tht is not
|
|
// included in a $k+1$ simplex of the complex.
|
|
|
|
// Roughly, the Fixed_alpha_shape data structure computes and stores,
|
|
// for each simplex it classification type.
|
|
|
|
|
|
//------------------------- TYPES ------------------------------------
|
|
|
|
public:
|
|
typedef Dt Triangulation;
|
|
typedef typename Dt::Geom_traits Gt;
|
|
typedef typename Dt::Triangulation_data_structure Tds;
|
|
|
|
//Classification type: no longer an enum inside the class as Vertex and cell must know it
|
|
typedef internal::Classification_type Classification_type;
|
|
static const Classification_type EXTERIOR = internal::EXTERIOR;
|
|
static const Classification_type REGULAR = internal::REGULAR;
|
|
static const Classification_type INTERIOR = internal::INTERIOR;
|
|
static const Classification_type SINGULAR = internal::SINGULAR;
|
|
|
|
typedef typename Gt::FT Coord_type;
|
|
typedef Coord_type NT;
|
|
typedef Coord_type FT;
|
|
|
|
typedef typename Dt::Point Point;
|
|
|
|
typedef typename Dt::Cell_handle Cell_handle;
|
|
typedef typename Dt::Vertex_handle Vertex_handle;
|
|
typedef typename Dt::Facet Facet;
|
|
typedef typename Dt::Edge Edge;
|
|
|
|
typedef typename Dt::Cell_circulator Cell_circulator;
|
|
typedef typename Dt::Facet_circulator Facet_circulator;
|
|
|
|
typedef typename Dt::Cell_iterator Cell_iterator;
|
|
typedef typename Dt::Facet_iterator Facet_iterator;
|
|
typedef typename Dt::Edge_iterator Edge_iterator;
|
|
typedef typename Dt::Vertex_iterator Vertex_iterator;
|
|
|
|
typedef typename Dt::Finite_cells_iterator Finite_cells_iterator;
|
|
typedef typename Dt::Finite_facets_iterator Finite_facets_iterator;
|
|
typedef typename Dt::Finite_edges_iterator Finite_edges_iterator;
|
|
typedef typename Dt::Finite_vertices_iterator Finite_vertices_iterator;
|
|
|
|
typedef typename Dt::Locate_type Locate_type;
|
|
typedef typename Dt::Weighted_tag Weighted_tag;
|
|
|
|
using Dt::dimension;
|
|
using Dt::finite_facets_begin;
|
|
using Dt::finite_facets_end;
|
|
using Dt::finite_edges_begin;
|
|
using Dt::finite_edges_end;
|
|
using Dt::all_edges_begin;
|
|
using Dt::all_edges_end;
|
|
using Dt::finite_vertices_begin;
|
|
using Dt::finite_vertices_end;
|
|
using Dt::finite_cells_begin;
|
|
using Dt::finite_cells_end;
|
|
using Dt::VERTEX;
|
|
using Dt::EDGE;
|
|
using Dt::FACET;
|
|
using Dt::CELL;
|
|
using Dt::OUTSIDE_CONVEX_HULL;
|
|
using Dt::OUTSIDE_AFFINE_HULL;
|
|
using Dt::vertex_triple_index;
|
|
using Dt::finite_adjacent_vertices;
|
|
using Dt::find_conflicts;
|
|
using Dt::is_edge;
|
|
using Dt::incident_vertices;
|
|
using Dt::incident_facets;
|
|
using Dt::is_infinite;
|
|
using Dt::is_Gabriel;
|
|
using Dt::tds;
|
|
|
|
typedef std::pair<Vertex_handle, Vertex_handle> Vertex_handle_pair;
|
|
typedef std::map<Vertex_handle_pair,Classification_type> Edge_status_map;
|
|
|
|
//test if a simplex is exterior to the alpha-shape
|
|
class Exterior_simplex_test{
|
|
const Fixed_alpha_shape_3 * _as;
|
|
public:
|
|
Exterior_simplex_test() {}
|
|
Exterior_simplex_test(const Fixed_alpha_shape_3 * as) {_as = as;}
|
|
bool operator() ( const Finite_cells_iterator& fci) const {
|
|
return _as->classify(fci) == EXTERIOR ;
|
|
}
|
|
bool operator() ( const Finite_vertices_iterator& fvi) const {
|
|
return _as->classify(fvi) == EXTERIOR ;
|
|
}
|
|
bool operator() ( const Finite_facets_iterator& ffi) const {
|
|
return _as->classify(*ffi) == EXTERIOR ;
|
|
}
|
|
bool operator() ( const Finite_edges_iterator& fei) const {
|
|
return _as->classify(*fei) == EXTERIOR ;
|
|
}
|
|
};
|
|
|
|
typedef Filter_iterator< Finite_vertices_iterator, Exterior_simplex_test> Alpha_shape_vertices_iterator;
|
|
typedef Filter_iterator< Finite_facets_iterator, Exterior_simplex_test> Alpha_shape_facets_iterator;
|
|
typedef Filter_iterator< Finite_edges_iterator, Exterior_simplex_test> Alpha_shape_edges_iterator;
|
|
typedef Filter_iterator< Finite_cells_iterator, Exterior_simplex_test> Alpha_shape_cells_iterator;
|
|
|
|
|
|
Vertex_handle
|
|
insert(const Point& p,Cell_handle start=Cell_handle())
|
|
{
|
|
if (this->dimension() < 3){
|
|
Vertex_handle new_v=Triangulation::insert(p,start);
|
|
if (this->dimension() == 3) initialize_alpha();
|
|
return new_v;
|
|
}
|
|
|
|
//Handle only case of dimension 3 of insert_in_conflict from Triangulation_3 class.
|
|
typename Triangulation::Locate_type lt;
|
|
int li, lj;
|
|
Cell_handle c = this->locate(p, lt, li, lj, start);
|
|
typename Triangulation::Conflict_tester_3 tester(p, this);
|
|
if ((lt == VERTEX) &&
|
|
(tester.compare_weight(c->vertex(li)->point(), p)==0) ) {
|
|
return c->vertex(li);
|
|
}
|
|
// If the new point is not in conflict with its cell, it is hidden.
|
|
if (!tester.test_initial_cell(c)) {
|
|
this->hidden_point_visitor.hide_point(c,p);
|
|
return Vertex_handle();
|
|
}
|
|
|
|
// Ok, we really insert the point now.
|
|
// First, find the conflict region.
|
|
std::vector<Cell_handle> cells;
|
|
std::vector<Facet> facets_on_the_boundary_of_the_hole;
|
|
|
|
cells.reserve(32);
|
|
this->find_conflicts
|
|
(c, tester, make_triple(std::back_inserter(facets_on_the_boundary_of_the_hole),
|
|
std::back_inserter(cells),
|
|
Emptyset_iterator()));
|
|
|
|
Facet facet=*boost::prior(facets_on_the_boundary_of_the_hole.end());
|
|
|
|
// Remember the points that are hidden by the conflicting cells,
|
|
// as they will be deleted during the insertion.
|
|
this->hidden_point_visitor.process_cells_in_conflict(cells.begin(), cells.end());
|
|
|
|
|
|
//Before insertion:
|
|
//recover edges on the boundary of the hole.
|
|
std::set<Edge,Compare_edge> hole_boundary_edges;
|
|
const int indices[3]={1,2,3};
|
|
for (typename std::vector<Facet>::iterator
|
|
it=facets_on_the_boundary_of_the_hole.begin();
|
|
it!=facets_on_the_boundary_of_the_hole.end();
|
|
++it)
|
|
{
|
|
Facet f=it->first->tds_data().is_in_conflict()?this->mirror_facet(*it):*it;
|
|
CGAL_precondition(!f.first->tds_data().is_in_conflict());
|
|
for (int i=0;i<3;++i){
|
|
Edge edge(f.first,(indices[i]+f.second)%4,(indices[(i+1)%3]+f.second)%4);
|
|
if (!this->is_infinite(edge))
|
|
hole_boundary_edges.insert(edge);
|
|
}
|
|
}
|
|
// Erase from edge_status_map, edges that will disappear:
|
|
// they are not on the boudary of the hole
|
|
std::set<Edge,Compare_edge> hole_edges;
|
|
std::pair<typename std::set<Edge,Compare_edge>::iterator,bool> it_hedge_and_not_already_seen;
|
|
for (typename std::vector<Cell_handle>::iterator it=cells.begin();it!=cells.end();++it){
|
|
for (int i=0;i<3;++i)
|
|
for (int k=i+1;k<4;++k)
|
|
{
|
|
Edge edge(*it,i,k);
|
|
if (this->is_infinite(edge) || hole_boundary_edges.find(edge)!=hole_boundary_edges.end() ) continue;
|
|
it_hedge_and_not_already_seen=hole_edges.insert(edge);
|
|
if (!it_hedge_and_not_already_seen.second){
|
|
edge_status_map.erase(make_vertex_handle_pair(*(it_hedge_and_not_already_seen.first))); //for infinite edges it does nothing
|
|
}
|
|
}
|
|
}
|
|
|
|
//Insertion using base triangulation
|
|
Vertex_handle v = this->_insert_in_hole(p, cells.begin(), cells.end(),
|
|
facet.first, facet.second);
|
|
|
|
//After insertion
|
|
//--set classification of new cells and facets that were on the boundary of the hole
|
|
cells.clear();
|
|
this->finite_incident_cells(v,std::back_inserter(cells));
|
|
for (typename std::vector<Cell_handle>::iterator it=cells.begin();it!=cells.end();++it){
|
|
set_cell_status(*it); //set cell status
|
|
set_facet_classification_type( Facet( (*it),(*it)->index(v) ) ); //set facet status (incident to one cell of the hole)
|
|
}
|
|
//--set classification of new facets
|
|
std::vector<Facet> facets;
|
|
this->finite_incident_facets(v,std::back_inserter(facets));
|
|
for (typename std::vector<Facet>::iterator fit=facets.begin();fit!=facets.end();++fit)
|
|
set_facet_classification_type(*fit);
|
|
//--init classif of new vertex
|
|
v->set_classification_type(SINGULAR);
|
|
std::vector<Edge> edges;
|
|
this->finite_incident_edges(v,std::back_inserter(edges));
|
|
//--set status of new edges + update status of new vertex
|
|
for (typename std::vector<Edge>::iterator eit=edges.begin();eit!=edges.end();++eit){
|
|
Classification_type status=compute_edge_status(eit->first, eit->second, eit->third);
|
|
Vertex_handle_pair vhp = make_vertex_handle_pair( *eit );
|
|
CGAL_precondition( edge_status_map.find(vhp)==edge_status_map.end() );
|
|
edge_status_map.insert(std::make_pair(vhp, status));
|
|
update_vertex_status(v,status);
|
|
}
|
|
//--set final status of new vertex
|
|
Cell_handle tmp;
|
|
int itmp1,itmp2;
|
|
v->is_on_chull( this->is_edge(this->infinite_vertex(),v,tmp,itmp1,itmp2) );
|
|
finalize_status_of_vertex(v);
|
|
|
|
//--set classification of old edges
|
|
for (typename std::set<Edge,Compare_edge>::iterator
|
|
eit=hole_boundary_edges.begin();eit!=hole_boundary_edges.end();++eit)
|
|
{
|
|
CGAL_precondition (!this->is_infinite(*eit));
|
|
Classification_type status=compute_edge_status(eit->first, eit->second, eit->third);
|
|
Vertex_handle_pair vhp = make_vertex_handle_pair( *eit );
|
|
typename Edge_status_map::iterator it_status=edge_status_map.find(vhp);
|
|
CGAL_precondition(it_status!=edge_status_map.end());
|
|
it_status->second = status;
|
|
}
|
|
|
|
//--set status of old vertices + update is_on_chull
|
|
//TODO: find a better way to do it : make an update
|
|
std::vector<Vertex_handle> vertices;
|
|
this->finite_adjacent_vertices(v,std::back_inserter(vertices));
|
|
for (typename std::vector<Vertex_handle>::iterator vit=vertices.begin();vit!=vertices.end();++vit){
|
|
if ( (*vit)->is_on_chull() )
|
|
{
|
|
(*vit)->is_on_chull( this->is_edge(this->infinite_vertex(),(*vit),tmp,itmp1,itmp2) );
|
|
}
|
|
set_vertex_status(*vit);
|
|
}
|
|
|
|
// Store the hidden points in their new cells.
|
|
this->hidden_point_visitor.reinsert_vertices(v);
|
|
return v;
|
|
}
|
|
|
|
void remove (Vertex_handle vertex_to_remove)
|
|
{
|
|
CGAL_precondition(vertex_to_remove!=Vertex_handle());
|
|
CGAL_precondition(vertex_to_remove!=this->infinite_vertex());
|
|
|
|
std::vector<Facet> link;
|
|
std::vector<Vertex_handle> vertices_to_update;
|
|
std::map<Vertex_handle,Classification_type> old_classification;
|
|
|
|
if (this->dimension() == 3)
|
|
{
|
|
//recover facet of the link: they are bounding
|
|
//the hole made when removing the vertex
|
|
std::vector<Cell_handle> incident_cells;
|
|
this->finite_incident_cells(vertex_to_remove,std::back_inserter(incident_cells));
|
|
for (typename std::vector<Cell_handle>::iterator it=
|
|
incident_cells.begin();it!=incident_cells.end();++it)
|
|
{
|
|
int index=(*it)->index(vertex_to_remove);
|
|
link.push_back( this->mirror_facet(Facet(*it,index)) );
|
|
CGAL_assertion (!this->is_infinite(link.back()));
|
|
}
|
|
|
|
//get vertices that will need to be updated
|
|
this->finite_adjacent_vertices(vertex_to_remove,std::back_inserter(vertices_to_update));
|
|
|
|
//1-erase removed edges from edge_map
|
|
//2-store old classification of vertices and set it to SINGULAR
|
|
for(typename std::vector<Vertex_handle>::iterator it=vertices_to_update.begin();it!=vertices_to_update.end();++it){
|
|
CGAL_precondition(edge_status_map.find(make_vertex_handle_pair(*it,vertex_to_remove)) != edge_status_map.end());
|
|
edge_status_map.erase(make_vertex_handle_pair(*it,vertex_to_remove));
|
|
old_classification.insert(std::make_pair(*it,(*it)->get_classification_type()));
|
|
(*it)->set_classification_type(SINGULAR);
|
|
}
|
|
}
|
|
|
|
//Do remove the vertex from the underlying triangulation
|
|
Dt tmp;
|
|
typename Dt:: template Vertex_remover<Dt> remover(tmp);
|
|
typedef typename Dt::Tr_Base Tr_Base;
|
|
Tr_Base::remove(vertex_to_remove,remover);
|
|
|
|
if (this->dimension()<3){
|
|
edge_status_map.clear();
|
|
return;
|
|
}
|
|
|
|
//recover new cells
|
|
std::set<Cell_handle> new_cells;//cells in the hole
|
|
std::set<Cell_handle> outer;//cells that are not in the hole
|
|
std::queue<Cell_handle> to_visit;
|
|
for (typename std::vector<Facet>::iterator it=link.begin();it!=link.end();++it){
|
|
Facet mirror_facet=this->mirror_facet(*it);
|
|
if ( !this->is_infinite(mirror_facet.first) )
|
|
{
|
|
to_visit.push( mirror_facet.first );
|
|
outer.insert(it->first);
|
|
}
|
|
}
|
|
|
|
while (!to_visit.empty()){
|
|
Cell_handle cell=to_visit.front();
|
|
to_visit.pop();
|
|
if ( !new_cells.insert(cell).second ) continue; //already explored
|
|
//explore neighbor cells
|
|
for (int i=0;i<4;++i){
|
|
Cell_handle candidate=cell->neighbor(i);
|
|
if ( !this->is_infinite(candidate) && outer.find(candidate)==outer.end())
|
|
to_visit.push(candidate);
|
|
}
|
|
}
|
|
|
|
//set status of new cells
|
|
for (typename std::set<Cell_handle>::iterator it=new_cells.begin();it!=new_cells.end();++it){
|
|
CGAL_precondition(!this->is_infinite(*it));
|
|
set_cell_status(*it);
|
|
}
|
|
|
|
//recover new facets + link facets
|
|
//set vertex that are on the convex hull (those on a facet incident to infinite cell)
|
|
while (!new_cells.empty())
|
|
{
|
|
Cell_handle cell=*new_cells.begin();
|
|
new_cells.erase(new_cells.begin());
|
|
for (int i=0;i<4;++i){
|
|
if ( new_cells.find(cell->neighbor(i)) != new_cells.end() )
|
|
link.push_back(Facet(cell,i));
|
|
else{
|
|
if ( this->is_infinite( cell->neighbor(i) ) ){
|
|
link.push_back(Facet(cell,i));
|
|
cell->vertex((i+1)%4)->is_on_chull(true);
|
|
cell->vertex((i+2)%4)->is_on_chull(true);
|
|
cell->vertex((i+3)%4)->is_on_chull(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::set<Edge,Compare_edge> new_edges;
|
|
//1- set status of these facets
|
|
//2- recover new edges + edges incident to link facets
|
|
const int index[3]={1,2,3};
|
|
for (typename std::vector<Facet>::iterator it=link.begin();it!=link.end();++it){
|
|
set_facet_classification_type(*it);
|
|
for (int i=0;i<3;++i){
|
|
new_edges.insert(Edge(it->first,(it->second+index[i])%4,(it->second+index[(i+1)%3])%4));
|
|
}
|
|
}
|
|
|
|
//set status of these edges
|
|
for(typename std::set<Edge,Compare_edge>::iterator it=new_edges.begin();it!=new_edges.end();++it){
|
|
Classification_type status=compute_edge_status(it->first, it->second, it->third);
|
|
//cross links
|
|
Vertex_handle_pair vhp = make_vertex_handle_pair( *it );
|
|
edge_status_map[vhp]=status;
|
|
|
|
//update status of incident vertices
|
|
update_vertex_status(it->first->vertex(it->second),status);
|
|
update_vertex_status(it->first->vertex(it->third),status);
|
|
}
|
|
|
|
//set final status of vertices
|
|
for(typename std::vector<Vertex_handle>::iterator it=vertices_to_update.begin();it!=vertices_to_update.end();++it){
|
|
//this one is working but should be more expensive
|
|
//set_vertex_status(*it); continue;
|
|
|
|
Classification_type old_status=old_classification[*it];
|
|
Classification_type status=(*it)->get_classification_type();
|
|
|
|
CGAL_precondition( status!=SINGULAR ); // at least on edge is incident to that vertex
|
|
|
|
if (status==INTERIOR){
|
|
if (old_status!=INTERIOR || (*it)->is_on_chull())
|
|
(*it)->set_classification_type(REGULAR);
|
|
else
|
|
(*it)->set_classification_type(INTERIOR);
|
|
continue;
|
|
}
|
|
|
|
if (status==REGULAR) continue;
|
|
|
|
if ( status==EXTERIOR && ( old_status==REGULAR || old_status==INTERIOR) ){ //if vertex was EXTERIOR or SINGULAR other edges are not in the alpha complex
|
|
//check if former REGULAR vertex becomes EXTERIOR or SINGULAR
|
|
std::list<Vertex_handle> incidentv;
|
|
finite_adjacent_vertices(*it,back_inserter(incidentv));
|
|
|
|
typename std::list<Vertex_handle>::iterator vvit=incidentv.begin();
|
|
for( ; vvit != incidentv.end(); ++vvit) {
|
|
//TODO: We take all edges -> WE SHOULD ONLY TAKE THOSE NOT IN THE HOLE
|
|
Vertex_handle_pair vhp = make_vertex_handle_pair( *vvit, *it);
|
|
CGAL_assertion(edge_status_map.find(vhp)!=edge_status_map.end());
|
|
Classification_type status=edge_status_map[vhp];
|
|
if (status!=EXTERIOR){
|
|
(*it)->set_classification_type(REGULAR);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( vvit != incidentv.end() ) continue;
|
|
}
|
|
|
|
if ( is_vertex_Gabriel((*it),Weighted_tag()) && is_gabriel_simplex_in_alpha_complex((*it)) )
|
|
(*it)->set_classification_type(SINGULAR);
|
|
else
|
|
(*it)->set_classification_type(EXTERIOR);
|
|
}
|
|
|
|
//Insert possible hidden points
|
|
internal::Hidden_inserter<Fixed_alpha_shape_3<Dt>,Weighted_tag>::insert(*this,remover);
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
typedef internal::Simplex_classif_predicate<Fixed_alpha_shape_3<Dt>,Weighted_tag> Simplex_classif;
|
|
|
|
|
|
//data members
|
|
NT _alpha; //the value of alpha
|
|
Edge_status_map edge_status_map; //the map containing the status of edges
|
|
|
|
|
|
//------------------------- CONSTRUCTORS ------------------------------
|
|
public:
|
|
Fixed_alpha_shape_3(NT alpha=0):_alpha(alpha){}
|
|
|
|
Fixed_alpha_shape_3(Dt& dt, NT alpha = 0):_alpha(alpha){
|
|
Dt::swap(dt);
|
|
if (dimension() == 3) initialize_alpha();
|
|
}
|
|
|
|
// Introduces an alpha-shape `A' for the alpha-value
|
|
// `alpha' that is initialized with the points in the range
|
|
// from first to last
|
|
template < class InputIterator >
|
|
Fixed_alpha_shape_3(const InputIterator& first,
|
|
const InputIterator& last,
|
|
const NT& alpha = 0): _alpha(alpha)
|
|
{
|
|
Dt::insert(first, last);
|
|
if (dimension() == 3) initialize_alpha();
|
|
}
|
|
|
|
|
|
|
|
private :
|
|
|
|
template < class InputIterator >
|
|
int make_alpha_shape(const InputIterator& first,
|
|
const InputIterator& last)
|
|
{
|
|
clear();
|
|
int n = Dt::insert(first, last);
|
|
if (dimension() == 3) initialize_alpha();
|
|
return n;
|
|
}
|
|
|
|
//--------------------- INITIALIZATION OF PRIVATE MEMBERS -----------
|
|
void initialize_status_of_cells();
|
|
void initialize_status_of_facets();
|
|
void initialize_status_of_edges();
|
|
void initialize_status_of_vertices();
|
|
void finalize_status_of_vertex(Vertex_handle);
|
|
void initialize_alpha() {
|
|
//identify vertices on the convex hull
|
|
std::vector<Vertex_handle> chull;
|
|
incident_vertices(this->infinite_vertex(),std::back_inserter(chull));
|
|
for ( typename std::vector<Vertex_handle>::iterator it=chull.begin();it!=chull.end();++it)
|
|
(*it)->is_on_chull(true);
|
|
|
|
initialize_status_of_cells();
|
|
initialize_status_of_facets();
|
|
initialize_status_of_edges();
|
|
initialize_status_of_vertices();
|
|
}
|
|
|
|
private :
|
|
// prevent default copy constructor and default assigment
|
|
Fixed_alpha_shape_3(const Fixed_alpha_shape_3&);
|
|
void operator=(const Fixed_alpha_shape_3&);
|
|
|
|
static
|
|
Vertex_handle_pair
|
|
make_vertex_handle_pair( Vertex_handle v1, Vertex_handle v2) {
|
|
return v1 < v2 ? std::make_pair(v1,v2)
|
|
: std::make_pair(v2,v1);
|
|
}
|
|
|
|
static
|
|
Vertex_handle_pair
|
|
make_vertex_handle_pair( const Edge& e) {
|
|
return make_vertex_handle_pair(e.first->vertex(e.second),e.first->vertex(e.third));
|
|
}
|
|
|
|
struct Compare_edge{
|
|
bool operator()(const Edge& e1,const Edge& e2) const
|
|
{
|
|
return make_vertex_handle_pair(e1)<make_vertex_handle_pair(e2);
|
|
}
|
|
};
|
|
|
|
bool is_vertex_Gabriel(Vertex_handle,Tag_false){return true;}
|
|
template <class Vertexhandle>
|
|
bool is_vertex_Gabriel(Vertexhandle v,Tag_true){return is_Gabriel(v);}
|
|
void update_vertex_status(Vertex_handle v,Classification_type edge_status);
|
|
void set_facet_classification_type(const Facet& f);
|
|
void set_vertex_status(Vertex_handle v);
|
|
inline void set_cell_status (Cell_handle c);
|
|
Classification_type compute_edge_status( const Cell_handle& c,int i,int j) const;
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
public:
|
|
// Returns the current alpha-value.
|
|
const NT& get_alpha() const
|
|
{
|
|
return _alpha;
|
|
}
|
|
|
|
void clear()
|
|
{
|
|
// clears the structure
|
|
Dt::clear();
|
|
edge_status_map.clear();
|
|
}
|
|
|
|
//--------------------- PREDICATES -----------------------------------
|
|
|
|
public:
|
|
Classification_type classify(const Cell_handle& s) const {
|
|
if (is_infinite(s)) return EXTERIOR;
|
|
return s->get_classification_type();
|
|
}
|
|
Classification_type classify(const Facet& f) const {
|
|
if (is_infinite(f)) return EXTERIOR;
|
|
return f.first->get_facet_classification_type(f.second);
|
|
}
|
|
Classification_type classify(const Edge& e) const {
|
|
if (is_infinite(e)) return EXTERIOR;
|
|
return edge_status_map.find(make_vertex_handle_pair(e))->second;
|
|
}
|
|
Classification_type classify(const Vertex_handle& v) const {
|
|
if (is_infinite(v)) return EXTERIOR;
|
|
return v->get_classification_type();
|
|
}
|
|
|
|
|
|
//------------------- GEOMETRIC PRIMITIVES ----------------------------
|
|
private:
|
|
bool
|
|
is_gabriel_simplex_in_alpha_complex (const Cell_handle& s) const{
|
|
return
|
|
Simplex_classif::predicate(*this)(
|
|
s->vertex(0)->point(),
|
|
s->vertex(1)->point(),
|
|
s->vertex(2)->point(),
|
|
s->vertex(3)->point(),
|
|
get_alpha()
|
|
) !=POSITIVE;
|
|
}
|
|
|
|
bool
|
|
is_gabriel_simplex_in_alpha_complex (const Cell_handle& s, const int& i) const{
|
|
return
|
|
Simplex_classif::predicate(*this)(
|
|
s->vertex(vertex_triple_index(i,0))->point(),
|
|
s->vertex(vertex_triple_index(i,1))->point(),
|
|
s->vertex(vertex_triple_index(i,2))->point(),
|
|
get_alpha()
|
|
) != POSITIVE;
|
|
}
|
|
|
|
bool
|
|
is_gabriel_simplex_in_alpha_complex (const Facet& f) {
|
|
return is_gabriel_simplex_in_alpha_complex(f.first, f.second);
|
|
}
|
|
|
|
bool
|
|
is_gabriel_simplex_in_alpha_complex (const Cell_handle& s, const int& i, const int& j) const {
|
|
return
|
|
Simplex_classif::predicate(*this)(
|
|
s->vertex(i)->point(),
|
|
s->vertex(j)->point(),
|
|
get_alpha()
|
|
) !=POSITIVE;
|
|
}
|
|
|
|
bool
|
|
is_gabriel_simplex_in_alpha_complex (const Edge& e) const {
|
|
return is_gabriel_simplex_in_alpha_complex(e.first,e.second,e.third);
|
|
}
|
|
|
|
bool
|
|
is_gabriel_simplex_in_alpha_complex (const Vertex_handle& v) const {
|
|
return
|
|
Simplex_classif::predicate(*this)(
|
|
v->point(),
|
|
get_alpha()
|
|
) !=POSITIVE;
|
|
}
|
|
|
|
//---------------------------------------------------------------------
|
|
public:
|
|
#ifdef CGAL_USE_GEOMVIEW
|
|
void show_alpha_shape_faces(Geomview_stream &gv) const;
|
|
#endif
|
|
|
|
|
|
//Iterators
|
|
|
|
//---------------------------------------------------------------------
|
|
Alpha_shape_vertices_iterator alpha_shape_vertices_begin() const {
|
|
return CGAL::filter_iterator(finite_vertices_end(),Exterior_simplex_test(this),finite_vertices_begin());}
|
|
Alpha_shape_vertices_iterator alpha_shape_vertices_end() const {
|
|
return CGAL::filter_iterator(finite_vertices_end(),Exterior_simplex_test(this));}
|
|
//---------------------------------------------------------------------
|
|
Alpha_shape_facets_iterator alpha_shape_facets_begin() const{
|
|
return CGAL::filter_iterator(finite_facets_end(),Exterior_simplex_test(this),finite_facets_begin());}
|
|
Alpha_shape_facets_iterator alpha_shape_facets_end() const {
|
|
return CGAL::filter_iterator(finite_facets_end(),Exterior_simplex_test(this));}
|
|
//---------------------------------------------------------------------
|
|
Alpha_shape_edges_iterator alpha_shape_edges_begin() const{
|
|
return CGAL::filter_iterator(finite_edges_end(),Exterior_simplex_test(this),finite_edges_begin());}
|
|
Alpha_shape_edges_iterator alpha_shape_edges_end() const {
|
|
return CGAL::filter_iterator(finite_edges_end(),Exterior_simplex_test(this));}
|
|
//---------------------------------------------------------------------
|
|
Alpha_shape_cells_iterator alpha_shape_cells_begin() const {
|
|
return CGAL::filter_iterator(finite_cells_end(),Exterior_simplex_test(this),finite_cells_begin());
|
|
}
|
|
Alpha_shape_cells_iterator alpha_shape_cells_end() const {
|
|
return CGAL::filter_iterator(finite_cells_end(),Exterior_simplex_test(this));
|
|
}
|
|
//---------------------------------------------------------------------
|
|
// To extract simplices given a classification type
|
|
template<class OutputIterator>
|
|
OutputIterator get_alpha_shape_cells(OutputIterator it,
|
|
Classification_type type) const
|
|
{
|
|
Finite_cells_iterator cit = finite_cells_begin();
|
|
for( ; cit != finite_cells_end() ; ++cit){
|
|
if (classify(cit) == type) *it++ = Cell_handle(cit);
|
|
}
|
|
return it;
|
|
}
|
|
|
|
template<class OutputIterator>
|
|
OutputIterator get_alpha_shape_facets(OutputIterator it,
|
|
Classification_type type) const
|
|
{
|
|
Finite_facets_iterator fit = finite_facets_begin();
|
|
for( ; fit != finite_facets_end() ; ++fit){
|
|
if (classify(*fit) == type) *it++ = *fit;
|
|
}
|
|
return it;
|
|
}
|
|
|
|
template<class OutputIterator>
|
|
OutputIterator get_alpha_shape_edges(OutputIterator it,
|
|
Classification_type type) const
|
|
{
|
|
Finite_edges_iterator eit = finite_edges_begin();
|
|
for( ; eit != finite_edges_end() ; ++eit){
|
|
if (classify(*eit) == type) *it++ = *eit;
|
|
}
|
|
return it;
|
|
}
|
|
|
|
template<class OutputIterator>
|
|
OutputIterator get_alpha_shape_vertices(OutputIterator it,
|
|
Classification_type type) const
|
|
{
|
|
Finite_vertices_iterator vit = finite_vertices_begin();
|
|
for( ; vit != finite_vertices_end() ; ++vit){
|
|
if (classify(vit) == type) *it++ = Vertex_handle(vit);
|
|
}
|
|
return it;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
template < class Dt >
|
|
const typename Fixed_alpha_shape_3<Dt>::Classification_type Fixed_alpha_shape_3<Dt>::EXTERIOR;
|
|
template < class Dt >
|
|
const typename Fixed_alpha_shape_3<Dt>::Classification_type Fixed_alpha_shape_3<Dt>::REGULAR;
|
|
template < class Dt >
|
|
const typename Fixed_alpha_shape_3<Dt>::Classification_type Fixed_alpha_shape_3<Dt>::INTERIOR;
|
|
template < class Dt >
|
|
const typename Fixed_alpha_shape_3<Dt>::Classification_type Fixed_alpha_shape_3<Dt>::SINGULAR;
|
|
|
|
//---------------------------------------------------------------------
|
|
//--------------------- MEMBER FUNCTIONS-------------------------------
|
|
//---------------------------------------------------------------------
|
|
|
|
|
|
//--------------------- INITIALIZATION OF PRIVATE MEMBERS -------------
|
|
|
|
template <class Dt>
|
|
void Fixed_alpha_shape_3<Dt>::set_cell_status(Cell_handle c){
|
|
Classification_type status=is_infinite(c) ? EXTERIOR:( is_gabriel_simplex_in_alpha_complex(c) ? INTERIOR : EXTERIOR );
|
|
c->set_classification_type(status);
|
|
}
|
|
|
|
template <class Dt>
|
|
void
|
|
Fixed_alpha_shape_3<Dt>::initialize_status_of_cells()
|
|
{
|
|
Finite_cells_iterator cell_it, done = finite_cells_end();
|
|
for( cell_it = finite_cells_begin(); cell_it != done; ++cell_it) {
|
|
set_cell_status(cell_it);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
template < class Dt >
|
|
void
|
|
Fixed_alpha_shape_3<Dt>::
|
|
set_facet_classification_type( const Facet& f) {
|
|
Cell_handle pCell = f.first;
|
|
int i = f.second;
|
|
Cell_handle pNeighbor = pCell->neighbor(i);
|
|
int iNeigh = pNeighbor->index(pCell);
|
|
|
|
unsigned nb_interior_cells=0;
|
|
|
|
if(!is_infinite(pCell)){
|
|
if (pCell->get_classification_type()==INTERIOR)
|
|
++nb_interior_cells;
|
|
}
|
|
|
|
if(!is_infinite(pNeighbor)){
|
|
if (pNeighbor->get_classification_type()==INTERIOR)
|
|
++nb_interior_cells;
|
|
}
|
|
|
|
Classification_type status=EXTERIOR;
|
|
switch (nb_interior_cells){
|
|
case 2:
|
|
status=INTERIOR;
|
|
break;
|
|
case 1:
|
|
status=REGULAR;
|
|
break;
|
|
default:
|
|
{
|
|
if ( is_Gabriel(f) ){
|
|
if ( is_gabriel_simplex_in_alpha_complex(f) ) status=SINGULAR;
|
|
}
|
|
}
|
|
}
|
|
pCell->set_facet_classification_type(i,status);
|
|
pNeighbor->set_facet_classification_type(iNeigh,status);
|
|
}
|
|
|
|
|
|
template <class Dt>
|
|
void
|
|
Fixed_alpha_shape_3<Dt>::initialize_status_of_facets()
|
|
{
|
|
for(Finite_facets_iterator fit = finite_facets_begin();
|
|
fit != finite_facets_end(); ++fit)
|
|
set_facet_classification_type(*fit);
|
|
}
|
|
|
|
|
|
|
|
template < class Dt >
|
|
typename Fixed_alpha_shape_3<Dt>::Classification_type
|
|
Fixed_alpha_shape_3<Dt>::
|
|
compute_edge_status( const Cell_handle& c, int i, int j) const
|
|
{
|
|
Facet_circulator fcirc, done;
|
|
fcirc = incident_facets(c,i,j);
|
|
done = fcirc;
|
|
|
|
bool is_regular=false;
|
|
bool is_interior=true;
|
|
do{
|
|
if (!is_infinite(*fcirc)){
|
|
Classification_type status=(*fcirc).first->get_facet_classification_type((*fcirc).second);
|
|
if (status!=INTERIOR) is_interior=false;
|
|
if (status!=EXTERIOR)
|
|
is_regular=true;
|
|
else
|
|
if (is_regular)
|
|
break;
|
|
}
|
|
}while(++fcirc!=done);
|
|
if (is_interior) return INTERIOR;
|
|
if (is_regular) return REGULAR;
|
|
|
|
if ( is_Gabriel(c,i,j) ){
|
|
if ( is_gabriel_simplex_in_alpha_complex(c,i,j) ) return SINGULAR;
|
|
}
|
|
return EXTERIOR;
|
|
}
|
|
|
|
template <class Dt>
|
|
void
|
|
Fixed_alpha_shape_3<Dt>::initialize_status_of_edges()
|
|
{
|
|
for (Finite_edges_iterator eit = finite_edges_begin();
|
|
eit != finite_edges_end(); ++eit)
|
|
{
|
|
Classification_type status=compute_edge_status(eit->first, eit->second, eit->third);
|
|
//cross links
|
|
Vertex_handle_pair vhp = make_vertex_handle_pair( *eit );
|
|
edge_status_map.insert(std::make_pair(vhp, status));
|
|
}
|
|
}
|
|
|
|
|
|
//this function is only to use for update (removal/update)
|
|
template <class Dt>
|
|
void
|
|
Fixed_alpha_shape_3<Dt>::set_vertex_status(Vertex_handle v){
|
|
std::list<Vertex_handle> incidentv;
|
|
finite_adjacent_vertices(v,back_inserter(incidentv));
|
|
|
|
bool is_interior=true;
|
|
bool is_regular=false;
|
|
typename std::list<Vertex_handle>::iterator vvit=incidentv.begin();
|
|
for( ; vvit != incidentv.end(); ++vvit) {
|
|
Vertex_handle_pair vhp = make_vertex_handle_pair( *vvit, v);
|
|
Classification_type status=edge_status_map[vhp];
|
|
if (status!=INTERIOR) is_interior=false;
|
|
if (!is_interior && status!=EXTERIOR){
|
|
is_regular=true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Classification_type status=EXTERIOR;
|
|
if (is_interior)
|
|
status=v->is_on_chull() ? REGULAR : INTERIOR;
|
|
else{
|
|
if (is_regular)
|
|
status=REGULAR;
|
|
else{
|
|
if ( is_vertex_Gabriel(v,Weighted_tag()) ){
|
|
if ( is_gabriel_simplex_in_alpha_complex(v) ) status=SINGULAR;
|
|
}
|
|
}
|
|
}
|
|
v->set_classification_type(status);
|
|
}
|
|
|
|
template <class Dt>
|
|
void
|
|
Fixed_alpha_shape_3<Dt>::update_vertex_status(Vertex_handle v,Classification_type edge_status){
|
|
Classification_type status=v->get_classification_type();
|
|
switch(status){
|
|
case SINGULAR:
|
|
switch(edge_status){
|
|
case INTERIOR:
|
|
status=INTERIOR;
|
|
break;
|
|
case EXTERIOR:
|
|
status=EXTERIOR;
|
|
break;
|
|
case REGULAR:
|
|
case SINGULAR:
|
|
status=REGULAR;
|
|
}
|
|
break;
|
|
case INTERIOR:
|
|
switch(edge_status){
|
|
case INTERIOR:
|
|
break;
|
|
case EXTERIOR:
|
|
case REGULAR:
|
|
case SINGULAR:
|
|
status=REGULAR;
|
|
}
|
|
break;
|
|
case EXTERIOR:
|
|
switch(edge_status){
|
|
case EXTERIOR:
|
|
break;
|
|
case INTERIOR:
|
|
case REGULAR:
|
|
case SINGULAR:
|
|
status=REGULAR;
|
|
}
|
|
break;
|
|
case REGULAR:
|
|
break;
|
|
}
|
|
v->set_classification_type(status);
|
|
}
|
|
|
|
template <class Dt>
|
|
void
|
|
Fixed_alpha_shape_3<Dt>::finalize_status_of_vertex(Vertex_handle v)
|
|
{
|
|
Classification_type status=v->get_classification_type();
|
|
if (v->is_on_chull() && status==INTERIOR){
|
|
v->set_classification_type(REGULAR);
|
|
return;
|
|
}
|
|
if (status==INTERIOR || status==REGULAR)
|
|
return;
|
|
|
|
//when dimension is 3 any vertex has at least one incident edge,
|
|
// thus can't be SINGULAR again (because of update_vertex_status behavior)
|
|
CGAL_assertion(v->get_classification_type()==EXTERIOR);
|
|
|
|
if ( is_vertex_Gabriel(v,Weighted_tag()) && is_gabriel_simplex_in_alpha_complex(v) )
|
|
v->set_classification_type(SINGULAR);
|
|
}
|
|
|
|
template <class Dt>
|
|
void
|
|
Fixed_alpha_shape_3<Dt>::initialize_status_of_vertices()
|
|
{
|
|
#if 1 //approach avoiding extensive use of the map on 3hfli we move from 0.110983 to 0.082987
|
|
for( Finite_vertices_iterator vit = finite_vertices_begin(); vit != finite_vertices_end(); ++vit)
|
|
vit->set_classification_type(SINGULAR);
|
|
for (typename Edge_status_map::const_iterator eit=edge_status_map.begin();eit!=edge_status_map.end();++eit){
|
|
Classification_type edge_status=eit->second;
|
|
Vertex_handle_pair vhp=eit->first;
|
|
update_vertex_status(vhp.first,edge_status);
|
|
update_vertex_status(vhp.second,edge_status);
|
|
}
|
|
for( Finite_vertices_iterator vit = finite_vertices_begin(); vit != finite_vertices_end(); ++vit)
|
|
finalize_status_of_vertex(vit);
|
|
#else
|
|
//This method is slower because it always makes queries in the edge classification map
|
|
for( Finite_vertices_iterator vit = finite_vertices_begin();
|
|
vit != finite_vertices_end(); ++vit)
|
|
set_vertex_status(vit);
|
|
#endif
|
|
}
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
template <class Dt>
|
|
std::ostream& operator<<(std::ostream& os, const Fixed_alpha_shape_3<Dt>& A)
|
|
// Inserts the alpha shape into the stream `os' as an indexed face set.
|
|
// Precondition: The insert operator must be defined for `Point'
|
|
{
|
|
typedef Fixed_alpha_shape_3<Dt> AS;
|
|
typedef typename AS::Vertex_handle Vertex_handle;
|
|
typedef typename AS::Cell_handle Cell_handle;
|
|
typedef typename AS::Alpha_shape_vertices_iterator
|
|
Alpha_shape_vertices_iterator;
|
|
typedef typename AS::Alpha_shape_facets_iterator
|
|
Alpha_shape_facets_iterator;
|
|
|
|
Unique_hash_map< Vertex_handle, int > V;
|
|
int number_of_vertices = 0;
|
|
|
|
Alpha_shape_vertices_iterator vit;
|
|
for( vit = A.alpha_shape_vertices_begin();
|
|
vit != A.alpha_shape_vertices_end();
|
|
++vit) {
|
|
V[*vit] = number_of_vertices++;
|
|
os << (*vit)->point() << std::endl;
|
|
}
|
|
|
|
Cell_handle c;
|
|
int i;
|
|
Alpha_shape_facets_iterator fit;
|
|
for( fit = A.alpha_shape_facets_begin();
|
|
fit != A.alpha_shape_facets_end();
|
|
++fit) {
|
|
c = fit->first;
|
|
i = fit->second;
|
|
// the following ensures that regular facets are output
|
|
// in ccw order
|
|
if (A.classify(*fit) == AS::REGULAR && (A.classify(c) == AS::INTERIOR)){
|
|
c = c->neighbor(i);
|
|
i = c->index(fit->first);
|
|
}
|
|
int i0 = Triangulation_utils_3::vertex_triple_index(i,0);
|
|
int i1 = Triangulation_utils_3::vertex_triple_index(i,1);
|
|
int i2 = Triangulation_utils_3::vertex_triple_index(i,2);
|
|
os << V[c->vertex(i0)] << ' '
|
|
<< V[c->vertex(i1)] << ' '
|
|
<< V[c->vertex(i2)] << std::endl;
|
|
}
|
|
return os;
|
|
}
|
|
|
|
} //namespace CGAL
|
|
|
|
#endif //CGAL_FIXED_ALPHA_SHAPE_3_H
|