Merge remote-tracking branch 'cgal/master'

This commit is contained in:
Sébastien Loriot 2020-04-10 18:16:18 +02:00
commit d845d6ce8d
133 changed files with 17820 additions and 6356 deletions

View File

@ -47,6 +47,9 @@ not provide any guarantee on the topology of the surface.
We describe next the algorithm and provide examples.
\note A \ref tuto_reconstruction "detailed tutorial on surface reconstruction"
is provided with a guide to choose the most appropriate method along
with pre- and post-processing.
\section AFSR_Definitions Definitions and the Algorithm

View File

@ -653,6 +653,74 @@ can be done directly using `Face_filtered_graph`.
Using \ref BGLNamedParameters some of the many options of METIS can be customized,
as shown in \ref BGL_polyhedron_3/polyhedron_partition.cpp "this example".
\section BGLGraphcut Graph Cut
An optimal partition from a set of labels can be computed through a
graph cut approach called alpha expansion
\cgalCite{Boykov2001FastApproximate}. \cgal provides
`CGAL::alpha_expansion_graphcut()` which, for a graph \f$(V,E)\f$,
computes the partition `f` that minimizes the following cost function:
\f[
\mathrm{C}(f) = \sum_{\{v0,v1\} \in E} C_E(v0,v1) + \sum_{v \in V} C_V(f_v)
\f]
where \f$C_E(v0,v1)\f$ is the edge cost of assigning a different label
to \f$v0\f$ and \f$v1\f$, and \f$C_V(f_v)\f$ is the vertex cost of
assigning the label \f$f\f$ to the vertex \f$v\f$.
Three different implementations are provided and can be selected by
using one of the following tags:
- `CGAL::Alpha_expansion_boost_adjacency_list_tag` (default)
- `CGAL::Alpha_expansion_boost_compressed_sparse_raw_tag`
- `CGAL::Alpha_expansion_MaxFlow_tag`, released under GPL
license and provided by the \ref PkgSurfaceMeshSegmentationRef
package
All these implementations produce the exact same result but behave
differently in terms of timing and memory (see
\cgalFigureRef{alpha_exp}). The _MaxFlow_ implementation is the
fastest, but it grows rapidly in memory when increasing the complexity
of the input graph and labeling; the _compressed sparse raw_ (CSR) is very
efficient from a memory point of view but becomes very slow as the
complexity of the input graph and labeling increases; the _adjacency
list_ version provides a good compromise and is therefore the default
implementation.
\cgalFigureBegin{alpha_exp, alpha_expansion.png}
Comparison of time and memory consumed by the different alpha
expansion implementations.
\cgalFigureEnd
The following example shows how to apply the alpha expansion algorithm
to a `boost::adjacency_list` describing a 2D array with 3 labels "X",
" " and "O":
\cgalExample{BGL_graphcut/alpha_expansion_example.cpp}
The output of this program shows how the initial 2D array is
regularized spatially:
```
Input:
XOX
XX X O
OX OO
X OOX
OXOO
Alpha expansion...
Output:
XXX
XX O
XX OO
X OOO
OOOO
```
*/
} /* namespace CGAL */

View File

@ -15,7 +15,8 @@ INPUT += ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/Euler_operations.h \
${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/io.h \
${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/partition.h \
${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/METIS/partition_graph.h \
${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/METIS/partition_dual_graph.h
${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/METIS/partition_dual_graph.h \
${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/alpha_expansion_graphcut.h
EXAMPLE_PATH = ${CGAL_Surface_mesh_skeletonization_EXAMPLE_DIR} \

View File

@ -193,6 +193,11 @@ operation.\n
<b>Default:</b> None.
\cgalNPEnd
\cgalNPBegin{implementation_tag} \anchor BGL_implementation_tag
tag used to select the implementation to be used among an algorithm-specific list.\n
<b>Type:</b>a tag class\n
<b>Default:</b> algorithm-specific.
\cgalNPEnd
\cgalNPTableEnd

View File

@ -533,7 +533,8 @@ both in term of time and memory.
\addtogroup PkgBGLPartition
Methods to split a mesh into subdomains, using the library
<a href="http://glaros.dtc.umn.edu/gkhome/metis/metis/overview">METIS</a>.
<a href="http://glaros.dtc.umn.edu/gkhome/metis/metis/overview">METIS</a> or a graphcut
implementation.
*/
/*!
@ -715,11 +716,12 @@ user might encounter.
\cgalCRPSection{Partitioning Methods}
- `CGAL::METIS::partition_graph()`
- `CGAL::METIS::partition_dual_graph()`
- `CGAL::alpha_expansion_graphcut()`
\cgalCRPSection{I/O Functions}
- \link PkgBGLIOFct CGAL::read_off() \endlink
- \link PkgBGLIOFct CGAL::write_off() \endlink
- \link PkgBGLIOFct CGAL::write_wrl() \endlink
- \link PkgBGLIOFct `CGAL::read_off()` \endlink
- \link PkgBGLIOFct `CGAL::write_off()` \endlink
- \link PkgBGLIOFct `CGAL::write_wrl()` \endlink
- `CGAL::write_vtp()`
*/

View File

@ -3,6 +3,7 @@
\example BGL_arrangement_2/arr_rational_nt.h
\example BGL_arrangement_2/arrangement_dual.cpp
\example BGL_arrangement_2/primal.cpp
\example BGL_graphcut/alpha_expansion_example.cpp
\example BGL_polyhedron_3/copy_polyhedron.cpp
\example BGL_polyhedron_3/cube.off
\example BGL_polyhedron_3/distance.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,42 @@
# Created by the script cgal_create_CMakeLists
# This is the CMake script for compiling a set of CGAL applications.
cmake_minimum_required(VERSION 3.1...3.15)
project( BGL_graphcut_Examples )
# CGAL and its components
find_package( CGAL QUIET COMPONENTS )
if ( NOT CGAL_FOUND )
message(STATUS "This project requires the CGAL library, and will not be compiled.")
return()
endif()
# Boost and its components
find_package( Boost REQUIRED )
if ( NOT Boost_FOUND )
message(STATUS "This project requires the Boost library, and will not be compiled.")
return()
endif()
# include for local directory
# include for local package
# Creating entries for all C++ files with "main" routine
# ##########################################################
create_single_source_cgal_program( "alpha_expansion_example.cpp" )

View File

@ -0,0 +1,96 @@
#include <CGAL/boost/graph/alpha_expansion_graphcut.h>
#include <boost/graph/adjacency_list.hpp>
struct Vertex_property
{
int label;
std::vector<double> cost;
};
struct Edge_property
{
double weight;
};
using Graph = boost::adjacency_list <boost::setS,
boost::vecS,
boost::undirectedS,
Vertex_property,
Edge_property>;
using GT = boost::graph_traits<Graph>;
using vertex_descriptor = GT::vertex_descriptor;
using edge_descriptor = GT::edge_descriptor;
int main()
{
std::array<char, 3> labels = { 'X', ' ', 'O' };
std::array<std::array<int, 6>, 5> input
= { { { 0, 2, 0, 1, 1, 1 },
{ 0, 0, 1, 0, 1, 2 },
{ 2, 0, 1, 1, 2, 2 },
{ 0, 1, 1, 2, 2, 0 },
{ 1, 1, 2, 0, 2, 2 } } };
std::array<std::array<vertex_descriptor, 6>, 5> vertices;
// Init vertices from values
Graph g;
for (std::size_t i = 0; i < input.size(); ++ i)
for (std::size_t j = 0; j < input[i].size(); ++ j)
{
vertices[i][j] = boost::add_vertex(g);
g[vertices[i][j]].label = input[i][j];
// Cost of assigning this vertex to any label is positive except
// for current label which is 0 (favor init solution)
g[vertices[i][j]].cost.resize(3, 1);
g[vertices[i][j]].cost[std::size_t(input[i][j])] = 0;
}
// Display input values
std::cerr << "Input:" << std::endl;
for (std::size_t i = 0; i < vertices.size(); ++ i)
{
for (std::size_t j = 0; j < vertices[i].size(); ++ j)
std::cerr << labels[std::size_t(g[vertices[i][j]].label)];
std::cerr << std::endl;
}
// Init adjacency
double weight = 0.5;
for (std::size_t i = 0; i < vertices.size(); ++ i)
for (std::size_t j = 0; j < vertices[i].size(); ++ j)
{
// Neighbor vertices are connected
if (i < vertices.size() - 1)
{
edge_descriptor ed = boost::add_edge (vertices[i][j], vertices[i+1][j], g).first;
g[ed].weight = weight;
}
if (j < vertices[i].size() - 1)
{
edge_descriptor ed = boost::add_edge (vertices[i][j], vertices[i][j+1], g).first;
g[ed].weight = weight;
}
}
std::cerr << std::endl << "Alpha expansion..." << std::endl << std::endl;
CGAL::alpha_expansion_graphcut (g,
get (&Edge_property::weight, g),
get (&Vertex_property::cost, g),
get (&Vertex_property::label, g),
CGAL::parameters::vertex_index_map (get (boost::vertex_index, g)));
// Display output graph
std::cerr << "Output:" << std::endl;
for (std::size_t i = 0; i < vertices.size(); ++ i)
{
for (std::size_t j = 0; j < vertices[i].size(); ++ j)
std::cerr << labels[std::size_t(g[vertices[i][j]].label)];
std::cerr << std::endl;
}
return 0;
}

View File

@ -0,0 +1,748 @@
#ifndef CGAL_BOOST_GRAPH_ALPHA_EXPANSION_GRAPHCUT_H
// Copyright (c) 2014 GeometryFactory (France). All rights reserved.
//
// This file is part of CGAL (www.cgal.org)
//
// $URL$
// $Id$
// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s) : Ilker O. Yaz, Simon Giraudot
#define CGAL_BOOST_GRAPH_ALPHA_EXPANSION_GRAPHCUT_H
#include <CGAL/Iterator_range.h>
#include <CGAL/assertions.h>
#include <CGAL/property_map.h>
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
#include <CGAL/Timer.h>
#endif
#include <CGAL/IO/trace.h>
#include <CGAL/boost/graph/Named_function_parameters.h>
#include <CGAL/boost/graph/named_params_helper.h>
#include <boost/version.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/compressed_sparse_row_graph.hpp>
#if BOOST_VERSION >= 104400 // at this version kolmogorov_max_flow become depricated.
# include <boost/graph/boykov_kolmogorov_max_flow.hpp>
#else
# include <boost/graph/kolmogorov_max_flow.hpp>
#endif
#include <vector>
namespace CGAL
{
/// \cond SKIP_IN_MANUAL
namespace internal
{
struct Alpha_expansion_old_API_wrapper_graph
{
typedef std::size_t vertex_descriptor;
typedef std::size_t edge_descriptor;
typedef boost::directed_tag directed_category;
typedef boost::disallow_parallel_edge_tag edge_parallel_category;
typedef boost::edge_list_graph_tag traversal_category;
typedef boost::counting_iterator<std::size_t> counting_iterator;
typedef CGAL::Iterator_range<counting_iterator> counting_range;
typedef CGAL::Identity_property_map<std::size_t> Vertex_index_map;
typedef CGAL::Pointer_property_map<std::size_t>::type Vertex_label_map;
struct Vertex_label_cost_map
{
typedef std::size_t key_type;
typedef std::vector<double> value_type;
typedef value_type reference;
typedef boost::readable_property_map_tag category;
const std::vector<std::vector<double> >* cost_matrix;
Vertex_label_cost_map (const std::vector<std::vector<double> >* cost_matrix)
: cost_matrix (cost_matrix)
{ }
friend reference get (const Vertex_label_cost_map& pmap, key_type idx)
{
std::vector<double> out;
out.reserve (pmap.cost_matrix->size());
for (std::size_t i = 0; i < pmap.cost_matrix->size(); ++ i)
out.push_back ((*pmap.cost_matrix)[i][idx]);
return out;
}
};
typedef CGAL::Pointer_property_map<double>::const_type Edge_cost_map;
const std::vector<std::pair<std::size_t, std::size_t> >& edges;
const std::vector<double>& edge_costs;
const std::vector<std::vector<double> >& cost_matrix;
std::vector<std::size_t>& labels;
Alpha_expansion_old_API_wrapper_graph (const std::vector<std::pair<std::size_t, std::size_t> >& edges,
const std::vector<double>& edge_costs,
const std::vector<std::vector<double> >& cost_matrix,
std::vector<std::size_t>& labels)
: edges (edges), edge_costs (edge_costs), cost_matrix (cost_matrix), labels (labels)
{ }
friend counting_range vertices (const Alpha_expansion_old_API_wrapper_graph& graph)
{
return CGAL::make_range (boost::counting_iterator<std::size_t>(0),
boost::counting_iterator<std::size_t>(graph.labels.size()));
}
friend std::size_t num_vertices (const Alpha_expansion_old_API_wrapper_graph& graph) { return graph.labels.size(); }
friend counting_range edges (const Alpha_expansion_old_API_wrapper_graph& graph)
{
return CGAL::make_range (boost::counting_iterator<std::size_t>(0),
boost::counting_iterator<std::size_t>(graph.edges.size()));
}
friend vertex_descriptor source (edge_descriptor ed, const Alpha_expansion_old_API_wrapper_graph& graph)
{ return graph.edges[ed].first; }
friend vertex_descriptor target (edge_descriptor ed, const Alpha_expansion_old_API_wrapper_graph& graph)
{ return graph.edges[ed].second; }
Vertex_index_map vertex_index_map() const { return Vertex_index_map(); }
Vertex_label_map vertex_label_map() { return CGAL::make_property_map(labels); }
Vertex_label_cost_map vertex_label_cost_map() const
{ return Vertex_label_cost_map(&cost_matrix); }
Edge_cost_map edge_cost_map() const { return CGAL::make_property_map(edge_costs); }
};
////////////////////////////////////////////////////////////////////////////////////////
// Comments about performance:
//
// 1) With BGL:
// * Using adjacency_list:
// ** Without pre-allocating vertex-list
// | OutEdgeList | VertexList | Performance |
// | listS | listS | 25.2 |
// | vecS | listS | 22.7 |
// | listS | vecS | 30.7 |
// | vecS | vecS | 26.1 |
//
// ** With pre-allocating vertex-list with max-node size
// (Note: exact number of vertices are not certain at the beginning)
// | OutEdgeList | VertexList | Performance |
// | listS | vecS | 25.2 |
// | vecS | vecS | 23.4 |
//
// * Didn't try adjacency_matrix since our graph is sparse
// ( Also one can check BGL book, performance section )
//
// Decision:
// * Alpha_expansion_graph_cut_boost: use adjacency_list<vecS, listS> without
// pre-allocating vertex-list.
//
// 2) With Boykov-Kolmogorov MAXFLOW software:
// (http://pub.ist.ac.at/~vnk/software/maxflow-v2.21.src.tar.gz)
// | Performance |
// | 3.1 |
// * Alpha_expansion_graph_cut_boykov_kolmogorov provides an implementation.
// MAXFLOW does not provide any option for pre-allocation (It is possible with v_3.02 though).
//
// Typical Benchmark result provided by Ilker
// | construction of vertices | construction of edges | graph cut | Total
// -----------------------------------------------------------------------------------------------------------
// boost with an adjacency list | 1.53 | 1.51 | 3.00 | 6.04
// boost with CSR | 0.11 (gather in a vector) | 0.15 (gather in a vector) | 2.67 | 2.93
// MaxFlow | 0.042 | 0.076 | 1.043 | 1.161
//
// The main issue for now with CSR is the construction of the opposite edge map that is too costly,
// since it is done by exploring all edges to find opposite
////////////////////////////////////////////////////////////////////////////////////////
} // namespace internal
/**
* @brief Implements alpha-expansion graph cut algorithm.
*
* For representing graph, it uses adjacency_list with OutEdgeList = vecS, VertexList = listS.
* Also no pre-allocation is made for vertex-list.
*/
class Alpha_expansion_boost_adjacency_list_impl
{
private:
typedef boost::adjacency_list_traits<boost::vecS, boost::listS, boost::directedS>
Adjacency_list_traits;
typedef boost::adjacency_list<boost::vecS, boost::listS, boost::directedS,
// 4 vertex properties
boost::property<boost::vertex_index_t, std::size_t,
boost::property<boost::vertex_color_t, boost::default_color_type,
boost::property<boost::vertex_distance_t, double,
boost::property<boost::vertex_predecessor_t, Adjacency_list_traits::edge_descriptor >
> > >,
// 3 edge properties
boost::property<boost::edge_capacity_t, double,
boost::property<boost::edge_residual_capacity_t, double,
boost::property<boost::edge_reverse_t, Adjacency_list_traits::edge_descriptor> >
> > Graph;
typedef boost::graph_traits<Graph> Traits;
typedef boost::color_traits<boost::default_color_type> ColorTraits;
public:
typedef Traits::vertex_descriptor Vertex_descriptor;
typedef Traits::vertex_iterator Vertex_iterator;
typedef Traits::edge_descriptor Edge_descriptor;
typedef Traits::edge_iterator Edge_iterator;
private:
Graph graph;
Vertex_descriptor cluster_source;
Vertex_descriptor cluster_sink;
public:
void clear_graph()
{
graph.clear();
cluster_source = boost::add_vertex(graph);
cluster_sink = boost::add_vertex(graph);
}
Vertex_descriptor add_vertex()
{
return boost::add_vertex(graph);
}
void add_tweight (Vertex_descriptor& v, double w1, double w2)
{
add_edge (cluster_source, v, w1, 0);
add_edge (v, cluster_sink, w2, 0);
}
void init_vertices()
{
// initialize vertex indices, it is necessary since we are using VertexList = listS
Vertex_iterator v_begin, v_end;
Traits::vertices_size_type index = 0;
for(boost::tie(v_begin, v_end) = vertices(graph); v_begin != v_end; ++v_begin) {
boost::put(boost::vertex_index, graph, *v_begin, index++);
}
}
double max_flow()
{
#if BOOST_VERSION >= 104400
return boost::boykov_kolmogorov_max_flow(graph, cluster_source,
cluster_sink);
#else
return boost::kolmogorov_max_flow(graph, cluster_source, cluster_sink);
#endif
}
template <typename VertexLabelMap, typename InputVertexDescriptor>
void update(VertexLabelMap vertex_label_map,
const std::vector<Vertex_descriptor>& inserted_vertices,
InputVertexDescriptor vd,
std::size_t vertex_i,
std::size_t alpha)
{
boost::default_color_type color = boost::get(boost::vertex_color, graph,
inserted_vertices[vertex_i]);
if(std::size_t(get (vertex_label_map, vd)) != alpha
&& color == ColorTraits::white()) //new comers (expansion occurs)
put (vertex_label_map, vd,
static_cast<typename boost::property_traits<VertexLabelMap>::value_type>(alpha));
}
void add_edge (Vertex_descriptor& v1, Vertex_descriptor& v2, double w1, double w2)
{
Edge_descriptor v1_v2, v2_v1;
bool v1_v2_added, v2_v1_added;
boost::tie(v1_v2, v1_v2_added) = boost::add_edge(v1, v2, graph);
boost::tie(v2_v1, v2_v1_added) = boost::add_edge(v2, v1, graph);
CGAL_assertion(v1_v2_added && v2_v1_added);
//put edge capacities
boost::put(boost::edge_reverse, graph, v1_v2, v2_v1);
boost::put(boost::edge_reverse, graph, v2_v1, v1_v2);
//map reverse edges
boost::put(boost::edge_capacity, graph, v1_v2, w1);
boost::put(boost::edge_capacity, graph, v2_v1, w2);
}
};
// another implementation using compressed_sparse_row_graph
// for now there is a performance problem while setting reverse edges
// if that can be solved, it is faster than Alpha_expansion_graph_cut_boost
class Alpha_expansion_boost_compressed_sparse_row_impl
{
private:
// CSR only accepts bundled props
struct VertexP {
boost::default_color_type vertex_color;
double vertex_distance_t;
// ? do not now there is another way to take it, I think since edge_descriptor does not rely on properties
// this should be fine...
boost::compressed_sparse_row_graph<boost::directedS>::edge_descriptor
vertex_predecessor;
};
struct EdgeP {
double edge_capacity;
double edge_residual_capacity;
boost::compressed_sparse_row_graph<boost::directedS>::edge_descriptor
edge_reverse;
};
typedef boost::compressed_sparse_row_graph<boost::directedS,
VertexP, EdgeP> Graph;
typedef boost::graph_traits<Graph> Traits;
typedef boost::color_traits<boost::default_color_type> ColorTraits;
public:
typedef Traits::vertex_descriptor Vertex_descriptor;
typedef Traits::vertex_iterator Vertex_iterator;
typedef Traits::edge_descriptor Edge_descriptor;
typedef Traits::edge_iterator Edge_iterator;
private:
Graph graph;
std::size_t nb_vertices;
std::vector<std::pair<std::size_t, std::size_t> > edge_map;
std::vector<EdgeP> edge_map_weights;
public:
void clear_graph()
{
nb_vertices = 2;
edge_map.clear();
edge_map_weights.clear();
// edge_map.reserve(labels.size() *
// 8); // there is no way to know exact edge count, it is a heuristic value
// edge_map_weights.reserve(labels.size() * 8);
}
Vertex_descriptor add_vertex()
{
return (nb_vertices ++);
}
void add_tweight (Vertex_descriptor& v, double w1, double w2)
{
add_edge (0, v, w1, 0);
add_edge (v, 1, w2, 0);
}
void init_vertices()
{
#if BOOST_VERSION >= 104000
graph = Graph(boost::edges_are_unsorted, edge_map.begin(), edge_map.end(),
edge_map_weights.begin(), nb_vertices);
#else
graph= Graph(edge_map.begin(), edge_map.end(),
edge_map_weights.begin(), nb_vertices);
#endif
// PERFORMANCE PROBLEM
// need to set reverse edge map, I guess there is no way to do that before creating the graph
// since we do not have edge_descs
// however from our edge_map, we know that each (2i, 2i + 1) is reverse pairs, how to facilitate that ?
// will look it back
Graph::edge_iterator ei, ee;
for(boost::tie(ei, ee) = boost::edges(graph); ei != ee; ++ei) {
Graph::vertex_descriptor v1 = boost::source(*ei, graph);
Graph::vertex_descriptor v2 = boost::target(*ei, graph);
std::pair<Graph::edge_descriptor, bool> opp_edge = boost::edge(v2, v1, graph);
CGAL_assertion(opp_edge.second);
graph[opp_edge.first].edge_reverse =
*ei; // and edge_reverse of *ei will be (or already have been) set by the opp_edge
}
}
double max_flow()
{
#if BOOST_VERSION >= 104400
// since properties are bundled, defaults does not work need to specify them
return boost::boykov_kolmogorov_max_flow
(graph,
boost::get(&EdgeP::edge_capacity, graph),
boost::get(&EdgeP::edge_residual_capacity, graph),
boost::get(&EdgeP::edge_reverse, graph),
boost::get(&VertexP::vertex_predecessor, graph),
boost::get(&VertexP::vertex_color, graph),
boost::get(&VertexP::vertex_distance_t, graph),
boost::get(boost::vertex_index,
graph), // this is not bundled, get it from graph (CRS provides one)
0, 1);
#else
return boost::kolmogorov_max_flow
(graph,
boost::get(&EdgeP::edge_capacity, graph),
boost::get(&EdgeP::edge_residual_capacity, graph),
boost::get(&EdgeP::edge_reverse, graph),
boost::get(&VertexP::vertex_predecessor, graph),
boost::get(&VertexP::vertex_color, graph),
boost::get(&VertexP::vertex_distance_t, graph),
boost::get(boost::vertex_index,
graph), // this is not bundled, get it from graph
0, 1);
#endif
}
template <typename VertexLabelMap, typename InputVertexDescriptor>
void update(VertexLabelMap vertex_label_map,
const std::vector<Vertex_descriptor>&,
InputVertexDescriptor vd,
std::size_t vertex_i,
std::size_t alpha)
{
boost::default_color_type color = graph[vertex_i + 2].vertex_color;
if(get(vertex_label_map, vd)!= alpha
&& color == ColorTraits::white()) //new comers (expansion occurs)
put(vertex_label_map, vd, alpha);
}
void add_edge(Vertex_descriptor v1, Vertex_descriptor v2, double w1, double w2)
{
edge_map.push_back(std::make_pair(v1, v2));
EdgeP p1;
p1.edge_capacity = w1;
edge_map_weights.push_back(p1);
edge_map.push_back(std::make_pair(v2, v1));
EdgeP p2;
p2.edge_capacity = w2;
edge_map_weights.push_back(p2);
}
};
// tags
struct Alpha_expansion_boost_adjacency_list_tag { };
struct Alpha_expansion_boost_compressed_sparse_row_tag { };
struct Alpha_expansion_MaxFlow_tag { };
// forward declaration
class Alpha_expansion_MaxFlow_impl;
/// \endcond
// NOTE: latest performances check (2019-07-22)
//
// Using a random graph with 50000 vertices, 100000 edges and 30 labels:
//
// METHOD TIMING MEMORY
// Boost Adjacency list 49s 122MiB
// Boost CSR 187s 77MiB
// MaxFlow 12s 717MiB
/**
\ingroup PkgBGLPartition
regularizes a partition of a graph into `n` labels using the alpha
expansion algorithm \cgalCite{Boykov2001FastApproximate}.
For a graph \f$(V,E)\f$, this function computes a partition `f`
that minimizes the following cost function:
\f[
\mathrm{C}(f) = \sum_{\{v0,v1\} \in E} C_E(v0,v1) + \sum_{v \in V} C_V(f_v)
\f]
where \f$C_E(v0,v1)\f$ is the edge cost of assigning a different
label to \f$v0\f$ and \f$v1\f$, and \f$C_V(f_v)\f$ is the vertex
cost of assigning the label \f$f\f$ to the vertex \f$v\f$.
\tparam InputGraph a model of `VertexAndEdgeListGraph`
\tparam EdgeCostMap a model of `ReadablePropertyMap` with
`boost::graph_traits<InputGraph>::%edge_descriptor` as key and `double`
as value
\tparam VertexLabelCostMap a model of `ReadablePropertyMap`
with `boost::graph_traits<InputGraph>::%vertex_descriptor` as key and
`std::vector<double>` as value
\tparam VertexLabelMap a model of `ReadWritePropertyMap` with
`boost::graph_traits<InputGraph>::%vertex_descriptor` as key and
`std::size_t` as value
\tparam NamedParameters a sequence of named parameters
\param input_graph the input graph.
\param edge_cost_map a property map providing the weight of each
edge.
\param vertex_label_map a property map providing the label of each
vertex. This map will be updated by the algorithm with the
regularized version of the partition.
\param vertex_label_cost_map a property map providing, for each
vertex, an `std::vector` containing the cost of this vertex to
belong to each label. Each `std::vector` should have the same size
`n` (which is the number of labels), each label being indexed from
`0` to `n-1`. For example, `get(vertex_label_cost_map,
vd)[label_idx]` returns the cost of vertex `vd` to belong to the
label `label_idx`.
\param np optional sequence of named parameters among the ones listed below
\cgalNamedParamsBegin
\cgalParamBegin{vertex_index_map}
a property map providing the index of each vertex
\cgalParamEnd
\cgalParamBegin{implementation_tag}
tag used to select
which implementation of the alpha expansion should be
used. Available implementation tags are:
- `CGAL::Alpha_expansion_boost_adjacency_list` (default)
- `CGAL::Alpha_expansion_boost_compressed_sparse_row_tag`
- `CGAL::Alpha_expansion_MaxFlow_tag`
\cgalParamEnd
\cgalNamedParamsEnd
\note The `MaxFlow` implementation is provided by the \ref PkgSurfaceMeshSegmentationRef
under GPL license. The header
`<CGAL/boost/graph/Alpha_expansion_MaxFlow_tag.h>`
must be included if users want to use this implementation.
*/
template <typename InputGraph,
typename EdgeCostMap,
typename VertexLabelCostMap,
typename VertexLabelMap,
typename NamedParameters>
double alpha_expansion_graphcut (const InputGraph& input_graph,
EdgeCostMap edge_cost_map,
VertexLabelCostMap vertex_label_cost_map,
VertexLabelMap vertex_label_map,
const NamedParameters& np)
{
using parameters::choose_parameter;
using parameters::get_parameter;
typedef boost::graph_traits<InputGraph> GT;
typedef typename GT::edge_descriptor input_edge_descriptor;
typedef typename GT::vertex_descriptor input_vertex_descriptor;
typedef typename GetInitializedVertexIndexMap<InputGraph, NamedParameters>::type VertexIndexMap;
VertexIndexMap vertex_index_map = CGAL::get_initialized_vertex_index_map(input_graph, np);
typedef typename GetImplementationTag<NamedParameters>::type Impl_tag;
// select implementation
typedef typename std::conditional
<std::is_same<Impl_tag, Alpha_expansion_boost_adjacency_list_tag>::value,
Alpha_expansion_boost_adjacency_list_impl,
typename std::conditional
<std::is_same<Impl_tag, Alpha_expansion_boost_compressed_sparse_row_tag>::value,
Alpha_expansion_boost_compressed_sparse_row_impl,
Alpha_expansion_MaxFlow_impl>::type>::type
Alpha_expansion;
typedef typename Alpha_expansion::Vertex_descriptor Vertex_descriptor;
Alpha_expansion alpha_expansion;
// TODO: check this hardcoded parameter
const double tolerance = 1e-10;
double min_cut = (std::numeric_limits<double>::max)();
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
double vertex_creation_time, edge_creation_time, cut_time;
vertex_creation_time = edge_creation_time = cut_time = 0.0;
#endif
std::vector<Vertex_descriptor> inserted_vertices;
inserted_vertices.resize(num_vertices (input_graph));
std::size_t number_of_labels = get(vertex_label_cost_map, *(vertices(input_graph).first)).size();
bool success;
do {
success = false;
for (std::size_t alpha = 0; alpha < number_of_labels; ++ alpha)
{
alpha_expansion.clear_graph();
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
Timer timer;
timer.start();
#endif
// For E-Data
// add every input vertex as a vertex to the graph, put edges to source & sink vertices
for (input_vertex_descriptor vd : CGAL::make_range(vertices(input_graph)))
{
std::size_t vertex_i = get(vertex_index_map, vd);
Vertex_descriptor new_vertex = alpha_expansion.add_vertex();
inserted_vertices[vertex_i] = new_vertex;
double source_weight = get(vertex_label_cost_map, vd)[alpha];
// since it is expansion move, current alpha labeled vertices will be assigned to alpha again,
// making sink_weight 'infinity' guarantee this.
double sink_weight = (std::size_t(get(vertex_label_map, vd)) == alpha ?
(std::numeric_limits<double>::max)()
: get(vertex_label_cost_map, vd)[get(vertex_label_map, vd)]);
alpha_expansion.add_tweight(new_vertex, source_weight, sink_weight);
}
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
vertex_creation_time += timer.time();
timer.reset();
#endif
// For E-Smooth
// add edge between every vertex,
for (input_edge_descriptor ed : CGAL::make_range(edges(input_graph)))
{
input_vertex_descriptor vd1 = source(ed, input_graph);
input_vertex_descriptor vd2 = target(ed, input_graph);
std::size_t idx1 = get (vertex_index_map, vd1);
std::size_t idx2 = get (vertex_index_map, vd2);
double weight = get (edge_cost_map, ed);
Vertex_descriptor v1 = inserted_vertices[idx1],
v2 = inserted_vertices[idx2];
std::size_t label_1 = get (vertex_label_map, vd1);
std::size_t label_2 = get (vertex_label_map, vd2);
if(label_1 == label_2) {
if(label_1 != alpha) {
alpha_expansion.add_edge(v1, v2, weight, weight);
}
} else {
Vertex_descriptor inbetween = alpha_expansion.add_vertex();
double w1 = (label_1 == alpha) ? 0 : weight;
double w2 = (label_2 == alpha) ? 0 : weight;
alpha_expansion.add_edge(inbetween, v1, w1, w1);
alpha_expansion.add_edge(inbetween, v2, w2, w2);
alpha_expansion.add_tweight(inbetween, 0., weight);
}
}
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
edge_creation_time += timer.time();
#endif
alpha_expansion.init_vertices();
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
timer.reset();
#endif
double flow = alpha_expansion.max_flow();
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
cut_time += timer.time();
#endif
if(min_cut - flow <= flow * tolerance) {
continue;
}
min_cut = flow;
success = true;
//update labeling
for (input_vertex_descriptor vd : CGAL::make_range(vertices (input_graph)))
{
std::size_t vertex_i = get (vertex_index_map, vd);
alpha_expansion.update(vertex_label_map, inserted_vertices, vd, vertex_i, alpha);
}
}
} while(success);
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
CGAL_TRACE_STREAM << "vertex creation time: " << vertex_creation_time <<
std::endl;
CGAL_TRACE_STREAM << "edge creation time: " << edge_creation_time << std::endl;
CGAL_TRACE_STREAM << "max flow algorithm time: " << cut_time << std::endl;
#endif
return min_cut;
}
/// \cond SKIP_IN_MANUAL
// variant with default NP
template <typename InputGraph,
typename EdgeCostMap,
typename VertexLabelCostMap,
typename VertexLabelMap>
double alpha_expansion_graphcut (const InputGraph& input_graph,
EdgeCostMap edge_cost_map,
VertexLabelCostMap vertex_label_cost_map,
VertexLabelMap vertex_label_map)
{
return alpha_expansion_graphcut (input_graph, edge_cost_map,
vertex_label_cost_map, vertex_label_map,
CGAL::parameters::all_default());
}
// Old API
inline double alpha_expansion_graphcut (const std::vector<std::pair<std::size_t, std::size_t> >& edges,
const std::vector<double>& edge_costs,
const std::vector<std::vector<double> >& cost_matrix,
std::vector<std::size_t>& labels)
{
internal::Alpha_expansion_old_API_wrapper_graph graph (edges, edge_costs, cost_matrix, labels);
return alpha_expansion_graphcut(graph,
graph.edge_cost_map(),
graph.vertex_label_cost_map(),
graph.vertex_label_map(),
CGAL::parameters::vertex_index_map (graph.vertex_index_map()));
}
template <typename AlphaExpansionImplementationTag>
double alpha_expansion_graphcut (const std::vector<std::pair<std::size_t, std::size_t> >& edges,
const std::vector<double>& edge_costs,
const std::vector<std::vector<double> >& cost_matrix,
std::vector<std::size_t>& labels,
const AlphaExpansionImplementationTag&)
{
internal::Alpha_expansion_old_API_wrapper_graph graph (edges, edge_costs, cost_matrix, labels);
return alpha_expansion_graphcut(graph,
graph.edge_cost_map(),
graph.vertex_label_cost_map(),
graph.vertex_label_map(),
CGAL::parameters::vertex_index_map (graph.vertex_index_map()).
implementation_tag (AlphaExpansionImplementationTag()));
}
/// \endcond
}//namespace CGAL
namespace boost
{
template <>
struct property_map<CGAL::internal::Alpha_expansion_old_API_wrapper_graph, boost::vertex_index_t>
{
typedef CGAL::internal::Alpha_expansion_old_API_wrapper_graph::Vertex_index_map type;
typedef CGAL::internal::Alpha_expansion_old_API_wrapper_graph::Vertex_index_map const_type;
};
}
#endif //CGAL_BOOST_GRAPH_ALPHA_EXPANSION_GRAPHCUT_H

View File

@ -35,9 +35,11 @@ bool is_index_map_valid(IndexMap idmap,
Id_type max_id = static_cast<Id_type>(num_simplices);
std::vector<bool> indices(max_id);
for(const auto& d : range)
// According to concepts, the descriptor ranges such as 'vertices(g)' return a 'std::pair<it, it>'
for(auto it = range.first; it != range.second; ++it)
{
const Id_type id = get(idmap, d);
const Id_type id = get(idmap, *it);
if(id >= 0 && id < max_id && !indices[id])
{
indices[id] = true;

View File

@ -40,6 +40,7 @@ namespace CGAL {
class Default_diagonalize_traits;
class Eigen_svd;
class Lapack_svd;
struct Alpha_expansion_boost_adjacency_list_tag;
//
@ -550,6 +551,16 @@ CGAL_DEF_GET_INITIALIZED_INDEX_MAP(face, typename boost::graph_traits<Graph>::fa
> ::type type;
};
template<typename NamedParameters>
class GetImplementationTag
{
public:
typedef typename internal_np::Lookup_named_param_def <
internal_np::implementation_tag_t,
NamedParameters,
Alpha_expansion_boost_adjacency_list_tag
>::type type;
};
} //namespace CGAL

View File

@ -36,6 +36,7 @@ CGAL_add_named_parameter(face_to_face_output_iterator_t, face_to_face_output_ite
CGAL_add_named_parameter(vertex_to_vertex_map_t, vertex_to_vertex_map, vertex_to_vertex_map)
CGAL_add_named_parameter(halfedge_to_halfedge_map_t, halfedge_to_halfedge_map, halfedge_to_halfedge_map)
CGAL_add_named_parameter(face_to_face_map_t, face_to_face_map, face_to_face_map)
CGAL_add_named_parameter(implementation_tag_t, implementation_tag, implementation_tag)
// List of named parameters that we use in the package 'Mesh_3'
CGAL_add_named_parameter(vertex_feature_degree_t, vertex_feature_degree, vertex_feature_degree_map)
@ -135,6 +136,7 @@ CGAL_add_named_parameter(plane_index_t, plane_index_map, plane_index_map)
CGAL_add_named_parameter(select_percentage_t, select_percentage, select_percentage)
CGAL_add_named_parameter(require_uniform_sampling_t, require_uniform_sampling, require_uniform_sampling)
CGAL_add_named_parameter(point_is_constrained_t, point_is_constrained, point_is_constrained_map)
CGAL_add_named_parameter(maximum_number_of_faces_t, maximum_number_of_faces, maximum_number_of_faces)
CGAL_add_named_parameter(transformation_t, transformation, transformation)
CGAL_add_named_parameter(point_set_filters_t, point_set_filters, point_set_filters)
CGAL_add_named_parameter(matcher_t, matcher, matcher)

View File

@ -652,4 +652,3 @@ bool is_selection_a_topological_disk(const FaceRange& face_selection,
} //end of namespace CGAL
#endif //CGAL_BOOST_GRAPH_SELECTION_H

View File

@ -192,6 +192,7 @@ void duplicate_terminal_vertices(Graph& graph,
} // namespace internal
#ifndef DOXYGEN_RUNNING
template <typename Graph,
typename Visitor,
typename IsTerminal,
@ -201,6 +202,7 @@ split_graph_into_polylines(const Graph& graph,
Visitor& polyline_visitor,
IsTerminal is_terminal,
LessForVertexDescriptors less);
#endif
/*!
\ingroup PkgBGLRef

View File

@ -227,7 +227,8 @@ Triangulation_hierarchy_2 t2h_data() { return build_dummy_triangulation_with_ids
template <typename Graph>
struct Surface_fixture_1 {
Surface_fixture_1() {
assert(read_a_mesh(m, "data/fixture1.off"));
const bool is_reading_successful = read_a_mesh(m, "data/fixture1.off");
assert(is_reading_successful);
assert(CGAL::is_valid_polygon_mesh(m));
typename boost::property_map<Graph, CGAL::vertex_point_t>::const_type
pm = get(CGAL::vertex_point, const_cast<const Graph&>(m));
@ -276,7 +277,8 @@ struct Surface_fixture_1 {
template <typename Graph>
struct Surface_fixture_2 {
Surface_fixture_2() {
assert(read_a_mesh(m, "data/fixture2.off"));
const bool is_reading_successful = read_a_mesh(m, "data/fixture2.off");
assert(is_reading_successful);
assert(CGAL::is_valid_polygon_mesh(m));
typename boost::property_map<Graph, CGAL::vertex_point_t>::const_type
@ -337,7 +339,8 @@ struct Surface_fixture_2 {
template <typename Graph>
struct Surface_fixture_3 {
Surface_fixture_3() {
assert(read_a_mesh(m, "data/fixture3.off"));
const bool is_reading_successful = read_a_mesh(m, "data/fixture3.off");
assert(is_reading_successful);
assert(CGAL::is_valid_polygon_mesh(m));
typename boost::property_map<Graph, CGAL::vertex_point_t>::const_type
@ -383,7 +386,8 @@ struct Surface_fixture_3 {
template <typename Graph>
struct Surface_fixture_4 {
Surface_fixture_4() {
assert(read_a_mesh(m, "data/fixture4.off"));
const bool is_reading_successful = read_a_mesh(m, "data/fixture4.off");
assert(is_reading_successful);
assert(CGAL::is_valid_polygon_mesh(m));
typename boost::property_map<Graph, CGAL::vertex_point_t>::const_type
@ -418,7 +422,8 @@ struct Surface_fixture_4 {
template <typename Graph>
struct Surface_fixture_5 {
Surface_fixture_5() {
assert(read_a_mesh(m, "data/add_face_to_border.off"));
const bool is_reading_successful = read_a_mesh(m, "data/add_face_to_border.off");
assert(is_reading_successful);
assert(CGAL::is_valid_polygon_mesh(m));
typename boost::property_map<Graph, CGAL::vertex_point_t>::const_type
@ -448,7 +453,8 @@ struct Surface_fixture_5 {
template <typename Graph>
struct Surface_fixture_6 {
Surface_fixture_6() {
assert(read_a_mesh(m, "data/quad.off"));
const bool is_reading_successful = read_a_mesh(m, "data/quad.off");
assert(is_reading_successful);
assert(CGAL::is_valid_polygon_mesh(m));
typename boost::graph_traits<Graph>::halfedge_descriptor h;
@ -467,7 +473,8 @@ struct Surface_fixture_6 {
template <typename Graph>
struct Surface_fixture_7 {
Surface_fixture_7() {
assert(read_a_mesh(m, "data/cube.off"));
const bool is_reading_successful = read_a_mesh(m, "data/cube.off");
assert(is_reading_successful);
assert(CGAL::is_valid_polygon_mesh(m));
h = *(halfedges(m).first);
@ -480,7 +487,8 @@ struct Surface_fixture_7 {
template <typename Graph>
struct Surface_fixture_8 {
Surface_fixture_8() {
assert(read_a_mesh(m, "data/fixture5.off"));
const bool is_reading_successful = read_a_mesh(m, "data/fixture5.off");
assert(is_reading_successful);
assert(CGAL::is_valid_polygon_mesh(m));
typename boost::property_map<Graph, CGAL::vertex_point_t>::const_type

View File

@ -96,6 +96,7 @@ void test(const NamedParameters& np)
assert(get_parameter(np, CGAL::internal_np::dry_run).v == 59);
assert(get_parameter(np, CGAL::internal_np::do_lock_mesh).v == 60);
assert(get_parameter(np, CGAL::internal_np::do_simplify_border).v == 61);
assert(get_parameter(np, CGAL::internal_np::maximum_number_of_faces).v == 78910);
// Named parameters that we use in the package 'Surface Mesh Simplification'
assert(get_parameter(np, CGAL::internal_np::get_cost_policy).v == 34);
@ -198,6 +199,7 @@ void test(const NamedParameters& np)
check_same_type<59>(get_parameter(np, CGAL::internal_np::dry_run));
check_same_type<60>(get_parameter(np, CGAL::internal_np::do_lock_mesh));
check_same_type<61>(get_parameter(np, CGAL::internal_np::do_simplify_border));
check_same_type<78910>(get_parameter(np, CGAL::internal_np::maximum_number_of_faces));
// Named parameters that we use in the package 'Surface Mesh Simplification'
check_same_type<34>(get_parameter(np, CGAL::internal_np::get_cost_policy));
@ -376,6 +378,7 @@ int main()
.inspector(A<9032>(9032))
.logger(A<9033>(9033))
.maximum_normal_deviation(A<9034>(9034))
.maximum_number_of_faces(A<78910>(78910))
);
return EXIT_SUCCESS;
}

View File

@ -526,6 +526,39 @@ namespace CartesianKernelFunctors {
}
};
template <typename K>
class Compare_signed_distance_to_line_2
{
typedef typename K::Point_2 Point_2;
typedef typename K::Line_2 Line_2;
typedef typename K::Equal_2 Equal_2;
typedef typename K::Less_signed_distance_to_line_2 Less_signed_distance_to_line_2;
public:
typedef typename K::Comparison_result result_type;
result_type
operator()(const Point_2& a, const Point_2& b,
const Point_2& c, const Point_2& d) const
{
CGAL_kernel_precondition_code(Equal_2 equal;)
CGAL_kernel_precondition(! equal(a,b));
return cmp_signed_dist_to_lineC2( a.x(), a.y(),
b.x(), b.y(),
c.x(), c.y(),
d.x(), d.y());
}
result_type
operator()(const Line_2& l, const Point_2& p, const Point_2& q) const
{
Less_signed_distance_to_line_2 less = K().less_signed_distance_to_line_2_object();
if (less(l, p, q)) return SMALLER;
if (less(l, q, p)) return LARGER;
return EQUAL;
}
};
template <typename K>
class Compare_squared_radius_3
{
@ -3884,7 +3917,7 @@ namespace CartesianKernelFunctors {
}
};
// TODO ...
template <typename K>
class Less_signed_distance_to_line_2
{

View File

@ -21,7 +21,7 @@ This component implements the algorithm described in \cgalCite{cgal:lm-clscm-12}
- a set of labels (for example: _ground_, _building_, _vegetation_) is defined by the user;
- a classifier is defined and trained: from the set of values taken by the features at an input item, it measures the likelihood of this item to belong to one label or another;
- classification is computed itemwise using the classifier;
- additional regularization can be used by smoothing either locally or globally through a _Graph Cut_ \cgalCite{Boykov2001FastApproximate} approach.
- additional regularization can be used by smoothing either locally or globally through a _graph cut_ \cgalCite{Boykov2001FastApproximate} approach.
\cgalFigureBegin{Classification_organization_fig,organization.svg}
Organization of the package.
@ -431,7 +431,7 @@ quantifies the strengh of the regularization, \f$i \sim j\f$
represents the pairs of neighboring items and
\f$\mathbf{1}_{\{.\}}\f$ the characteristic function.
A _Graph Cut_ based algorithm (Alpha Expansion) is used to quickly reach
A _graph cut_ based algorithm (alpha expansion) is used to quickly reach
an approximate solution close to the global optimum of this energy.
This method allows to consistently segment the input data set in

View File

@ -15,7 +15,7 @@
#include <CGAL/license/Classification.h>
#include <CGAL/internal/Surface_mesh_segmentation/Alpha_expansion_graph_cut.h>
#include <CGAL/boost/graph/alpha_expansion_graphcut.h>
#include <CGAL/Bbox_3.h>
#include <CGAL/Classification/Label_set.h>
#include <CGAL/property_map.h>
@ -234,12 +234,6 @@ namespace internal {
const std::vector<std::pair<std::size_t, std::size_t> >& m_input_to_indices;
LabelIndexRange& m_out;
#ifdef CGAL_DO_NOT_USE_BOYKOV_KOLMOGOROV_MAXFLOW_SOFTWARE
typedef CGAL::internal::Alpha_expansion_graph_cut_boost Alpha_expansion;
#else
typedef CGAL::internal::Alpha_expansion_graph_cut_boykov_kolmogorov Alpha_expansion;
#endif
public:
Classify_functor_graphcut (const ItemRange& input,
@ -310,8 +304,7 @@ namespace internal {
assigned_label[j] = nb_class_best;
}
Alpha_expansion graphcut;
graphcut(edges, edge_weights, probability_matrix, assigned_label);
CGAL::alpha_expansion_graphcut (edges, edge_weights, probability_matrix, assigned_label);
for (std::size_t i = 0; i < assigned_label.size(); ++ i)
m_out[m_indices[sub][i]] = static_cast<typename LabelIndexRange::iterator::value_type>(assigned_label[i]);

View File

@ -20,4 +20,3 @@ STL_Extension
Solver_interface
Spatial_searching
Stream_support
Surface_mesh_segmentation

View File

@ -188,7 +188,108 @@ namespace internal {
typename K::FT
squared_distance(const typename K::Segment_2 &seg1,
const typename K::Segment_2 &seg2,
const K& k)
const K& k,
const Cartesian_tag&)
{
typedef typename K::RT RT;
typedef typename K::FT FT;
bool crossing1, crossing2;
RT c1s, c1e, c2s, c2e;
if (seg1.source() == seg1.target())
return internal::squared_distance(seg1.source(), seg2, k);
if (seg2.source() == seg2.target())
return internal::squared_distance(seg2.source(), seg1, k);
Orientation o1s = orientation(seg2.source(), seg2.target(), seg1.source());
Orientation o1e = orientation(seg2.source(), seg2.target(), seg1.target());
if (o1s == RIGHT_TURN) {
crossing1 = (o1e != RIGHT_TURN);
} else {
if (o1e != LEFT_TURN) {
if (o1s == COLLINEAR && o1e == COLLINEAR)
return internal::squared_distance_parallel(seg1, seg2, k);
crossing1 = true;
} else {
crossing1 = (o1s == COLLINEAR);
}
}
Orientation o2s = orientation(seg1.source(), seg1.target(), seg2.source());
Orientation o2e = orientation(seg1.source(), seg1.target(), seg2.target());
if (o2s == RIGHT_TURN) {
crossing2 = (o2e != RIGHT_TURN);
} else {
if (o2e != LEFT_TURN) {
if (o2s == COLLINEAR && o2e == COLLINEAR)
return internal::squared_distance_parallel(seg1, seg2, k);
crossing2 = true;
} else {
crossing2 = (o2s == COLLINEAR);
}
}
if (crossing1) {
if (crossing2)
return (FT)0;
c2s = CGAL::abs(wcross(seg1.source(), seg1.target(), seg2.source(), k));
c2e = CGAL::abs(wcross(seg1.source(), seg1.target(), seg2.target(), k));
Comparison_result dm = compare(c2s,c2e);
if (dm == SMALLER) {
return internal::squared_distance(seg2.source(), seg1, k);
} else {
if (dm == LARGER) {
return internal::squared_distance(seg2.target(), seg1, k);
} else {
// parallel, should not happen (no crossing)
return internal::squared_distance_parallel(seg1, seg2, k);
}
}
} else {
c1s = CGAL::abs(wcross(seg2.source(), seg2.target(), seg1.source(), k));
c1e = CGAL::abs(wcross(seg2.source(), seg2.target(), seg1.target(), k));
Comparison_result dm = compare(c1s,c1e);
if (crossing2) {
if (dm == SMALLER) {
return internal::squared_distance(seg1.source(), seg2, k);
} else {
if (dm == LARGER) {
return internal::squared_distance(seg1.target(), seg2, k);
} else {
// parallel, should not happen (no crossing)
return internal::squared_distance_parallel(seg1, seg2, k);
}
}
} else {
FT min1, min2;
if (dm == EQUAL)
return internal::squared_distance_parallel(seg1, seg2, k);
min1 = (dm == SMALLER) ?
internal::squared_distance(seg1.source(), seg2, k):
internal::squared_distance(seg1.target(), seg2, k);
c2s = CGAL::abs(wcross(seg1.source(), seg1.target(), seg2.source(), k));
c2e = CGAL::abs(wcross(seg1.source(), seg1.target(), seg2.target(), k));
dm = compare(c2s,c2e);
if (dm == EQUAL) // should not happen.
return internal::squared_distance_parallel(seg1, seg2, k);
min2 = (dm == SMALLER) ?
internal::squared_distance(seg2.source(), seg1, k):
internal::squared_distance(seg2.target(), seg1, k);
return (min1 < min2) ? min1 : min2;
}
}
}
template <class K>
typename K::FT
squared_distance(const typename K::Segment_2 &seg1,
const typename K::Segment_2 &seg2,
const K& k,
const Homogeneous_tag&)
{
typedef typename K::RT RT;
typedef typename K::FT FT;
@ -277,6 +378,7 @@ namespace internal {
}
}
template <class K>
inline typename K::RT
_distance_measure_sub(const typename K::RT &startwcross,
@ -690,7 +792,8 @@ template <class K>
inline typename K::FT
squared_distance(const Segment_2<K> &seg1, const Segment_2<K> &seg2)
{
return internal::squared_distance(seg1, seg2, K());
typedef typename K::Kernel_tag Tag;
return internal::squared_distance(seg1, seg2, K(), Tag());
}
template <class K>

View File

@ -0,0 +1,34 @@
#include <iostream>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Exact_rational.h>
#include <CGAL/Simple_cartesian.h>
typedef CGAL::Simple_cartesian<double> SC;
typedef CGAL::Simple_cartesian<CGAL::Exact_rational> EC;
template <typename Kernel>
double fct() {
typedef typename Kernel::Segment_2 Segment_2;
const Segment_2 segi = {
{ -4.0380854964382, -1.9947196614192 },
{ 10.43442091460618, -0.5886833953492263 } };
const Segment_2 segj = {
{ -11.5138934277993, -2.721011070186227 },
{ -8.822747585009402, -2.459560251317805 } };
const auto dist = CGAL::squared_distance(segi, segj);
std::cout << "#dist: " << dist << std::endl;
return CGAL::to_double(dist);
}
int main()
{
auto approx_dist = fct<SC>();
fct<CGAL::Epick>();
auto exact_dist = fct<EC>();
assert(CGAL::abs(approx_dist - exact_dist) < 0.05 * CGAL::abs(exact_dist));
return 0;
}

View File

@ -11,7 +11,8 @@ HTML_HEADER = ${CGAL_DOC_HEADER}
LAYOUT_FILE = ${CGAL_DOC_RESOURCE_DIR}/DoxygenLayout.xml
GENERATE_TAGFILE = ${CGAL_DOC_TAG_GEN_DIR}/Manual.tag
EXAMPLE_PATH = ${CGAL_Convex_hull_2_EXAMPLE_DIR} \
${CGAL_Kernel_23_EXAMPLE_DIR}
${CGAL_Kernel_23_EXAMPLE_DIR} \
${CGAL_Poisson_surface_reconstruction_3_EXAMPLE_DIR}
FILTER_PATTERNS = *.txt=${CMAKE_BINARY_DIR}/pkglist_filter
HTML_EXTRA_FILES += ${CGAL_DOC_RESOURCE_DIR}/hacks.js \

View File

@ -0,0 +1,295 @@
namespace CGAL {
/*!
\example Poisson_surface_reconstruction_3/tutorial_example.cpp
*/
/*!
\page tuto_reconstruction Surface Reconstruction from Point Clouds
\cgalAutoToc
\author Simon Giraudot
Surface reconstruction from point clouds is a core topic in geometry
processing \cgalCite{cgal:btsag-asosr-16}. It is an ill-posed problem:
there is an infinite number of surfaces that approximate a single
point cloud and a point cloud does not define a surface in
itself. Thus additional assumptions and constraints must be defined by
the user and reconstruction can be achieved in many different
ways. This tutorial provides guidance on how to use the different
algorithms of \cgal to effectively perform surface reconstruction.
\section TutorialsReconstruction_algorithms Which algorithm should I use?
\cgal offers three different algorithms for surface reconstruction:
- \ref Chapter_Poisson_Surface_Reconstruction "Poisson Surface Reconstruction"
- \ref Chapter_Advancing_Front_Surface_Reconstruction "Advancing Front Surface Reconstruction"
- \ref Chapter_Scale_space_reconstruction "Scale Space Surface Reconstruction"
Because reconstruction is an ill-posed problem, it must be regularized
via prior knowledge. Differences in prior lead to different
algorithms, and choosing one or the other of these methods is
dependent on these priors. For example, Poisson always generates
closed shapes (bounding a volume) and requires normals but does not
interpolate input points (the output surface does not pass exactly
through the input points). The following table lists different
properties of the input and output to help the user choose the method
best suited to each problem:
<center>
| | Poisson | Advancing front | Scale space |
|------------------------------------------|:-------:|:----------------:|:----------------:|
| Are normals required? | Yes | No | No |
| Is noise handled? | Yes | By preprocessing | Yes |
| Is variable sampling handled? | Yes | Yes | By preprocessing |
| Are input points exactly on the surface? | No | Yes | Yes |
| Is the output always closed? | Yes | No | No |
| Is the output always smooth? | Yes | No | No |
| Is the output always manifold? | Yes | Yes | Optional |
| Is the output always orientable? | Yes | Yes | Optional |
</center>
\cgalFigureBegin{TutorialsReconstructionFigComparisons, compare_reconstructions.png}
Comparison of reconstruction methods applied to the same input (full shape and close-up). From left to right: original point cloud; Poisson; advancing front; scale space.
\cgalFigureEnd
More information on these different methods can be found on their
respective manual pages and in Section \ref TutorialsReconstruction_reconstruction.
\section TutorialsReconstruction_overview Pipeline Overview
This tutorial aims at providing a more comprehensive view of the
possibilities offered by \cgal for dealing with point clouds, for
surface reconstruction purposes. The following diagram shows an
overview (not exhaustive) of common reconstruction steps using \cgal
tools.
\cgalFigureBegin{TutorialsReconstructionFigPipeline, reconstruction.svg}
Pipeline Overview
\cgalFigureEnd
We now review some of these steps in more detail.
\section TutorialsReconstruction_input Reading Input
The reconstruction algorithms in \cgal take a range of iterators on a
container as input and use property maps to access the points (and the
normals when they are required). Points are typically stored in plain
text format (denoted as 'XYZ' format) where each point is separated by
a newline character and each coordinate separated by a white
space. Other formats available are 'OFF', 'PLY' and 'LAS'. \cgal
provides functions to read such formats:
- `read_xyz_points()`
- `read_off_points()`
- `read_ply_points()`
- `read_ply_points_with_properties()` to read additional PLY properties
- `read_las_points()`
- `read_las_points_with_properties()` to read additional LAS properties
\cgal also provides a dedicated container `CGAL::Point_set_3` to
handle point sets with additional properties such as normal
vectors. In this case, property maps are easily handled as shown in
the following sections. This structure also handles the stream
operator to read point sets in any of the formats previously
described. Using this method yields substantially shorter code, as can
be seen on the following example:
\snippet Poisson_surface_reconstruction_3/tutorial_example.cpp Reading input
\section TutorialsReconstruction_preprocessing Preprocessing
Because reconstruction algorithms have some specific requirements that
point clouds do not always meet, some preprocessing might be necessary
to yield the best results.
Note that this _preprocessing_ step is optional: when the input point
cloud has no imperfections, reconstruction can be applied to it
without any preprocessing.
\cgalFigureBegin{TutorialsReconstructionFigPreprocessing, reconstruction_preproc.png}
Comparison of advancing front reconstruction output using different
preprocessing on the same input. The smooth point cloud was generated
using _jet smoothing_; the simplified point cloud was generated using
_grid simplification_.
\cgalFigureEnd
\subsection TutorialsReconstruction_preprocessing_outliers Outlier removal
Some acquisition techniques generate points which are far away from
the surface. These points, commonly referred to as "outliers", have no
relevance for reconstruction. Using the \cgal reconstruction
algorithms on outlier-ridden point clouds produce overly distorted
output, it is therefore strongly advised to filter these outliers
_before_ performing reconstruction.
\snippet Poisson_surface_reconstruction_3/tutorial_example.cpp Outlier removal
\subsection TutorialsReconstruction_preprocessing_simplification Simplification
Some laser scanners generate points with widely variable
sampling. Typically, lines of scan are very densely sampled but the
gap between two lines of scan is much larger, leading to an overly
massive point cloud with large variations of sampling density. This
type of input point cloud might generate imperfect output using
algorithms which, in general, only handle small variations of sampling
density.
\cgal provides several simplification algorithms. In addition to
reducing the size of the input point cloud and therefore decreasing
computation time, some of them can help making the input more
uniform. This is the case of the function `grid_simplify_point_set()`
which defines a grid of a user-specified size and keeps one point per
occupied cell.
\snippet Poisson_surface_reconstruction_3/tutorial_example.cpp Simplification
\subsection TutorialsReconstruction_preprocessing_smoothing Smoothing
Although reconstructions via 'Poisson' or 'Scale space' handle noise
internally, one may want to get tighter control over the smoothing
step. For example, a slightly noisy point cloud can benefit from some
reliable smoothing algorithms and be reconstructed via 'Advancing
front' which provides relevant properties (oriented mesh with
boundaries).
Two functions are provided to smooth a noisy point cloud with a good
approximation (i.e. without degrading curvature, for example):
- `jet_smooth_point_set()`
- `bilateral_smooth_point_set()`
These functions directly modify the container:
\snippet Poisson_surface_reconstruction_3/tutorial_example.cpp Smoothing
\subsection TutorialsReconstruction_preprocessing_normal Normal Estimation and Orientation
\ref Chapter_Poisson_Surface_Reconstruction "Poisson Surface Reconstruction"
requires points with oriented normal vectors. To apply the algorithm
to a raw point cloud, normals must be estimated first, for example
with one of these two functions:
- `pca_estimate_normals()`
- `jet_estimate_normals()`
PCA is faster but jet is more accurate in the presence of high
curvatures. These function only estimates the _direction_ of the
normals, not their orientation (the orientation of the vectors might
not be locally consistent). To properly orient the normals, the
function `mst_orient_normals()` can be used. Notice that it can also
be used directly on input normals if their orientation is not
consistent.
\snippet Poisson_surface_reconstruction_3/tutorial_example.cpp Normal estimation
\section TutorialsReconstruction_reconstruction Reconstruction
\subsection TutorialsReconstruction_reconstruction_poisson Poisson
Poisson reconstruction consists in computing an implicit function
whose gradient matches the input normal vector field: this indicator
function has opposite signs inside and outside of the inferred shape
(hence the need for closed shapes). This method thus requires normals
and produces smooth closed surfaces. It is not appropriate if the
surface is expected to interpolate the input points. On the contrary,
it performs well if the aim is to approximate a noisy point cloud with
a smooth surface.
\snippet Poisson_surface_reconstruction_3/tutorial_example.cpp Poisson reconstruction
\subsection TutorialsReconstruction_reconstruction_advancing Advancing Front
Advancing front is a Delaunay-based approach which interpolates a
subset of the input points. It generates triples of point indices
which describe the triangular facets of the reconstruction: it uses a
priority queue to sequentially pick the Delaunay facet the most likely
to be part of the surface, based on a size criterion (to favor the
small facets) and an angle criterion (to favor smoothness). Its main
virtue is to generate oriented manifold surfaces with boundaries:
contrary to Poisson, it does not require normals and is not bound to
reconstruct closed shapes. However, it requires preprocessing if the
point cloud is noisy.
The \ref Chapter_Advancing_Front_Surface_Reconstruction "Advancing Front"
package provides several ways of constructing the function. Here is
a simple example:
\snippet Poisson_surface_reconstruction_3/tutorial_example.cpp Advancing front reconstruction
\subsection TutorialsReconstruction_reconstruction_scale_space Scale Space
Scale space reconstruction aims at producing a surface which
interpolates the input points (interpolant) while offering some
robustness to noise. More specifically, it first applies several times
a smoothing filter (such as Jet Smoothing) to the input point set to
produce a scale space; then, the smoothest scale is meshed (using for
example the Advancing Front mesher); finally, the resulting
connectivity between smoothed points is propagated to the original raw
input point set. This method is the right choice if the input point
cloud is noisy but the user still wants the surface to pass exactly
through the points.
\snippet Poisson_surface_reconstruction_3/tutorial_example.cpp Scale space reconstruction
\section TutorialsReconstruction_postprocessing Output and Postprocessing
Each of these methods produce a triangle mesh stored in different
ways. If this output mesh is hampered by defects such as holes or
self-intersections, \cgal provide several algorithms to post-process
it (hole filling, remeshing, etc.) in the package \ref
Chapter_PolygonMeshProcessing "Polygon Mesh Processing".
We do not discuss these functions here as there are many
postprocessing possibilities whose relevance strongly depends on the
user's expectations on the output mesh.
The mesh (postprocessed or not) can easily be saved in the PLY format
(here, using the binary variant):
\snippet Poisson_surface_reconstruction_3/tutorial_example.cpp Output poisson
A polygon soup can also be saved in the OFF format by iterating on the
points and faces:
\snippet Poisson_surface_reconstruction_3/tutorial_example.cpp Output scale space
Finally, if the polygon soup can be converted into a polygon mesh, it
can also be saved directly in the OFF format using the stream
operator:
\snippet Poisson_surface_reconstruction_3/tutorial_example.cpp Output advancing front
\section TutorialsReconstruction_recap Full Code Example
All the code snippets used in this tutorial can be assembled to create
a full algorithm pipeline (provided the correct _includes_ are
used). We give a full code example which achieves all the steps
described in this tutorial. The reconstruction method can be selected
by the user at runtime with the second argument.
\include Poisson_surface_reconstruction_3/tutorial_example.cpp
\section TutorialsReconstruction_pipeline Full Pipeline Images
The following figure an example of a full reconstruction pipeline
applied to a bear statue (courtesy _EPFL Computer Graphics and
Geometry Laboratory_ \cgalCite{cgal:e-esmr}). Two mesh processing
algorithms (hole filling and isotropic remeshing) are also applied
(refer to the chapter \ref Chapter_PolygonMeshProcessing "Polygon Mesh Processing"
for more information).
\cgalFigureBegin{TutorialsReconstructionFigFull, reconstruction_pipeline.png}
Full reconstruction pipeline (with close-ups).
\cgalFigureEnd
*/
}

View File

@ -16,6 +16,8 @@ User Manual.
geometric primitives, the notion of <em>traits classes</em> which
define what primitives are used by a geometric algorithm, the
notions of \em concept and \em model.
- \subpage tuto_reconstruction
\section tuto_examples Package Examples

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

View File

@ -708,6 +708,12 @@ Teillaud"
, year = 1999
}
@Misc{ cgal:e-esmr,
title = {{EPFL} statue model repository},
howpublished = {{EPFL} Computer Graphics and Geometry Laboratory},
url = {http://lgg.epfl.ch/statues_dataset.php}
}
@inproceedings{ cgal:eddhls-maam-95
,author = {Matthias Eck and Tony DeRose and Tom Duchamp and
Hugues Hoppe and Michael Lounsbery and Werner Stuetzle}
@ -1771,6 +1777,21 @@ ABSTRACT = {We present the first complete, exact and efficient C++ implementatio
pages=""
}
@article{cgal:btsag-asosr-16,
TITLE = {{A Survey of Surface Reconstruction from Point Clouds}},
AUTHOR = {Berger, Matthew and Tagliasacchi, Andrea and Seversky, Lee and Alliez, Pierre and Guennebaud, Gael and Levine, Joshua and Sharf, Andrei and Silva, Claudio},
URL = {https://hal.inria.fr/hal-01348404},
JOURNAL = {{Computer Graphics Forum}},
PUBLISHER = {{Wiley}},
PAGES = {27},
YEAR = {2016},
DOI = {10.1111/cgf.12802},
PDF = {https://hal.inria.fr/hal-01348404/file/survey-author.pdf},
HAL_ID = {hal-01348404},
HAL_VERSION = {v2}
}
@TechReport{ cgal:pabl-cco-07,
author = {Poudret, M. and Arnould, A. and Bertrand, Y. and Lienhardt, P.},
title = {Cartes Combinatoires Ouvertes.},

View File

@ -302,12 +302,6 @@ ALIASES = "sc{1}=<span style=\"font-variant: small-caps;\">\1</sp
"cgalCite{1}=<!-- -->\cite \1" \
"cgalPackageSection{2}=\htmlonly[block] <div style=\"background-color: #EEEDF2;\">\endhtmlonly \section \1 \2 ^^ \htmlonly[block] </div>\endhtmlonly"
# This tag can be used to specify a number of word-keyword mappings (TCL only).
# A mapping has the form "name=value". For example adding "class=itcl::class"
# will allow you to use the command class in the itcl::class meaning.
TCL_SUBST =
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all
@ -2188,12 +2182,6 @@ EXTERNAL_GROUPS = NO
EXTERNAL_PAGES = NO
# The PERL_PATH should be the absolute path and name of the perl script
# interpreter (i.e. the result of 'which perl').
# The default file (with absolute path) is: /usr/bin/perl.
PERL_PATH = /usr/bin/perl
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
@ -2207,15 +2195,6 @@ PERL_PATH = /usr/bin/perl
CLASS_DIAGRAMS = NO
# You can define message sequence charts within doxygen comments using the \msc
# command. Doxygen will then run the mscgen tool (see:
# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
# documentation. The MSCGEN_PATH tag allows you to specify the directory where
# the mscgen tool resides. If left empty the tool is assumed to be found in the
# default search path.
MSCGEN_PATH =
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.

View File

@ -116,9 +116,9 @@ template<class T>inline std::enable_if_t<std::is_empty<T>::value, unsigned> dept
// For an iterator, exact/approx applies to the objects it points to
template <class T, class=std::enable_if_t<is_iterator_type<T,std::input_iterator_tag>::value>>
auto exact(T const& t) {return make_transforming_iterator(t,[](auto const&u){return CGAL::exact(u);});}
auto exact(T const& t) {return make_transforming_iterator(t,[](auto const&u)->decltype(auto){return CGAL::exact(u);});}
template <class T, class=std::enable_if_t<is_iterator_type<T,std::input_iterator_tag>::value>>
auto approx(T const& t) {return make_transforming_iterator(t,[](auto const&u){return CGAL::approx(u);});}
auto approx(T const& t) {return make_transforming_iterator(t,[](auto const&u)->decltype(auto){return CGAL::approx(u);});}
template <class T, class=std::enable_if_t<is_iterator_type<T,std::input_iterator_tag>::value>>
unsigned depth(T const&) {return 1;} // FIXME: depth(*t) would be better when t is valid, but not for end iterators, and the true answer would iterate on the range, but we can't do that with only one iterator... We need to replace iterators with ranges to solve that.

View File

@ -539,6 +539,26 @@ public:
const Tr& tr;
};
template<typename Triangle, typename PointRange>
class Triangle_3_from_soup
{
typedef typename boost::range_value<PointRange>::type Point_3;
typedef typename Kernel_traits<Point_3>::Kernel Kernel;
public:
typedef typename Kernel::Triangle_3 result_type;
Triangle_3_from_soup(const PointRange& pts) : points(pts) { }
result_type operator()(const Triangle& t) const
{
return result_type(points[t[0]], points[t[1]], points[t[2]]);
}
private:
const PointRange& points;
};
}//end namespace internal
template <class C3t3,
@ -688,6 +708,52 @@ struct Random_points_in_triangles_3
}
};
template <class PointRange,
class Triangle = std::vector<std::size_t>,
class Creator = Creator_uniform_3<
typename Kernel_traits< typename PointRange::value_type >::Kernel::RT,
typename PointRange::value_type> >
struct Random_points_in_triangle_soup
: public Generic_random_point_generator<Triangle,
internal::Triangle_3_from_soup<Triangle, PointRange>,
Random_points_in_triangle_3<typename PointRange::value_type>,
typename PointRange::value_type>
{
typedef Generic_random_point_generator<Triangle,
internal::Triangle_3_from_soup<Triangle, PointRange>,
Random_points_in_triangle_3<typename PointRange::value_type>,
typename PointRange::value_type> Base;
typedef typename PointRange::value_type Point_3;
typedef typename Kernel_traits<Point_3>::Kernel Kernel;
typedef Triangle Id;
typedef Point_3 result_type;
typedef Random_points_in_triangle_soup<PointRange, Triangle, Creator> This;
template<typename TriangleRange>
Random_points_in_triangle_soup(const TriangleRange& triangles,
const PointRange& points,
Random& rnd = get_default_random())
: Base(triangles,
internal::Triangle_3_from_soup<Triangle, PointRange>(points),
internal::Apply_approx_sqrt<typename Kernel_traits<Point_3>::Kernel::Compute_squared_area_3>(),
rnd)
{ }
This& operator++()
{
Base::generate_point();
return *this;
}
This operator++(int)
{
This tmp = *this;
++(*this);
return tmp;
}
};
} //namespace CGAL
#include <CGAL/enable_warnings.h>

View File

@ -761,6 +761,52 @@ namespace HomogeneousKernelFunctors {
}
};
template <typename K>
class Compare_signed_distance_to_line_2
{
typedef typename K::Point_2 Point_2;
typedef typename K::Line_2 Line_2;
typedef typename K::Less_signed_distance_to_line_2 Less_signed_distance_to_line_2;
public:
typedef Comparison_result result_type;
result_type
operator()(const Point_2& p, const Point_2& q,
const Point_2& r, const Point_2& s) const
{
typedef typename K::RT RT;
const RT & phx = p.hx();
const RT & phy = p.hy();
const RT & phw = p.hw();
const RT & qhx = q.hx();
const RT & qhy = q.hy();
const RT & qhw = q.hw();
const RT & rhx = r.hx();
const RT & rhy = r.hy();
const RT & rhw = r.hw();
const RT & shx = s.hx();
const RT & shy = s.hy();
const RT & shw = s.hw();
RT scaled_dist_r_minus_scaled_dist_s =
( rhx*shw - shx*rhw ) * (phy*qhw - qhy*phw)
- ( rhy*shw - shy*rhw ) * (phx*qhw - qhx*phw);
return compare(scaled_dist_r_minus_scaled_dist_s, 0);
}
result_type
operator()(const Line_2& l, const Point_2& p, const Point_2& q) const
{
Less_signed_distance_to_line_2 less = K().less_signed_distance_to_line_2_object();
if (less(l, p, q)) return SMALLER;
if (less(l, q, p)) return LARGER;
return EQUAL;
}
};
template <typename K>
class Compare_slope_2
{
@ -4109,26 +4155,7 @@ namespace HomogeneousKernelFunctors {
operator()(const Point_2& p, const Point_2& q,
const Point_2& r, const Point_2& s) const
{
typedef typename K::RT RT;
const RT & phx= p.hx();
const RT & phy= p.hy();
const RT & phw= p.hw();
const RT & qhx= q.hx();
const RT & qhy= q.hy();
const RT & qhw= q.hw();
const RT & rhx= r.hx();
const RT & rhy= r.hy();
const RT & rhw= r.hw();
const RT & shx= s.hx();
const RT & shy= s.hy();
const RT & shw= s.hw();
RT scaled_dist_r_minus_scaled_dist_s =
( rhx*shw - shx*rhw ) * (phy*qhw - qhy*phw)
- ( rhy*shw - shy*rhw ) * (phx*qhw - qhx*phw);
return scaled_dist_r_minus_scaled_dist_s < 0;
return Compare_signed_distance_to_line_2<K>().operator()(p, q, r, s) == SMALLER;
}
result_type

View File

@ -2,6 +2,20 @@ Release History
===============
[Release 5.1] (https://github.com/CGAL/cgal/releases/tag/releases%2FCGAL-5.1)
### 2D and 3D Linear Geometry Kernel
- Add `CompareSignedDistanceToLine_2` in the 2D/3D Kernel concept to compare
the signed distance of two points to a line, or the line passing through two given points.
Corresponding functors in the model (`Compare_signed_distance_to_line_2`) are also added.
### 2D Triangulations
- Add function `split_subconstraint_graph_into_constraints()` to
`Constrained_triangulation_plus_2` to initialize the constraints
from a soup of disconnected segments that should first be split
into polylines.
Release 5.0
-----------
Release date: June 2020
@ -24,6 +38,8 @@ Release date: June 2020
### CGAL and the Boost Graph Library (BGL)
- Introduced the function `set_triangulation_ids(Triangulation& tr)` which must be used to initialize vertex,
edge, and face indices of a triangulation meant to be used with BGL algorithms.
- Added function `alpha_expansion_graphcut()` which regularizes a
multi-label partition over a user-defined graph.
### Polygon Mesh Processing
@ -38,6 +54,10 @@ Release date: June 2020
components that would be removed with the specified threshold, but without actually removing them.
- The function `CGAL::Polygon_mesh_processing::stitch_borders()` now returns the number
of halfedge pairs that were stitched.
- Introduced the new functions `CGAL::Polygon_mesh_processing::merge_reversible_connected_components()`,
`CGAL::Polygon_mesh_processing::duplicate_incompatible_edges_in_polygon_soup()`,
and `CGAL::Polygon_mesh_processing::orient_triangle_soup_with_reference_triangle_mesh()` that can be helpful
when repairing a polygon soup.
- New function to split meshes along a mesh or a plane:
`CGAL::Polygon_mesh_processing::split()`
- New function to split a single mesh containing several connected components into several meshes containing one connected component:
@ -47,6 +67,7 @@ Release date: June 2020
- The function `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh` now allows passing a point map (for the point range)
and a vertex point map (for the polygon mesh) via named parameters.
- Added the function `CGAL::Polygon_mesh_processing::polygon_mesh_to_polygon_soup()`.
- Added a new function `CGAL::Polygon_mesh_processing::sample_triangle_soup()` that generates points on a triangle soup surface.
### Point Set Processing
- Added wrapper functions for registration:
@ -62,6 +83,7 @@ Release date: June 2020
sets using ICP algorithm implemented in the third party library libpointmatcher, and registers
the points sets by transforming the data point set using the computed transformation.
### 2D Triangulations
- To fix an inconsistency between code and documentation and to clarify which types of intersections
are truly allowed in constrained Delaunay triangulations, the tag `CGAL::No_intersection_tag`
@ -74,6 +96,16 @@ Release date: June 2020
does not allow any intersection, except for the configuration of two constraints having a single
common endpoints, for convience.
### 3D Triangulations
- The free function `CGAL::file_input()` and the member function `CGAL::Triangulation_3::file_input()`
have been added. The first allows to load a `Triangulation_3` from an input stream, using functors to create vertices and cells.
The second is simply the member function version of the first one.
### 3D Triangulation Data Structure
- The free function `CGAL::file_input()` and the member function `CGAL::TDS_3::file_input()`
have been added. The first allows to load a `TDS_3` from an input stream, using functors to create vertices and cells.
The second is simply the member function version of the first one.
### dD Spatial Searching
- Improved the performance of the kd-tree in some cases:
@ -115,8 +147,7 @@ Release date: June 2020
### STL Extensions for CGAL
- Added a new concurrency tag: `CGAL::Parallel_if_available_tag`. This tag is a convenience typedef to `CGAL::Parallel_tag`
if the third party library TBB has been found and linked with, and to `CGAL::Sequential_tag` otherwise.
### Convex_hull_3
- A new overload for `convex_hull_3()` that takes a model of `VertexListGraph` has been added.

View File

@ -68,12 +68,12 @@ introduces a vector `v` initialized to `(x, y, z)`.
Vector_3(int x, int y, int z);
/*!
introduces a vector `v` initialized to `(x, y, z).
introduces a vector `v` initialized to `(x, y, z)`.
*/
Vector_3(double x, double y, double z);
/*!
introduces a vector `v` initialized to `(hx/hw, hy/hw, hz/hw).
introduces a vector `v` initialized to `(hx/hw, hy/hw, hz/hw)`.
*/
Vector_3(const Kernel::RT &hx, const Kernel::RT &hy, const Kernel::RT &hz, const Kernel::RT &hw = RT(1));

View File

@ -976,6 +976,40 @@ public:
/// @}
}; /* end Kernel::ComparePowerDistance_3 */
/*!
\ingroup PkgKernel23ConceptsFunctionObjects
\cgalConcept
\cgalRefines `AdaptableFunctor` (with four arguments)
*/
class CompareSignedDistanceToLine_2 {
public:
/// \name Operations
/// A model of this concept must provide:
/// @{
/*!
compares the signed distance of `r` and `s` to the directed line through `p` and `q`.
*/
Comparison_result operator()(const Kernel::Point_2& p,
const Kernel::Point_2& q,
const Kernel::Point_2& r,
const Kernel::Point_2& s);
/*!
compares the signed distance of `r` and `s` to the directed line `l`.
*/
Comparison_result operator()(const Kernel::Line_2& l,
const Kernel::Point_2& r,
const Kernel::Point_2& s);
/// @}
}; /* end Kernel::CompareSignedDistanceToLine_2 */
/*!
\ingroup PkgKernel23ConceptsFunctionObjects
\cgalConcept
@ -8757,7 +8791,7 @@ public:
\ingroup PkgKernel23ConceptsFunctionObjects
\cgalConcept
\cgalRefines `AdaptableFunctor`` (with four arguments)
\cgalRefines `AdaptableFunctor` (with four arguments)
\sa `has_smaller_signed_distance_to_line_grp`

View File

@ -647,6 +647,11 @@ public:
*/
typedef unspecified_type Compare_angle_with_x_axis_2;
/*!
a model of `Kernel::CompareSignedDistanceToLine_2`
*/
typedef unspecified_type Compare_signed_distance_to_line_2;
/*!
a model of `Kernel::CompareSlope_2`
*/

View File

@ -281,6 +281,7 @@
- `Kernel::CompareSlope_3`
- `Kernel::ComparePowerDistance_2`
- `Kernel::ComparePowerDistance_3`
- `Kernel::CompareSignedDistanceToLine_2`
- `Kernel::CompareSquaredDistance_2`
- `Kernel::CompareSquaredDistance_3`
- `Kernel::CompareSquaredRadius_3`

View File

@ -341,9 +341,7 @@ compare_signed_distance_to_line(const typename K::Point_2& p,
const typename K::Point_2& s,
const K& k)
{
if (k.less_signed_distance_to_line_2_object()(p, q, r, s)) return SMALLER;
if (k.less_signed_distance_to_line_2_object()(p, q, s, r)) return LARGER;
return EQUAL;
return k.compare_signed_distance_to_line_2_object()(p, q, r, s);
}
template <class K>
@ -354,9 +352,7 @@ compare_signed_distance_to_line(const typename K::Line_2& l,
const typename K::Point_2& q,
const K& k)
{
if (k.less_signed_distance_to_line_2_object()(l, p, q)) return SMALLER;
if (k.less_signed_distance_to_line_2_object()(l, q, p)) return LARGER;
return EQUAL;
return k.compare_signed_distance_to_line_2_object()(l, p, q);
}
template < class K >

View File

@ -116,6 +116,8 @@ CGAL_Kernel_pred_RT(Compare_power_distance_2,
compare_power_distance_2_object)
CGAL_Kernel_pred_RT(Compare_power_distance_3,
compare_power_distance_3_object)
CGAL_Kernel_pred(Compare_signed_distance_to_line_2,
compare_signed_distance_to_line_2_object)
CGAL_Kernel_pred(Compare_slope_2,
compare_slope_2_object)
CGAL_Kernel_pred(Compare_slope_3,

View File

@ -407,7 +407,7 @@ public:
Point_3_from_sample()),
boost::make_transform_iterator (m_samples.end(),
Point_3_from_sample())),
3);
3, CGAL::parameters::point_map (CGAL::Identity_property_map_no_lvalue<K::Point_3>()));
std::cerr << "Average spacing = " << spacing << std::endl;
}

View File

@ -0,0 +1,184 @@
// Copyright (c) 2018 GeometryFactory (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s) : Simon Giraudot
#ifndef CGAL_PSP_INTERNAL_CALLBACK_WRAPPER_H
#define CGAL_PSP_INTERNAL_CALLBACK_WRAPPER_H
#include <CGAL/license/Point_set_processing_3.h>
#include <functional>
#include <CGAL/thread.h>
namespace CGAL {
namespace Point_set_processing_3 {
namespace internal {
template <typename ConcurrencyTag>
class Callback_wrapper
{
std::size_t m_advancement;
bool m_interrupted;
public:
Callback_wrapper (const std::function<bool(double)>&, std::size_t, std::size_t, bool = false) { }
void reset (std::size_t, std::size_t, bool = false) { }
std::size_t& advancement() { return m_advancement; }
bool& interrupted() { return m_interrupted; }
void join() { }
};
template <>
class Callback_wrapper<CGAL::Sequential_tag>
{
const std::function<bool(double)>& m_callback;
std::size_t m_advancement;
bool m_interrupted;
std::size_t m_size;
public:
Callback_wrapper (const std::function<bool(double)>& callback,
std::size_t size,
std::size_t advancement = 0,
bool interrupted = false)
: m_callback (callback)
, m_advancement (advancement)
, m_interrupted (interrupted)
, m_size (size)
{ }
Callback_wrapper (const Callback_wrapper& other)
: m_callback (other.m_callback)
, m_advancement (other.m_advancement)
, m_interrupted (other.m_interrupted)
, m_size (other.m_size)
{ }
~Callback_wrapper () { }
void reset (std::size_t size, std::size_t advancement, bool interrupted = false)
{
m_size = size;
m_advancement = advancement;
m_interrupted = interrupted;
}
std::size_t& advancement()
{
return m_advancement;
}
bool& interrupted()
{
if (m_callback)
m_interrupted = !(m_callback(m_advancement / double(m_size)));
return m_interrupted;
}
void join() { }
};
#ifdef CGAL_LINKED_WITH_TBB
template <>
class Callback_wrapper<CGAL::Parallel_tag>
{
const std::function<bool(double)>& m_callback;
cpp11::atomic<std::size_t>* m_advancement;
cpp11::atomic<bool>* m_interrupted;
std::size_t m_size;
bool m_creator;
cpp11::thread* m_thread;
// assignment operator shouldn't be used (m_callback is const ref)
Callback_wrapper& operator= (const Callback_wrapper&)
{
return *this;
}
public:
Callback_wrapper (const std::function<bool(double)>& callback,
std::size_t size,
std::size_t advancement = 0,
bool interrupted = false)
: m_callback (callback)
, m_advancement (new cpp11::atomic<std::size_t>())
, m_interrupted (new cpp11::atomic<bool>())
, m_size (size)
, m_creator (true)
, m_thread (nullptr)
{
// cpp11::atomic only has default constructor, initialization done in two steps
*m_advancement = advancement;
*m_interrupted = interrupted;
if (m_callback)
m_thread = new cpp11::thread (*this);
}
Callback_wrapper (const Callback_wrapper& other)
: m_callback (other.m_callback)
, m_advancement (other.m_advancement)
, m_interrupted (other.m_interrupted)
, m_size (other.m_size)
, m_creator (false)
, m_thread (nullptr)
{
}
~Callback_wrapper ()
{
if (m_creator)
{
delete m_advancement;
delete m_interrupted;
}
if (m_thread != nullptr)
delete m_thread;
}
void reset (std::size_t size, std::size_t advancement, bool interrupted = false)
{
m_size = size;
*m_advancement = advancement;
*m_interrupted = interrupted;
if (m_callback)
m_thread = new cpp11::thread (*this);
}
cpp11::atomic<std::size_t>& advancement() { return *m_advancement; }
cpp11::atomic<bool>& interrupted() { return *m_interrupted; }
void join()
{
if (m_thread != nullptr)
m_thread->join();
}
void operator()()
{
while (*m_advancement != m_size)
{
if (m_callback && !m_callback (*m_advancement / double(m_size)))
*m_interrupted = true;
if (*m_interrupted)
return;
cpp11::sleep_for (0.00001);
}
if (m_callback)
m_callback (1.);
}
};
#endif
} // namespace internal
} // namespace Point_set_processing_3
} // namespace CGAL
#endif // CGAL_PSP_INTERNAL_CALLBACK_WRAPPER_H

View File

@ -0,0 +1,180 @@
// Copyright (c) 2019 GeometryFactory Sarl (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s) : Simon Giraudot
#ifndef CGAL_PSP_INTERNAL_NEIGHBOR_QUERY_H
#define CGAL_PSP_INTERNAL_NEIGHBOR_QUERY_H
#include <CGAL/license/Point_set_processing_3.h>
#include <CGAL/Search_traits_3.h>
#include <CGAL/Fuzzy_sphere.h>
#include <CGAL/Orthogonal_k_neighbor_search.h>
#include <CGAL/point_set_processing_assertions.h>
#include <CGAL/iterator.h>
#include <boost/function_output_iterator.hpp>
namespace CGAL {
namespace Point_set_processing_3 {
namespace internal {
struct Maximum_points_reached_exception : public std::exception { };
template <typename Kernel_, typename PointRangeRef, typename PointMap>
class Neighbor_query
{
public:
typedef Kernel_ Kernel;
typedef PointRangeRef Point_range;
typedef PointMap Point_map;
typedef typename Kernel::FT FT;
typedef typename Kernel::Point_3 Point_3;
typedef typename Range_iterator_type<PointRangeRef>::type input_iterator;
typedef typename input_iterator::value_type value_type;
typedef CGAL::Prevent_deref<input_iterator> iterator;
struct Deref_point_map
{
typedef input_iterator key_type;
typedef typename boost::property_traits<PointMap>::reference reference;
typedef typename boost::property_traits<PointMap>::value_type value_type;
typedef typename boost::property_traits<PointMap>::category category;
PointMap point_map;
Deref_point_map () { }
Deref_point_map (PointMap point_map) : point_map(point_map) { }
friend reference get (const Deref_point_map& map, key_type it)
{
return get(map.point_map, *it);
}
};
typedef CGAL::Search_traits_3<Kernel> Tree_traits_base;
typedef CGAL::Search_traits_adapter<input_iterator, Deref_point_map, Tree_traits_base> Tree_traits;
typedef CGAL::Sliding_midpoint<Tree_traits> Splitter;
typedef CGAL::Distance_adapter<input_iterator, Deref_point_map, CGAL::Euclidean_distance<Tree_traits_base> > Distance;
typedef CGAL::Kd_tree<Tree_traits, Splitter, CGAL::Tag_true, CGAL::Tag_true> Tree;
typedef CGAL::Fuzzy_sphere<Tree_traits> Sphere;
typedef CGAL::Orthogonal_k_neighbor_search<Tree_traits, Distance, Splitter, Tree> Neighbor_search;
typedef typename Neighbor_search::iterator Search_iterator;
private:
PointRangeRef m_points;
PointMap m_point_map;
Deref_point_map m_deref_map;
Tree_traits m_traits;
Tree m_tree;
Distance m_distance;
// Forbid copy
Neighbor_query (const Neighbor_query&) { }
public:
Neighbor_query (PointRangeRef points, PointMap point_map)
: m_points (points)
, m_point_map (point_map)
, m_deref_map (point_map)
, m_traits (m_deref_map)
, m_tree (iterator(m_points.begin()), iterator(m_points.end()), Splitter(), m_traits)
, m_distance (m_deref_map)
{
m_tree.build();
}
PointMap point_map() const { return m_point_map; }
template <typename OutputIterator>
void get_iterators (const Point_3& query, unsigned int k, FT neighbor_radius,
OutputIterator output) const
{
if (neighbor_radius != FT(0))
{
Sphere fs (query, neighbor_radius, 0, m_traits);
// if k=0, no limit on the number of neighbors returned
if (k == 0)
k = (std::numeric_limits<unsigned int>::max)();
unsigned int nb = 0;
try
{
std::function<void(const input_iterator&)> output_iterator_with_limit
= [&](const input_iterator& it)
{
*(output ++) = it;
if (++ nb == k)
throw Maximum_points_reached_exception();
};
auto function_output_iterator
= boost::make_function_output_iterator (output_iterator_with_limit);
m_tree.search (function_output_iterator, fs);
}
catch (const Maximum_points_reached_exception&)
{ }
// Fallback, if less than 3 points are return, search for the 3
// first points
if (nb < 3)
k = 3;
// Else, no need to search for K nearest neighbors
else
k = 0;
}
if (k != 0)
{
// Gather set of (k+1) neighboring points.
// Perform k+1 queries (as in point set, the query point is
// output first). Search may be aborted if k is greater
// than number of input points.
Neighbor_search search (m_tree, query, k+1, 0, true, m_distance);
Search_iterator search_iterator = search.begin();
unsigned int i;
for (i = 0; i < (k+1); ++ i)
{
if(search_iterator == search.end())
break; // premature ending
*(output ++) = search_iterator->first;
search_iterator++;
}
}
}
template <typename OutputIterator>
void get_points (const Point_3& query, unsigned int k, FT neighbor_radius,
OutputIterator output) const
{
return get_iterators(query, k, neighbor_radius,
boost::make_function_output_iterator
([&](const input_iterator& it)
{
*(output ++) = get (m_point_map, *it);
}));
}
};
} } } // namespace CGAL::Point_set_processing_3::internal
#endif // CGAL_PSP_INTERNAL_NEIGHBOR_QUERY_H

View File

@ -1,108 +0,0 @@
// Copyright (c) 2018 GeometryFactory (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s) : Simon Giraudot
#ifndef CGAL_PSP_INTERNAL_PARALLEL_CALLBACK_H
#define CGAL_PSP_INTERNAL_PARALLEL_CALLBACK_H
#include <CGAL/license/Point_set_processing_3.h>
#include <functional>
#include <CGAL/thread.h>
namespace CGAL {
namespace Point_set_processing_3 {
namespace internal {
class Parallel_callback
{
const std::function<bool(double)>& m_callback;
cpp11::atomic<std::size_t>* m_advancement;
cpp11::atomic<bool>* m_interrupted;
std::size_t m_size;
bool m_creator;
cpp11::thread* m_thread;
// assignment operator shouldn't be used (m_callback is const ref)
Parallel_callback& operator= (const Parallel_callback&)
{
return *this;
}
public:
Parallel_callback (const std::function<bool(double)>& callback,
std::size_t size,
std::size_t advancement = 0,
bool interrupted = false)
: m_callback (callback)
, m_advancement (new cpp11::atomic<std::size_t>())
, m_interrupted (new cpp11::atomic<bool>())
, m_size (size)
, m_creator (true)
, m_thread (nullptr)
{
// cpp11::atomic only has default constructor, initialization done in two steps
*m_advancement = advancement;
*m_interrupted = interrupted;
if (m_callback)
m_thread = new cpp11::thread (*this);
}
Parallel_callback (const Parallel_callback& other)
: m_callback (other.m_callback)
, m_advancement (other.m_advancement)
, m_interrupted (other.m_interrupted)
, m_size (other.m_size)
, m_creator (false)
, m_thread (nullptr)
{
}
~Parallel_callback ()
{
if (m_creator)
{
delete m_advancement;
delete m_interrupted;
}
if (m_thread != nullptr)
delete m_thread;
}
cpp11::atomic<std::size_t>& advancement() { return *m_advancement; }
cpp11::atomic<bool>& interrupted() { return *m_interrupted; }
void join()
{
if (m_thread != nullptr)
m_thread->join();
}
void operator()()
{
while (*m_advancement != m_size)
{
if (!m_callback (*m_advancement / double(m_size)))
*m_interrupted = true;
if (*m_interrupted)
return;
cpp11::sleep_for (0.00001);
}
m_callback (1.);
}
};
} // namespace internal
} // namespace Point_set_processing_3
} // namespace CGAL
#endif // CGAL_PSP_INTERNAL_PARALLEL_CALLBACK_H

View File

@ -1,104 +0,0 @@
// Copyright (c) 2019 GeometryFactory Sarl (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s) : Simon Giraudot
#ifndef CGAL_PSP_INTERNAL_NEIGHBOR_QUERY_H
#define CGAL_PSP_INTERNAL_NEIGHBOR_QUERY_H
#include <CGAL/license/Point_set_processing_3.h>
#include <CGAL/Search_traits_3.h>
#include <CGAL/Fuzzy_sphere.h>
#include <CGAL/Orthogonal_k_neighbor_search.h>
#include <CGAL/point_set_processing_assertions.h>
#include <boost/function_output_iterator.hpp>
namespace CGAL {
namespace Point_set_processing_3 {
namespace internal {
struct Maximum_points_reached_exception : public std::exception { };
template <typename Point,
typename TreeTraits,
typename TreeSplitter,
typename TreeUseExtendedNode,
typename FT,
typename PointContainer>
void neighbor_query (const Point& query,
const CGAL::Kd_tree<TreeTraits, TreeSplitter, TreeUseExtendedNode>& tree,
unsigned int k,
FT neighbor_radius,
PointContainer& points)
{
typedef typename CGAL::Orthogonal_k_neighbor_search<TreeTraits> Neighbor_search;
typedef typename Neighbor_search::iterator Search_iterator;
typedef CGAL::Fuzzy_sphere<TreeTraits> Sphere;
if (neighbor_radius != FT(0))
{
Sphere fs (query, neighbor_radius, 0, tree.traits());
// if k=0, no limit on the number of neighbors returned
if (k == 0)
k = (std::numeric_limits<unsigned int>::max)();
try
{
std::function<void(const Point&)> back_insert_with_limit
= [&](const Point& point) -> void
{
points.push_back (point);
if (points.size() == k)
throw Maximum_points_reached_exception();
};
auto function_output_iterator
= boost::make_function_output_iterator (back_insert_with_limit);
tree.search (function_output_iterator, fs);
}
catch (const Maximum_points_reached_exception&)
{ }
// Fallback, if less than 3 points are return, search for the 3
// first points
if (points.size() < 3)
k = 3;
// Else, no need to search for K nearest neighbors
else
k = 0;
}
if (k != 0)
{
// Gather set of (k+1) neighboring points.
// Perform k+1 queries (as in point set, the query point is
// output first). Search may be aborted if k is greater
// than number of input points.
points.reserve(k+1);
Neighbor_search search(tree,query,k+1);
Search_iterator search_iterator = search.begin();
unsigned int i;
for(i=0;i<(k+1);i++)
{
if(search_iterator == search.end())
break; // premature ending
points.push_back(search_iterator->first);
search_iterator++;
}
CGAL_point_set_processing_precondition(points.size() >= 1);
}
}
} } } // namespace CGAL::Point_set_processing_3::internal
#endif // CGAL_PSP_INTERNAL_NEIGHBOR_QUERY_H

View File

@ -17,18 +17,19 @@
#include <CGAL/disable_warnings.h>
#include <CGAL/number_type_config.h>
#include <CGAL/Search_traits_3.h>
#include <CGAL/Orthogonal_k_neighbor_search.h>
#include <CGAL/Point_set_processing_3/internal/neighbor_query.h>
#include <CGAL/Point_set_processing_3/internal/Neighbor_query.h>
#include <CGAL/Point_set_processing_3/internal/Callback_wrapper.h>
#include <CGAL/for_each.h>
#include <CGAL/property_map.h>
#include <CGAL/point_set_processing_assertions.h>
#include <CGAL/Point_with_normal_3.h>
#include <CGAL/squared_distance_3.h>
#include <functional>
#include <CGAL/boost/graph/Named_function_parameters.h>
#include <CGAL/boost/graph/named_params_helper.h>
#include <boost/iterator/zip_iterator.hpp>
#include <iterator>
#include <set>
#include <algorithm>
@ -38,23 +39,6 @@
#include <CGAL/Memory_sizer.h>
#include <CGAL/property_map.h>
#ifdef CGAL_LINKED_WITH_TBB
#include <CGAL/Point_set_processing_3/internal/Parallel_callback.h>
#include <tbb/parallel_for.h>
#include <tbb/blocked_range.h>
#include <tbb/scalable_allocator.h>
#include <atomic>
#endif // CGAL_LINKED_WITH_TBB
// Default allocator: use TBB allocators if available
#ifdef CGAL_LINKED_WITH_TBB
# define CGAL_PSP3_DEFAULT_ALLOCATOR tbb::scalable_allocator
#else // CGAL_LINKED_WITH_TBB
# define CGAL_PSP3_DEFAULT_ALLOCATOR std::allocator
#endif // CGAL_LINKED_WITH_TBB
//#define CGAL_PSP3_VERBOSE
namespace CGAL {
@ -66,47 +50,6 @@ namespace CGAL {
namespace bilateral_smooth_point_set_internal{
// Item in the Kd-tree: position (Point_3) + index
template <typename Kernel>
class Kd_tree_element : public Point_with_normal_3<Kernel>
{
public:
unsigned int index;
// basic geometric types
typedef typename CGAL::Origin Origin;
typedef CGAL::Point_with_normal_3<Kernel> Base;
Kd_tree_element(const Origin& o = ORIGIN, unsigned int id=0)
: Base(o), index(id)
{}
Kd_tree_element(const Base& p, unsigned int id=0)
: Base(p), index(id)
{}
Kd_tree_element(const Kd_tree_element& other)
: Base(other), index(other.index)
{}
Kd_tree_element& operator=(const Kd_tree_element&)=default;
};
// Helper class for the Kd-tree
template <typename Kernel>
class Kd_tree_gt : public Kernel
{
public:
typedef Kd_tree_element<Kernel> Point_3;
};
template <typename Kernel>
class Kd_tree_traits : public CGAL::Search_traits_3<Kd_tree_gt<Kernel> >
{
public:
typedef typename Kernel::Point_3 PointType;
};
/// Compute bilateral projection for each point
/// according to their KNN neighborhood points
///
@ -117,12 +60,14 @@ public:
///
/// @return
template <typename Kernel>
CGAL::Point_with_normal_3<Kernel>
template <typename Kernel, typename PointRange,
typename PointMap, typename VectorMap>
std::pair<typename Kernel::Point_3, typename Kernel::Vector_3>
compute_denoise_projection(
const CGAL::Point_with_normal_3<Kernel>& query, ///< 3D point to project
const std::vector<CGAL::Point_with_normal_3<Kernel>,
CGAL_PSP3_DEFAULT_ALLOCATOR<CGAL::Point_with_normal_3<Kernel> > >& neighbor_pwns, //
const typename PointRange::iterator::value_type& vt,
PointMap point_map,
VectorMap normal_map,
const std::vector<typename PointRange::iterator>& neighbor_pwns,
typename Kernel::FT radius, ///< accept neighborhood radius
typename Kernel::FT sharpness_angle ///< control sharpness(0-90)
)
@ -133,7 +78,6 @@ compute_denoise_projection(
// basic geometric types
typedef typename Kernel::FT FT;
typedef CGAL::Point_with_normal_3<Kernel> Pwn;
typedef typename Kernel::Vector_3 Vector;
typedef typename Kernel::Point_3 Point;
@ -148,23 +92,21 @@ compute_denoise_projection(
FT cos_sigma = cos(sharpness_angle * CGAL_PI / 180.0);
FT sharpness_bandwidth = std::pow((CGAL::max)(1e-8, 1 - cos_sigma), 2);
typename std::vector<Pwn,CGAL_PSP3_DEFAULT_ALLOCATOR<Pwn> >::const_iterator
pwn_iter = neighbor_pwns.begin();
for (; pwn_iter != neighbor_pwns.end(); ++pwn_iter)
for (typename PointRange::iterator it : neighbor_pwns)
{
const Point& np = pwn_iter->position();
const Vector& nn = pwn_iter->normal();
const Point& np = get(point_map, *it);
const Vector& nn = get(normal_map, *it);
FT dist2 = CGAL::squared_distance(query.position(), np);
FT dist2 = CGAL::squared_distance(get(point_map, vt), np);
if (dist2 < radius2)
{
FT theta = std::exp(dist2 * iradius16);
FT psi = std::exp(-std::pow(1 - query.normal() * nn, 2)
FT psi = std::exp(-std::pow(1 - get(normal_map, vt) * nn, 2)
/ sharpness_bandwidth);
weight = theta * psi;
project_dist_sum += ((query.position() - np) * nn) * weight;
project_dist_sum += ((get(point_map, vt) - np) * nn) * weight;
project_weight_sum += weight;
normal_sum = normal_sum + nn * weight;
}
@ -173,10 +115,10 @@ compute_denoise_projection(
Vector update_normal = normal_sum / project_weight_sum;
update_normal = update_normal / sqrt(update_normal.squared_length());
Point update_point = query.position() - update_normal *
Point update_point = get(point_map, vt) - update_normal *
(project_dist_sum / project_weight_sum);
return Pwn(update_point, update_normal);
return std::make_pair (update_point, update_normal);
}
/// Computes max-spacing of one query point from K nearest neighbors.
@ -187,41 +129,30 @@ compute_denoise_projection(
/// @tparam Tree KD-tree.
///
/// @return max spacing.
template < typename Kernel,
typename Tree >
typename Kernel::FT
template <typename NeighborQuery>
typename NeighborQuery::Kernel::FT
compute_max_spacing(
const CGAL::Point_with_normal_3<Kernel>& query, ///< 3D point
Tree& tree, ///< KD-tree
const typename NeighborQuery::value_type& vt,
typename NeighborQuery::Point_map point_map,
NeighborQuery& neighbor_query, ///< KD-tree
unsigned int k) ///< number of neighbors
{
// basic geometric types
typedef typename NeighborQuery::Kernel Kernel;
typedef typename Kernel::FT FT;
typedef CGAL::Point_with_normal_3<Kernel> Pwn;
// types for K nearest neighbors search
typedef bilateral_smooth_point_set_internal::Kd_tree_traits<Kernel> Tree_traits;
typedef CGAL::Orthogonal_k_neighbor_search<Tree_traits> Neighbor_search;
typedef typename Neighbor_search::iterator Search_iterator;
// performs k + 1 queries (if unique the query point is
// output first). search may be aborted when k is greater
// than number of input points
Neighbor_search search(tree,query,k+1);
Search_iterator search_iterator = search.begin();
++search_iterator;
FT max_distance = (FT)0.0;
unsigned int i;
for(i = 0; i < (k+1) ; ++i)
{
if(search_iterator == search.end())
break; // premature ending
Pwn pwn = search_iterator->first;
double dist2 = CGAL::squared_distance(query.position(), pwn.position());
max_distance = (CGAL::max)(dist2, max_distance);
++search_iterator;
}
neighbor_query.get_iterators
(get(point_map, vt), k, (FT)(0.0),
boost::make_function_output_iterator
([&](const typename NeighborQuery::input_iterator& it)
{
double dist2 = CGAL::squared_distance (get(point_map, vt), get(point_map, *it));
max_distance = (CGAL::max)(dist2, max_distance);
}));
// output max spacing
return std::sqrt(max_distance);
@ -231,102 +162,6 @@ compute_max_spacing(
/// \endcond
#ifdef CGAL_LINKED_WITH_TBB
/// \cond SKIP_IN_MANUAL
/// This is for parallelization of function: bilateral_smooth_point_set()
template <typename Kernel, typename Tree>
class Compute_pwns_neighbors
{
typedef typename CGAL::Point_with_normal_3<Kernel> Pwn;
typedef typename std::vector<Pwn,CGAL_PSP3_DEFAULT_ALLOCATOR<Pwn> > Pwns;
typedef typename std::vector<Pwns,CGAL_PSP3_DEFAULT_ALLOCATOR<Pwns> >
Pwns_neighbors;
typedef typename Kernel::FT FT;
unsigned int m_k;
FT m_neighbor_radius;
const Tree & m_tree;
const Pwns & m_pwns;
Pwns_neighbors & m_pwns_neighbors;
cpp11::atomic<std::size_t>& advancement;
cpp11::atomic<bool>& interrupted;
public:
Compute_pwns_neighbors(unsigned int k, FT neighbor_radius, const Tree &tree,
const Pwns &pwns, Pwns_neighbors &neighbors,
cpp11::atomic<std::size_t>& advancement,
cpp11::atomic<bool>& interrupted)
: m_k(k), m_neighbor_radius (neighbor_radius), m_tree(tree)
, m_pwns(pwns), m_pwns_neighbors(neighbors)
, advancement (advancement), interrupted (interrupted) {}
void operator() ( const tbb::blocked_range<size_t>& r ) const
{
for (size_t i = r.begin(); i!=r.end(); i++)
{
if (interrupted)
break;
CGAL::Point_set_processing_3::internal::neighbor_query
(m_pwns[i], m_tree, m_k, m_neighbor_radius, m_pwns_neighbors[i]);
++ advancement;
}
}
};
/// \endcond
/// \cond SKIP_IN_MANUAL
/// This is for parallelization of function: compute_denoise_projection()
template <typename Kernel>
class Pwn_updater
{
typedef typename CGAL::Point_with_normal_3<Kernel> Pwn;
typedef typename std::vector<Pwn,CGAL_PSP3_DEFAULT_ALLOCATOR<Pwn> > Pwns;
typedef typename Kernel::FT FT;
FT sharpness_angle;
FT radius;
Pwns* pwns;
Pwns* update_pwns;
std::vector<Pwns,CGAL_PSP3_DEFAULT_ALLOCATOR<Pwns> >* pwns_neighbors;
cpp11::atomic<std::size_t>& advancement;
cpp11::atomic<bool>& interrupted;
public:
Pwn_updater(FT sharpness,
FT r,
Pwns *in,
Pwns *out,
std::vector<Pwns,CGAL_PSP3_DEFAULT_ALLOCATOR<Pwns> >* neighbors,
cpp11::atomic<std::size_t>& advancement,
cpp11::atomic<bool>& interrupted):
sharpness_angle(sharpness),
radius(r),
pwns(in),
update_pwns(out),
pwns_neighbors(neighbors),
advancement (advancement),
interrupted (interrupted) {}
void operator() ( const tbb::blocked_range<size_t>& r ) const
{
for (size_t i = r.begin(); i != r.end(); ++i)
{
if (interrupted)
break;
(*update_pwns)[i] = bilateral_smooth_point_set_internal::
compute_denoise_projection<Kernel>((*pwns)[i],
(*pwns_neighbors)[i],
radius,
sharpness_angle);
++ advancement;
}
}
};
/// \endcond
#endif // CGAL_LINKED_WITH_TBB
// ----------------------------------------------------------------------------
// Public section
@ -400,16 +235,18 @@ bilateral_smooth_point_set(
using parameters::get_parameter;
// basic geometric types
typedef typename PointRange::iterator iterator;
typedef typename iterator::value_type value_type;
typedef typename CGAL::GetPointMap<PointRange, NamedParameters>::type PointMap;
typedef typename Point_set_processing_3::GetNormalMap<PointRange, NamedParameters>::type NormalMap;
typedef typename Point_set_processing_3::GetK<PointRange, NamedParameters>::Kernel Kernel;
typedef typename Kernel::Point_3 Point_3;
typedef typename Kernel::Vector_3 Vector_3;
CGAL_static_assertion_msg(!(boost::is_same<NormalMap,
typename Point_set_processing_3::GetNormalMap<PointRange, NamedParameters>::NoMap>::value),
"Error: no normal map");
typedef typename CGAL::Point_with_normal_3<Kernel> Pwn;
typedef typename std::vector<Pwn,CGAL_PSP3_DEFAULT_ALLOCATOR<Pwn> > Pwns;
typedef typename Kernel::FT FT;
double sharpness_angle = choose_parameter(get_parameter(np, internal_np::sharpness_angle), 30.);
@ -420,43 +257,20 @@ bilateral_smooth_point_set(
CGAL_point_set_processing_precondition(k > 1);
// types for K nearest neighbors search structure
typedef bilateral_smooth_point_set_internal::
Kd_tree_element<Kernel> Kd_tree_element;
typedef bilateral_smooth_point_set_internal::Kd_tree_traits<Kernel> Tree_traits;
typedef CGAL::Orthogonal_k_neighbor_search<Tree_traits> Neighbor_search;
typedef typename Neighbor_search::Tree Tree;
typedef Point_set_processing_3::internal::Neighbor_query<Kernel, PointRange&, PointMap> Neighbor_query;
PointMap point_map = choose_parameter<PointMap>(get_parameter(np, internal_np::point_map));
NormalMap normal_map = choose_parameter<NormalMap>(get_parameter(np, internal_np::normal_map));
FT neighbor_radius = choose_parameter(get_parameter(np, internal_np::neighbor_radius), FT(0));
// copy points and normals
Pwns pwns;
for(typename PointRange::iterator it = points.begin(); it != points.end(); ++it)
{
typename boost::property_traits<PointMap>::reference p = get(point_map, *it);
typename boost::property_traits<NormalMap>::reference n = get(normal_map, *it);
CGAL_point_set_processing_precondition(n.squared_length() > 1e-10);
pwns.push_back(Pwn(p, n));
}
std::size_t nb_points = pwns.size();
std::size_t nb_points = points.size();
#ifdef CGAL_PSP3_VERBOSE
std::cout << "Initialization and compute max spacing: " << std::endl;
#endif
// initiate a KD-tree search for points
std::vector<Kd_tree_element,
CGAL_PSP3_DEFAULT_ALLOCATOR<Kd_tree_element> > treeElements;
treeElements.reserve(pwns.size());
typename std::vector<Pwn,CGAL_PSP3_DEFAULT_ALLOCATOR<Pwn> >::iterator
pwn_iter = pwns.begin();
for (unsigned int i = 0; pwn_iter != pwns.end(); ++pwn_iter)
{
treeElements.push_back(Kd_tree_element(*pwn_iter, i));
}
Tree tree(treeElements.begin(), treeElements.end());
Neighbor_query neighbor_query (points, point_map);
// Guess spacing
#ifdef CGAL_PSP3_VERBOSE
CGAL::Real_timer task_timer;
@ -464,10 +278,10 @@ bilateral_smooth_point_set(
#endif
FT guess_neighbor_radius = 0.0;
for(pwn_iter = pwns.begin(); pwn_iter != pwns.end(); ++pwn_iter)
for (const value_type& vt : points)
{
FT max_spacing = bilateral_smooth_point_set_internal::
compute_max_spacing<Kernel,Tree>(*pwn_iter, tree, k);
compute_max_spacing (vt, point_map, neighbor_query, k);
guess_neighbor_radius = (CGAL::max)(max_spacing, guess_neighbor_radius);
}
@ -486,49 +300,40 @@ bilateral_smooth_point_set(
task_timer.start();
#endif
// compute all neighbors
std::vector<Pwns,CGAL_PSP3_DEFAULT_ALLOCATOR<Pwns> > pwns_neighbors;
typedef std::vector<iterator> iterators;
std::vector<iterators> pwns_neighbors;
pwns_neighbors.resize(nb_points);
#ifndef CGAL_LINKED_WITH_TBB
CGAL_static_assertion_msg (!(boost::is_convertible<ConcurrencyTag, Parallel_tag>::value),
"Parallel_tag is enabled but TBB is unavailable.");
#else
if (boost::is_convertible<ConcurrencyTag,Parallel_tag>::value)
{
Point_set_processing_3::internal::Parallel_callback
parallel_callback (callback, 2 * nb_points);
Point_set_processing_3::internal::Callback_wrapper<ConcurrencyTag>
callback_wrapper (callback, 2 * nb_points);
Compute_pwns_neighbors<Kernel, Tree> f(k, neighbor_radius, tree, pwns, pwns_neighbors,
parallel_callback.advancement(),
parallel_callback.interrupted());
tbb::parallel_for(tbb::blocked_range<size_t>(0, nb_points), f);
typedef boost::zip_iterator<boost::tuple<iterator, typename std::vector<iterators>::iterator> > Zip_iterator;
bool interrupted = parallel_callback.interrupted();
CGAL::for_each<ConcurrencyTag>
(CGAL::make_range (boost::make_zip_iterator (boost::make_tuple (points.begin(), pwns_neighbors.begin())),
boost::make_zip_iterator (boost::make_tuple (points.end(), pwns_neighbors.end()))),
[&](const typename Zip_iterator::reference& t)
{
if (callback_wrapper.interrupted())
return false;
// We interrupt by hand as counter only goes halfway and won't terminate by itself
parallel_callback.interrupted() = true;
parallel_callback.join();
neighbor_query.get_iterators (get(point_map, get<0>(t)), k, neighbor_radius,
std::back_inserter (get<1>(t)));
// If interrupted during this step, nothing is computed, we return NaN
if (interrupted)
return std::numeric_limits<double>::quiet_NaN();
}
else
#endif
{
typename std::vector<Pwns,CGAL_PSP3_DEFAULT_ALLOCATOR<Pwns> >::iterator
pwns_iter = pwns_neighbors.begin();
++ callback_wrapper.advancement();
std::size_t nb = 0;
for(pwn_iter = pwns.begin(); pwn_iter != pwns.end(); ++pwn_iter, ++pwns_iter, ++ nb)
{
CGAL::Point_set_processing_3::internal::neighbor_query
(*pwn_iter, tree, k, neighbor_radius, *pwns_iter);
return true;
});
if (callback && !callback ((nb+1) / double(2. * nb_points)))
return std::numeric_limits<double>::quiet_NaN();
}
}
bool interrupted = callback_wrapper.interrupted();
// We interrupt by hand as counter only goes halfway and won't terminate by itself
callback_wrapper.interrupted() = true;
callback_wrapper.join();
// If interrupted during this step, nothing is computed, we return NaN
if (interrupted)
return std::numeric_limits<double>::quiet_NaN();
#ifdef CGAL_PSP3_VERBOSE
task_timer.stop();
@ -541,53 +346,45 @@ bilateral_smooth_point_set(
task_timer.start();
#endif
// update points and normals
Pwns update_pwns(nb_points);
std::vector<std::pair<Point_3, Vector_3> > update_pwns(nb_points);
#ifdef CGAL_LINKED_WITH_TBB
if(boost::is_convertible<ConcurrencyTag, CGAL::Parallel_tag>::value)
{
Point_set_processing_3::internal::Parallel_callback
parallel_callback (callback, 2 * nb_points, nb_points);
callback_wrapper.reset (2 * nb_points, nb_points);
//tbb::task_scheduler_init init(4);
tbb::blocked_range<size_t> block(0, nb_points);
Pwn_updater<Kernel> pwn_updater(sharpness_angle,
guess_neighbor_radius,
&pwns,
&update_pwns,
&pwns_neighbors,
parallel_callback.advancement(),
parallel_callback.interrupted());
tbb::parallel_for(block, pwn_updater);
typedef boost::zip_iterator
<boost::tuple<iterator,
typename std::vector<iterators>::iterator,
typename std::vector<std::pair<Point_3, Vector_3> >::iterator> > Zip_iterator_2;
parallel_callback.join();
// If interrupted during this step, nothing is computed, we return NaN
if (parallel_callback.interrupted())
return std::numeric_limits<double>::quiet_NaN();
}
else
#endif // CGAL_LINKED_WITH_TBB
{
std::size_t nb = nb_points;
CGAL::for_each<ConcurrencyTag>
(CGAL::make_range (boost::make_zip_iterator (boost::make_tuple
(points.begin(), pwns_neighbors.begin(), update_pwns.begin())),
boost::make_zip_iterator (boost::make_tuple
(points.end(), pwns_neighbors.end(), update_pwns.end()))),
[&](const typename Zip_iterator_2::reference& t)
{
if (callback_wrapper.interrupted())
return false;
get<2>(t) = bilateral_smooth_point_set_internal::
compute_denoise_projection<Kernel, PointRange>
(get<0>(t),
point_map, normal_map,
get<1>(t),
guess_neighbor_radius,
sharpness_angle);
++ callback_wrapper.advancement();
return true;
});
callback_wrapper.join();
// If interrupted during this step, nothing is computed, we return NaN
if (callback_wrapper.interrupted())
return std::numeric_limits<double>::quiet_NaN();
typename std::vector<Pwn,CGAL_PSP3_DEFAULT_ALLOCATOR<Pwn> >::iterator
update_iter = update_pwns.begin();
typename std::vector<Pwns,CGAL_PSP3_DEFAULT_ALLOCATOR<Pwns> >::iterator
neighbor_iter = pwns_neighbors.begin();
for(pwn_iter = pwns.begin(); pwn_iter != pwns.end();
++pwn_iter, ++update_iter, ++neighbor_iter, ++ nb)
{
*update_iter = bilateral_smooth_point_set_internal::
compute_denoise_projection<Kernel>
(*pwn_iter,
*neighbor_iter,
guess_neighbor_radius,
sharpness_angle);
if (callback && !callback ((nb+1) / double(2. * nb_points)))
return std::numeric_limits<double>::quiet_NaN();
}
}
#ifdef CGAL_PSP3_VERBOSE
task_timer.stop();
memory = CGAL::Memory_sizer().virtual_size();
@ -596,13 +393,13 @@ bilateral_smooth_point_set(
#endif
// save results
FT sum_move_error = 0;
typename PointRange::iterator it = points.begin();
for(unsigned int i = 0 ; it != points.end(); ++it, ++i)
std::size_t nb = 0;
for (value_type& vt : points)
{
typename boost::property_traits<PointMap>::reference p = get(point_map, *it);
sum_move_error += CGAL::squared_distance(p, update_pwns[i].position());
put (point_map, *it, update_pwns[i].position());
put (normal_map, *it, update_pwns[i].normal());
sum_move_error += CGAL::squared_distance(get(point_map, vt), update_pwns[nb].first);
put (point_map, vt, update_pwns[nb].first);
put (normal_map, vt, update_pwns[nb].second);
++ nb;
}
return sum_move_error / nb_points;

View File

@ -18,7 +18,9 @@
#include <CGAL/Search_traits_3.h>
#include <CGAL/squared_distance_3.h>
#include <CGAL/Orthogonal_k_neighbor_search.h>
#include <CGAL/Point_set_processing_3/internal/Neighbor_query.h>
#include <CGAL/Point_set_processing_3/internal/Callback_wrapper.h>
#include <CGAL/for_each.h>
#include <CGAL/property_map.h>
#include <CGAL/point_set_processing_assertions.h>
#include <CGAL/assertions.h>
@ -27,15 +29,12 @@
#include <CGAL/boost/graph/Named_function_parameters.h>
#include <CGAL/boost/graph/named_params_helper.h>
#include <boost/iterator/zip_iterator.hpp>
#include <iterator>
#include <list>
#ifdef CGAL_LINKED_WITH_TBB
#include <CGAL/Point_set_processing_3/internal/Parallel_callback.h>
#include <tbb/parallel_for.h>
#include <tbb/blocked_range.h>
#include <tbb/scalable_allocator.h>
#endif // CGAL_LINKED_WITH_TBB
#ifdef DOXYGEN_RUNNING
#define CGAL_BGL_NP_TEMPLATE_PARAMETERS NamedParameters
@ -60,81 +59,37 @@ namespace internal {
/// @tparam Tree KD-tree.
///
/// @return average spacing (scalar).
template < typename Kernel,
typename Tree >
typename Kernel::FT
compute_average_spacing(const typename Kernel::Point_3& query, ///< 3D point whose spacing we want to compute
const Tree& tree, ///< KD-tree
template <typename NeighborQuery>
typename NeighborQuery::Kernel::FT
compute_average_spacing(const typename NeighborQuery::Kernel::Point_3& query, ///< 3D point whose spacing we want to compute
const NeighborQuery& neighbor_query, ///< KD-tree
unsigned int k) ///< number of neighbors
{
// basic geometric types
typedef typename NeighborQuery::Kernel Kernel;
typedef typename Kernel::FT FT;
typedef typename Kernel::Point_3 Point;
// types for K nearest neighbors search
typedef Search_traits_3<Kernel> Tree_traits;
typedef Orthogonal_k_neighbor_search<Tree_traits> Neighbor_search;
typedef typename Neighbor_search::iterator Search_iterator;
// performs k + 1 queries (if unique the query point is
// output first). search may be aborted when k is greater
// than number of input points
Neighbor_search search(tree,query,k+1);
Search_iterator search_iterator = search.begin();
FT sum_distances = (FT)0.0;
unsigned int i;
for(i=0;i<(k+1);i++)
{
if(search_iterator == search.end())
break; // premature ending
Point p = search_iterator->first;
sum_distances += std::sqrt(CGAL::squared_distance(query,p));
search_iterator++;
}
unsigned int i = 0;
neighbor_query.get_points
(query, k, 0,
boost::make_function_output_iterator
([&](const Point& p)
{
sum_distances += std::sqrt(CGAL::squared_distance (query,p));
++ i;
}));
// output average spacing
return sum_distances / (FT)i;
}
#ifdef CGAL_LINKED_WITH_TBB
template <typename Kernel, typename Tree>
class Compute_average_spacings {
typedef typename Kernel::Point_3 Point;
typedef typename Kernel::FT FT;
const Tree& tree;
const unsigned int k;
const std::vector<Point>& input;
std::vector<FT>& output;
cpp11::atomic<std::size_t>& advancement;
cpp11::atomic<bool>& interrupted;
public:
Compute_average_spacings(Tree& tree, unsigned int k, std::vector<Point>& points,
std::vector<FT>& output,
cpp11::atomic<std::size_t>& advancement,
cpp11::atomic<bool>& interrupted)
: tree(tree), k (k), input (points), output (output)
, advancement (advancement)
, interrupted (interrupted)
{ }
void operator()(const tbb::blocked_range<std::size_t>& r) const
{
for( std::size_t i = r.begin(); i != r.end(); ++i)
{
if (interrupted)
break;
output[i] = CGAL::internal::compute_average_spacing<Kernel,Tree>(input[i], tree, k);
++ advancement;
}
}
};
#endif // CGAL_LINKED_WITH_TBB
} /* namespace internal */
/// \endcond
@ -195,20 +150,17 @@ compute_average_spacing(
using parameters::get_parameter;
// basic geometric types
typedef typename PointRange::const_iterator iterator;
typedef typename CGAL::GetPointMap<PointRange, CGAL_BGL_NP_CLASS>::const_type PointMap;
typedef typename Point_set_processing_3::GetK<PointRange, CGAL_BGL_NP_CLASS>::Kernel Kernel;
typedef typename Kernel::Point_3 Point;
PointMap point_map = choose_parameter<PointMap>(get_parameter(np, internal_np::point_map));
PointMap point_map = choose_parameter(get_parameter(np, internal_np::point_map), PointMap());
const std::function<bool(double)>& callback = choose_parameter(get_parameter(np, internal_np::callback),
std::function<bool(double)>());
// types for K nearest neighbors search structure
typedef typename Kernel::FT FT;
typedef Search_traits_3<Kernel> Tree_traits;
typedef Orthogonal_k_neighbor_search<Tree_traits> Neighbor_search;
typedef typename Neighbor_search::Tree Tree;
typedef Point_set_processing_3::internal::Neighbor_query<Kernel, const PointRange&, PointMap> Neighbor_query;
// precondition: at least one element in the container.
// to fix: should have at least three distinct points
@ -219,60 +171,46 @@ compute_average_spacing(
CGAL_point_set_processing_precondition(k >= 2);
// Instanciate a KD-tree search.
// Note: We have to convert each input iterator to Point_3.
std::vector<Point> kd_tree_points;
for(typename PointRange::const_iterator it = points.begin(); it != points.end(); it++)
kd_tree_points.push_back(get(point_map, *it));
Tree tree(kd_tree_points.begin(), kd_tree_points.end());
Neighbor_query neighbor_query (points, point_map);
// iterate over input points, compute and output normal
// vectors (already normalized)
FT sum_spacings = (FT)0.0;
std::size_t nb = 0;
std::size_t nb_points = std::distance(points.begin(), points.end());
#ifndef CGAL_LINKED_WITH_TBB
CGAL_static_assertion_msg (!(boost::is_convertible<ConcurrencyTag, Parallel_tag>::value),
"Parallel_tag is enabled but TBB is unavailable.");
#else
if (boost::is_convertible<ConcurrencyTag,Parallel_tag>::value)
{
Point_set_processing_3::internal::Parallel_callback
parallel_callback (callback, kd_tree_points.size());
Point_set_processing_3::internal::Callback_wrapper<ConcurrencyTag>
callback_wrapper (callback, nb_points);
std::vector<FT> spacings (kd_tree_points.size (), -1);
CGAL::internal::Compute_average_spacings<Kernel, Tree>
f (tree, k, kd_tree_points, spacings,
parallel_callback.advancement(),
parallel_callback.interrupted());
tbb::parallel_for(tbb::blocked_range<size_t>(0, kd_tree_points.size ()), f);
std::vector<FT> spacings (nb_points, -1);
for (unsigned int i = 0; i < spacings.size (); ++ i)
if (spacings[i] >= 0.)
{
sum_spacings += spacings[i];
++ nb;
}
typedef boost::zip_iterator<boost::tuple<iterator, typename std::vector<FT>::iterator> > Zip_iterator;
parallel_callback.join();
}
else
#endif
CGAL::for_each<ConcurrencyTag>
(CGAL::make_range (boost::make_zip_iterator (boost::make_tuple (points.begin(), spacings.begin())),
boost::make_zip_iterator (boost::make_tuple (points.end(), spacings.end()))),
[&](const typename Zip_iterator::reference& t)
{
for(typename PointRange::const_iterator it = points.begin(); it != points.end(); it++, nb++)
{
sum_spacings += internal::compute_average_spacing<Kernel,Tree>(
get(point_map,*it),
tree,k);
if (callback && !callback ((nb+1) / double(kd_tree_points.size())))
{
++ nb;
break;
}
}
}
if (callback_wrapper.interrupted())
return false;
get<1>(t) = CGAL::internal::compute_average_spacing<Neighbor_query>
(get(point_map, get<0>(t)), neighbor_query, k);
++ callback_wrapper.advancement();
return true;
});
for (unsigned int i = 0; i < spacings.size (); ++ i)
if (spacings[i] >= 0.)
{
sum_spacings += spacings[i];
++ nb;
}
callback_wrapper.join();
// return average spacing
return sum_spacings / (FT)(nb);
return sum_spacings / (FT)(nb);
}
/// \cond SKIP_IN_MANUAL

View File

@ -17,9 +17,9 @@
#include <CGAL/disable_warnings.h>
#include <CGAL/IO/trace.h>
#include <CGAL/Search_traits_3.h>
#include <CGAL/Orthogonal_k_neighbor_search.h>
#include <CGAL/Point_set_processing_3/internal/neighbor_query.h>
#include <CGAL/Point_set_processing_3/internal/Neighbor_query.h>
#include <CGAL/Point_set_processing_3/internal/Callback_wrapper.h>
#include <CGAL/for_each.h>
#include <CGAL/Monge_via_jet_fitting.h>
#include <CGAL/property_map.h>
#include <CGAL/point_set_processing_assertions.h>
@ -32,13 +32,6 @@
#include <iterator>
#include <list>
#ifdef CGAL_LINKED_WITH_TBB
#include <CGAL/Point_set_processing_3/internal/Parallel_callback.h>
#include <tbb/parallel_for.h>
#include <tbb/blocked_range.h>
#include <tbb/scalable_allocator.h>
#endif // CGAL_LINKED_WITH_TBB
namespace CGAL {
@ -58,18 +51,16 @@ namespace internal {
/// @tparam Tree KD-tree.
///
/// @return Computed normal. Orientation is random.
template < typename Kernel,
typename SvdTraits,
typename Tree
>
typename Kernel::Vector_3
jet_estimate_normal(const typename Kernel::Point_3& query, ///< point to compute the normal at
Tree& tree, ///< KD-tree
template <typename SvdTraits, typename NeighborQuery>
typename NeighborQuery::Kernel::Vector_3
jet_estimate_normal(const typename NeighborQuery::Point_3& query, ///< point to compute the normal at
const NeighborQuery& neighbor_query, ///< KD-tree
unsigned int k, ///< number of neighbors
typename Kernel::FT neighbor_radius,
typename NeighborQuery::FT neighbor_radius,
unsigned int degree_fitting)
{
// basic geometric types
typedef typename NeighborQuery::Kernel Kernel;
typedef typename Kernel::Point_3 Point;
// types for jet fitting
@ -79,8 +70,7 @@ jet_estimate_normal(const typename Kernel::Point_3& query, ///< point to compute
typedef typename Monge_jet_fitting::Monge_form Monge_form;
std::vector<Point> points;
CGAL::Point_set_processing_3::internal::neighbor_query
(query, tree, k, neighbor_radius, points);
neighbor_query.get_points (query, k, neighbor_radius, std::back_inserter(points));
// performs jet fitting
Monge_jet_fitting monge_fit;
@ -92,49 +82,6 @@ jet_estimate_normal(const typename Kernel::Point_3& query, ///< point to compute
return monge_form.normal_direction();
}
#ifdef CGAL_LINKED_WITH_TBB
template <typename Kernel, typename SvdTraits, typename Tree>
class Jet_estimate_normals {
typedef typename Kernel::FT FT;
typedef typename Kernel::Point_3 Point;
typedef typename Kernel::Vector_3 Vector;
const Tree& tree;
const unsigned int k;
const FT neighbor_radius;
const unsigned int degree_fitting;
const std::vector<Point>& input;
std::vector<Vector>& output;
cpp11::atomic<std::size_t>& advancement;
cpp11::atomic<bool>& interrupted;
public:
Jet_estimate_normals(Tree& tree, unsigned int k, FT neighbor_radius,
std::vector<Point>& points,
unsigned int degree_fitting, std::vector<Vector>& output,
cpp11::atomic<std::size_t>& advancement,
cpp11::atomic<bool>& interrupted)
: tree(tree), k (k), neighbor_radius (neighbor_radius)
, degree_fitting (degree_fitting), input (points), output (output)
, advancement (advancement)
, interrupted (interrupted)
{ }
void operator()(const tbb::blocked_range<std::size_t>& r) const
{
for( std::size_t i = r.begin(); i != r.end(); ++i)
{
if (interrupted)
break;
output[i] = CGAL::internal::jet_estimate_normal<Kernel,SvdTraits>(input[i], tree, k, neighbor_radius, degree_fitting);
++ advancement;
}
}
};
#endif // CGAL_LINKED_WITH_TBB
} /* namespace internal */
/// \endcond
@ -202,6 +149,8 @@ jet_estimate_normals(
CGAL_TRACE("Calls jet_estimate_normals()\n");
// basic geometric types
typedef typename PointRange::iterator iterator;
typedef typename iterator::value_type value_type;
typedef typename CGAL::GetPointMap<PointRange, NamedParameters>::type PointMap;
typedef typename Point_set_processing_3::GetNormalMap<PointRange, NamedParameters>::type NormalMap;
typedef typename Point_set_processing_3::GetK<PointRange, NamedParameters>::Kernel Kernel;
@ -223,15 +172,8 @@ jet_estimate_normals(
const std::function<bool(double)>& callback = choose_parameter(get_parameter(np, internal_np::callback),
std::function<bool(double)>());
typedef typename Kernel::Point_3 Point;
// Input points types
typedef typename boost::property_traits<NormalMap>::value_type Vector;
// types for K nearest neighbors search structure
typedef typename CGAL::Search_traits_3<Kernel> Tree_traits;
typedef typename CGAL::Orthogonal_k_neighbor_search<Tree_traits> Neighbor_search;
typedef typename Neighbor_search::Tree Tree;
typedef Point_set_processing_3::internal::Neighbor_query<Kernel, PointRange&, PointMap> Neighbor_query;
// precondition: at least one element in the container.
// to fix: should have at least three distinct points
@ -244,60 +186,32 @@ jet_estimate_normals(
std::size_t memory = CGAL::Memory_sizer().virtual_size(); CGAL_TRACE(" %ld Mb allocated\n", memory>>20);
CGAL_TRACE(" Creates KD-tree\n");
typename PointRange::iterator it;
// Instanciate a KD-tree search.
// Note: We have to convert each input iterator to Point_3.
std::vector<Point> kd_tree_points;
for(it = points.begin(); it != points.end(); it++)
kd_tree_points.push_back(get(point_map, *it));
Tree tree(kd_tree_points.begin(), kd_tree_points.end());
Neighbor_query neighbor_query (points, point_map);
memory = CGAL::Memory_sizer().virtual_size(); CGAL_TRACE(" %ld Mb allocated\n", memory>>20);
CGAL_TRACE(" Computes normals\n");
// iterate over input points, compute and output normal
// vectors (already normalized)
#ifndef CGAL_LINKED_WITH_TBB
CGAL_static_assertion_msg (!(boost::is_convertible<ConcurrencyTag, Parallel_tag>::value),
"Parallel_tag is enabled but TBB is unavailable.");
#else
if (boost::is_convertible<ConcurrencyTag,Parallel_tag>::value)
{
Point_set_processing_3::internal::Parallel_callback
parallel_callback (callback, kd_tree_points.size());
std::size_t nb_points = points.size();
std::vector<Vector> normals (kd_tree_points.size (),
CGAL::NULL_VECTOR);
CGAL::internal::Jet_estimate_normals<Kernel, SvdTraits, Tree>
f (tree, k, neighbor_radius,
kd_tree_points, degree_fitting, normals,
parallel_callback.advancement(),
parallel_callback.interrupted());
tbb::parallel_for(tbb::blocked_range<size_t>(0, kd_tree_points.size ()), f);
std::size_t i = 0;
for(it = points.begin(); it != points.end(); ++ it, ++ i)
if (normals[i] != CGAL::NULL_VECTOR)
put (normal_map, *it, normals[i]);
Point_set_processing_3::internal::Callback_wrapper<ConcurrencyTag>
callback_wrapper (callback, nb_points);
parallel_callback.join();
}
else
#endif
CGAL::for_each<ConcurrencyTag>
(points,
[&](value_type& vt)
{
std::size_t nb = 0;
for(it = points.begin(); it != points.end(); it++, ++ nb)
{
Vector normal = internal::jet_estimate_normal<Kernel,SvdTraits,Tree>(
get(point_map,*it),
tree, k, neighbor_radius, degree_fitting);
if (callback_wrapper.interrupted())
return false;
put(normal_map, *it, normal); // normal_map[it] = normal
if (callback && !callback ((nb+1) / double(kd_tree_points.size())))
break;
}
}
put (normal_map, vt,
CGAL::internal::jet_estimate_normal<SvdTraits>
(get(point_map, vt), neighbor_query, k, neighbor_radius, degree_fitting));
++ callback_wrapper.advancement();
return true;
});
callback_wrapper.join();
memory = CGAL::Memory_sizer().virtual_size(); CGAL_TRACE(" %ld Mb allocated\n", memory>>20);
CGAL_TRACE("End of jet_estimate_normals()\n");

View File

@ -17,9 +17,9 @@
#include <CGAL/disable_warnings.h>
#include <CGAL/IO/trace.h>
#include <CGAL/Search_traits_3.h>
#include <CGAL/Orthogonal_k_neighbor_search.h>
#include <CGAL/Point_set_processing_3/internal/neighbor_query.h>
#include <CGAL/Point_set_processing_3/internal/Neighbor_query.h>
#include <CGAL/Point_set_processing_3/internal/Callback_wrapper.h>
#include <CGAL/for_each.h>
#include <CGAL/Monge_via_jet_fitting.h>
#include <CGAL/property_map.h>
#include <CGAL/point_set_processing_assertions.h>
@ -28,16 +28,11 @@
#include <CGAL/boost/graph/Named_function_parameters.h>
#include <CGAL/boost/graph/named_params_helper.h>
#include <boost/iterator/zip_iterator.hpp>
#include <iterator>
#include <list>
#ifdef CGAL_LINKED_WITH_TBB
#include <CGAL/Point_set_processing_3/internal/Parallel_callback.h>
#include <tbb/parallel_for.h>
#include <tbb/blocked_range.h>
#include <tbb/scalable_allocator.h>
#endif // CGAL_LINKED_WITH_TBB
namespace CGAL {
@ -57,20 +52,20 @@ namespace internal {
/// @tparam Tree KD-tree.
///
/// @return computed point
template <typename Kernel,
typename SvdTraits,
typename Tree
template <typename SvdTraits,
typename NeighborQuery
>
typename Kernel::Point_3
typename NeighborQuery::Kernel::Point_3
jet_smooth_point(
const typename Kernel::Point_3& query, ///< 3D point to project
Tree& tree, ///< KD-tree
const typename NeighborQuery::Kernel::Point_3& query, ///< 3D point to project
NeighborQuery& neighbor_query, ///< KD-tree
const unsigned int k, ///< number of neighbors.
typename Kernel::FT neighbor_radius,
typename NeighborQuery::Kernel::FT neighbor_radius,
const unsigned int degree_fitting,
const unsigned int degree_monge)
{
// basic geometric types
typedef typename NeighborQuery::Kernel Kernel;
typedef typename Kernel::Point_3 Point;
// types for jet fitting
@ -80,8 +75,7 @@ jet_smooth_point(
typedef typename Monge_jet_fitting::Monge_form Monge_form;
std::vector<Point> points;
CGAL::Point_set_processing_3::internal::neighbor_query
(query, tree, k, neighbor_radius, points);
neighbor_query.get_points (query, k, neighbor_radius, std::back_inserter(points));
// performs jet fitting
Monge_jet_fitting monge_fit;
@ -92,50 +86,6 @@ jet_smooth_point(
return monge_form.origin();
}
#ifdef CGAL_LINKED_WITH_TBB
template <typename Kernel, typename SvdTraits, typename Tree>
class Jet_smooth_pwns {
typedef typename Kernel::Point_3 Point;
const Tree& tree;
const unsigned int k;
const typename Kernel::FT neighbor_radius;
unsigned int degree_fitting;
unsigned int degree_monge;
const std::vector<Point>& input;
std::vector<Point>& output;
cpp11::atomic<std::size_t>& advancement;
cpp11::atomic<bool>& interrupted;
public:
Jet_smooth_pwns (Tree& tree, unsigned int k, typename Kernel::FT neighbor_radius,
std::vector<Point>& points,
unsigned int degree_fitting, unsigned int degree_monge, std::vector<Point>& output,
cpp11::atomic<std::size_t>& advancement,
cpp11::atomic<bool>& interrupted)
: tree(tree), k (k), neighbor_radius(neighbor_radius)
, degree_fitting (degree_fitting)
, degree_monge (degree_monge), input (points), output (output)
, advancement (advancement)
, interrupted (interrupted)
{ }
void operator()(const tbb::blocked_range<std::size_t>& r) const
{
for( std::size_t i = r.begin(); i != r.end(); ++i)
{
if (interrupted)
break;
output[i] = CGAL::internal::jet_smooth_point<Kernel, SvdTraits>(input[i], tree, k,
neighbor_radius,
degree_fitting,
degree_monge);
++ advancement;
}
}
};
#endif // CGAL_LINKED_WITH_TBB
} /* namespace internal */
@ -204,6 +154,7 @@ jet_smooth_point_set(
using parameters::get_parameter;
// basic geometric types
typedef typename PointRange::iterator iterator;
typedef typename CGAL::GetPointMap<PointRange, NamedParameters>::type PointMap;
typedef typename Point_set_processing_3::GetK<PointRange, NamedParameters>::Kernel Kernel;
typedef typename GetSvdTraits<NamedParameters>::type SvdTraits;
@ -220,12 +171,8 @@ jet_smooth_point_set(
const std::function<bool(double)>& callback = choose_parameter(get_parameter(np, internal_np::callback),
std::function<bool(double)>());
typedef typename Kernel::Point_3 Point;
// types for K nearest neighbors search structure
typedef typename CGAL::Search_traits_3<Kernel> Tree_traits;
typedef typename CGAL::Orthogonal_k_neighbor_search<Tree_traits> Neighbor_search;
typedef typename Neighbor_search::Tree Tree;
typedef Point_set_processing_3::internal::Neighbor_query<Kernel, PointRange&, PointMap> Neighbor_query;
// precondition: at least one element in the container.
// to fix: should have at least three distinct points
@ -235,56 +182,53 @@ jet_smooth_point_set(
// precondition: at least 2 nearest neighbors
CGAL_point_set_processing_precondition(k >= 2);
typename PointRange::iterator it;
// Instanciate a KD-tree search.
// Note: We have to convert each input iterator to Point_3.
std::vector<Point> kd_tree_points;
for(it = points.begin(); it != points.end(); it++)
kd_tree_points.push_back(get(point_map, *it));
Tree tree(kd_tree_points.begin(), kd_tree_points.end());
Neighbor_query neighbor_query (points, point_map);
// Iterates over input points and mutates them.
// Implementation note: the cast to Point& allows to modify only the point's position.
#ifndef CGAL_LINKED_WITH_TBB
CGAL_static_assertion_msg (!(boost::is_convertible<ConcurrencyTag, Parallel_tag>::value),
"Parallel_tag is enabled but TBB is unavailable.");
#else
if (boost::is_convertible<ConcurrencyTag,Parallel_tag>::value)
{
Point_set_processing_3::internal::Parallel_callback
parallel_callback (callback, kd_tree_points.size());
std::size_t nb_points = points.size();
std::vector<Point> mutated_points (kd_tree_points.size (), CGAL::ORIGIN);
CGAL::internal::Jet_smooth_pwns<Kernel, SvdTraits, Tree>
f (tree, k, neighbor_radius, kd_tree_points, degree_fitting, degree_monge,
mutated_points,
parallel_callback.advancement(),
parallel_callback.interrupted());
tbb::parallel_for(tbb::blocked_range<size_t>(0, kd_tree_points.size ()), f);
unsigned int i = 0;
for(it = points.begin(); it != points.end(); ++ it, ++ i)
if (mutated_points[i] != CGAL::ORIGIN)
put(point_map, *it, mutated_points[i]);
Point_set_processing_3::internal::Callback_wrapper<ConcurrencyTag>
callback_wrapper (callback, nb_points);
parallel_callback.join();
std::vector<typename Kernel::Point_3> smoothed (points.size());
}
else
#endif
typedef boost::zip_iterator
<boost::tuple<iterator,
typename std::vector<typename Kernel::Point_3>::iterator> > Zip_iterator;
CGAL::for_each<ConcurrencyTag>
(CGAL::make_range (boost::make_zip_iterator (boost::make_tuple (points.begin(), smoothed.begin())),
boost::make_zip_iterator (boost::make_tuple (points.end(), smoothed.end()))),
[&](const typename Zip_iterator::reference& t)
{
std::size_t nb = 0;
for(it = points.begin(); it != points.end(); it++, ++ nb)
{
const typename boost::property_traits<PointMap>::reference p = get(point_map, *it);
put(point_map, *it ,
internal::jet_smooth_point<Kernel, SvdTraits>(
p,tree,k,neighbor_radius,degree_fitting,degree_monge) );
if (callback && !callback ((nb+1) / double(kd_tree_points.size())))
break;
}
}
if (callback_wrapper.interrupted())
return false;
get<1>(t) = CGAL::internal::jet_smooth_point<SvdTraits>
(get (point_map, get<0>(t)), neighbor_query,
k,
neighbor_radius,
degree_fitting,
degree_monge);
++ callback_wrapper.advancement();
return true;
});
callback_wrapper.join();
// Finally, update points
CGAL::for_each<ConcurrencyTag>
(CGAL::make_range (boost::make_zip_iterator (boost::make_tuple (points.begin(), smoothed.begin())),
boost::make_zip_iterator (boost::make_tuple (points.end(), smoothed.end()))),
[&](const typename Zip_iterator::reference& t)
{
put (point_map, get<0>(t), get<1>(t));
return true;
});
}

View File

@ -17,10 +17,7 @@
#include <CGAL/disable_warnings.h>
#include <CGAL/IO/trace.h>
#include <CGAL/Search_traits_3.h>
#include <CGAL/Orthogonal_k_neighbor_search.h>
#include <CGAL/Point_set_processing_3/internal/neighbor_query.h>
#include <CGAL/Point_set_processing_3/internal/Search_traits_vertex_handle_3.h>
#include <CGAL/Point_set_processing_3/internal/Neighbor_query.h>
#include <CGAL/property_map.h>
#include <CGAL/Index_property_map.h>
#include <CGAL/Memory_sizer.h>
@ -296,17 +293,16 @@ mst_find_source(
/// @tparam Kernel Geometric traits class.
///
/// @return the Riemannian graph
template <typename ForwardIterator,
template <typename PointRange,
typename PointMap,
typename NormalMap,
typename IndexMap,
typename ConstrainedMap,
typename Kernel
>
Riemannian_graph<ForwardIterator>
Riemannian_graph<typename PointRange::iterator>
create_riemannian_graph(
ForwardIterator first, ///< iterator over the first input point.
ForwardIterator beyond, ///< past-the-end iterator over the input points.
PointRange& points, ///< input points
PointMap point_map, ///< property map: value_type of ForwardIterator -> Point_3
NormalMap normal_map, ///< property map: value_type of ForwardIterator -> Vector_3
IndexMap index_map, ///< property map ForwardIterator -> index
@ -320,43 +316,23 @@ create_riemannian_graph(
typedef typename boost::property_traits<NormalMap>::reference Vector_ref;
// Types for K nearest neighbors search structure
typedef Point_vertex_handle_3<ForwardIterator> Point_vertex_handle_3;
typedef internal::Search_traits_vertex_handle_3<ForwardIterator> Traits;
typedef Euclidean_distance_vertex_handle_3<ForwardIterator> KDistance;
typedef Orthogonal_k_neighbor_search<Traits,KDistance> Neighbor_search;
typedef typename Neighbor_search::Tree Tree;
typedef typename PointRange::iterator ForwardIterator;
typedef Point_set_processing_3::internal::Neighbor_query<Kernel, PointRange&, PointMap> Neighbor_query;
// Riemannian_graph types
typedef internal::Riemannian_graph<ForwardIterator> Riemannian_graph;
typedef typename boost::property_map<Riemannian_graph, boost::edge_weight_t>::type Riemannian_graph_weight_map;
// Precondition: at least one element in the container.
CGAL_point_set_processing_precondition(first != beyond);
// Precondition: at least 2 nearest neighbors
CGAL_point_set_processing_precondition(k >= 2);
// Number of input points
const std::size_t num_input_points = distance(first, beyond);
const std::size_t num_input_points = points.size();
std::size_t memory = CGAL::Memory_sizer().virtual_size(); CGAL_TRACE(" %ld Mb allocated\n", memory>>20);
CGAL_TRACE(" Creates KD-tree\n");
// Instanciate a KD-tree search.
// Notes: We have to wrap each input point by a Point_vertex_handle_3.
// The KD-tree is allocated dynamically to recover RAM as soon as possible.
std::vector<Point_vertex_handle_3> kd_tree_points; kd_tree_points.reserve(num_input_points);
for (ForwardIterator it = first; it != beyond; it++)
{
Point_ref point = get(point_map, *it);
Point_vertex_handle_3 point_wrapper(point.x(), point.y(), point.z(), it);
kd_tree_points.push_back(point_wrapper);
}
boost::shared_ptr<Tree> tree( new Tree(kd_tree_points.begin(), kd_tree_points.end()) );
// Recover RAM
kd_tree_points.clear();
Neighbor_query neighbor_query (points, point_map);
memory = CGAL::Memory_sizer().virtual_size(); CGAL_TRACE(" %ld Mb allocated\n", memory>>20);
CGAL_TRACE(" Creates Riemannian Graph\n");
@ -369,7 +345,7 @@ create_riemannian_graph(
Riemannian_graph riemannian_graph;
//
// add vertices
for (ForwardIterator it = first; it != beyond; it++)
for (ForwardIterator it = points.begin(); it != points.end(); it++)
{
typename Riemannian_graph::vertex_descriptor v = add_vertex(riemannian_graph);
CGAL_point_set_processing_assertion(v == get(index_map,it));
@ -383,16 +359,14 @@ create_riemannian_graph(
//
// add edges
Riemannian_graph_weight_map riemannian_graph_weight_map = get(boost::edge_weight, riemannian_graph);
for (ForwardIterator it = first; it != beyond; it++)
for (ForwardIterator it = points.begin(); it != points.end(); it++)
{
std::size_t it_index = get(index_map,it);
Vector_ref it_normal_vector = get(normal_map,*it);
Point_ref point = get(point_map, *it);
Point_vertex_handle_3 point_wrapper(point.x(), point.y(), point.z(), it);
std::vector<Point_vertex_handle_3> neighbor_points;
CGAL::Point_set_processing_3::internal::neighbor_query
(point_wrapper, *tree, k, neighbor_radius, neighbor_points);
std::vector<ForwardIterator> neighbor_points;
neighbor_query.get_iterators (point, k, neighbor_radius, std::back_inserter(neighbor_points));
for (std::size_t i = 0; i < neighbor_points.size(); ++ i)
{
@ -665,7 +639,7 @@ mst_orient_normals(
if (boost::is_same<ConstrainedMap,
typename CGAL::Point_set_processing_3::GetIsConstrainedMap<PointRange, NamedParameters>::NoMap>::value)
riemannian_graph = create_riemannian_graph(points.begin(), points.end(),
riemannian_graph = create_riemannian_graph(points,
point_map, normal_map, index_map,
Default_constrained_map<typename PointRange::iterator>
(mst_find_source(points.begin(), points.end(),
@ -675,7 +649,7 @@ mst_orient_normals(
neighbor_radius,
kernel);
else
riemannian_graph = create_riemannian_graph(points.begin(), points.end(),
riemannian_graph = create_riemannian_graph(points,
point_map, normal_map, index_map,
constrained_map,
k,

View File

@ -18,9 +18,9 @@
#include <CGAL/IO/trace.h>
#include <CGAL/Dimension.h>
#include <CGAL/Search_traits_3.h>
#include <CGAL/Orthogonal_k_neighbor_search.h>
#include <CGAL/Point_set_processing_3/internal/neighbor_query.h>
#include <CGAL/Point_set_processing_3/internal/Neighbor_query.h>
#include <CGAL/Point_set_processing_3/internal/Callback_wrapper.h>
#include <CGAL/for_each.h>
#include <CGAL/linear_least_squares_fitting_3.h>
#include <CGAL/property_map.h>
#include <CGAL/point_set_processing_assertions.h>
@ -33,13 +33,6 @@
#include <iterator>
#include <list>
#ifdef CGAL_LINKED_WITH_TBB
#include <CGAL/Point_set_processing_3/internal/Parallel_callback.h>
#include <tbb/parallel_for.h>
#include <tbb/blocked_range.h>
#include <tbb/scalable_allocator.h>
#endif // CGAL_LINKED_WITH_TBB
namespace CGAL {
@ -58,22 +51,20 @@ namespace internal {
/// @tparam Tree KD-tree.
///
/// @return Computed normal. Orientation is random.
template < typename Kernel,
typename Tree
>
typename Kernel::Vector_3
pca_estimate_normal(const typename Kernel::Point_3& query, ///< point to compute the normal at
const Tree& tree, ///< KD-tree
template <typename NeighborQuery>
typename NeighborQuery::Kernel::Vector_3
pca_estimate_normal(const typename NeighborQuery::Kernel::Point_3& query, ///< point to compute the normal at
const NeighborQuery& neighbor_query, ///< KD-tree
unsigned int k, ///< number of neighbors
typename Kernel::FT neighbor_radius)
typename NeighborQuery::Kernel::FT neighbor_radius)
{
// basic geometric types
typedef typename NeighborQuery::Kernel Kernel;
typedef typename Kernel::Point_3 Point;
typedef typename Kernel::Plane_3 Plane;
std::vector<Point> points;
CGAL::Point_set_processing_3::internal::neighbor_query
(query, tree, k, neighbor_radius, points);
neighbor_query.get_points (query, k, neighbor_radius, std::back_inserter(points));
// performs plane fitting by point-based PCA
Plane plane;
@ -83,48 +74,6 @@ pca_estimate_normal(const typename Kernel::Point_3& query, ///< point to compute
return plane.orthogonal_vector();
}
#ifdef CGAL_LINKED_WITH_TBB
template <typename Kernel, typename Tree>
class PCA_estimate_normals {
typedef typename Kernel::FT FT;
typedef typename Kernel::Point_3 Point;
typedef typename Kernel::Vector_3 Vector;
const Tree& tree;
const unsigned int k;
const FT neighbor_radius;
const std::vector<Point>& input;
std::vector<Vector>& output;
cpp11::atomic<std::size_t>& advancement;
cpp11::atomic<bool>& interrupted;
public:
PCA_estimate_normals(Tree& tree, unsigned int k, FT neighbor_radius,
std::vector<Point>& points,
std::vector<Vector>& output,
cpp11::atomic<std::size_t>& advancement,
cpp11::atomic<bool>& interrupted)
: tree(tree), k (k), neighbor_radius (neighbor_radius)
, input (points), output (output)
, advancement (advancement)
, interrupted (interrupted)
{ }
void operator()(const tbb::blocked_range<std::size_t>& r) const
{
for( std::size_t i = r.begin(); i != r.end(); ++i)
{
if (interrupted)
break;
output[i] = CGAL::internal::pca_estimate_normal<Kernel,Tree>(input[i], tree, k, neighbor_radius);
++ advancement;
}
}
};
#endif // CGAL_LINKED_WITH_TBB
} /* namespace internal */
/// \endcond
@ -206,15 +155,12 @@ pca_estimate_normals(
const std::function<bool(double)>& callback = choose_parameter(get_parameter(np, internal_np::callback),
std::function<bool(double)>());
typedef typename Kernel::Point_3 Point;
// Input points types
typedef typename boost::property_traits<NormalMap>::value_type Vector;
typedef typename PointRange::iterator iterator;
typedef typename iterator::value_type value_type;
// types for K nearest neighbors search structure
typedef typename CGAL::Search_traits_3<Kernel> Tree_traits;
typedef typename CGAL::Orthogonal_k_neighbor_search<Tree_traits> Neighbor_search;
typedef typename Neighbor_search::Tree Tree;
typedef Point_set_processing_3::internal::Neighbor_query<Kernel, PointRange&, PointMap> Neighbor_query;
// precondition: at least one element in the container.
// to fix: should have at least three distinct points
@ -227,59 +173,33 @@ pca_estimate_normals(
std::size_t memory = CGAL::Memory_sizer().virtual_size(); CGAL_TRACE(" %ld Mb allocated\n", memory>>20);
CGAL_TRACE(" Creates KD-tree\n");
typename PointRange::iterator it;
// Instanciate a KD-tree search.
// Note: We have to convert each input iterator to Point_3.
std::vector<Point> kd_tree_points;
for(it = points.begin(); it != points.end(); it++)
kd_tree_points.push_back(get(point_map, *it));
Tree tree(kd_tree_points.begin(), kd_tree_points.end());
Neighbor_query neighbor_query (points, point_map);
memory = CGAL::Memory_sizer().virtual_size(); CGAL_TRACE(" %ld Mb allocated\n", memory>>20);
CGAL_TRACE(" Computes normals\n");
// iterate over input points, compute and output normal
// vectors (already normalized)
#ifndef CGAL_LINKED_WITH_TBB
CGAL_static_assertion_msg (!(boost::is_convertible<ConcurrencyTag, Parallel_tag>::value),
"Parallel_tag is enabled but TBB is unavailable.");
#else
if (boost::is_convertible<ConcurrencyTag,Parallel_tag>::value)
{
Point_set_processing_3::internal::Parallel_callback
parallel_callback (callback, kd_tree_points.size());
std::size_t nb_points = points.size();
std::vector<Vector> normals (kd_tree_points.size (),
CGAL::NULL_VECTOR);
CGAL::internal::PCA_estimate_normals<Kernel, Tree>
f (tree, k, neighbor_radius, kd_tree_points, normals,
parallel_callback.advancement(),
parallel_callback.interrupted());
tbb::parallel_for(tbb::blocked_range<size_t>(0, kd_tree_points.size ()), f);
unsigned int i = 0;
for(it = points.begin(); it != points.end(); ++ it, ++ i)
if (normals[i] != CGAL::NULL_VECTOR)
put (normal_map, *it, normals[i]);
Point_set_processing_3::internal::Callback_wrapper<ConcurrencyTag>
callback_wrapper (callback, nb_points);
parallel_callback.join();
}
else
#endif
{
std::size_t nb = 0;
for(it = points.begin(); it != points.end(); it++, ++ nb)
{
Vector normal = internal::pca_estimate_normal<Kernel,Tree>(
get(point_map,*it),
tree,
k, neighbor_radius);
CGAL::for_each<ConcurrencyTag>
(points,
[&](value_type& vt)
{
if (callback_wrapper.interrupted())
return false;
put(normal_map, *it, normal); // normal_map[it] = normal
if (callback && !callback ((nb+1) / double(kd_tree_points.size())))
break;
}
}
put (normal_map, vt,
CGAL::internal::pca_estimate_normal
(get(point_map, vt), neighbor_query, k, neighbor_radius));
++ callback_wrapper.advancement();
return true;
});
callback_wrapper.join();
memory = CGAL::Memory_sizer().virtual_size(); CGAL_TRACE(" %ld Mb allocated\n", memory>>20);
CGAL_TRACE("End of pca_estimate_normals()\n");

View File

@ -16,9 +16,7 @@
#include <CGAL/disable_warnings.h>
#include <CGAL/Search_traits_3.h>
#include <CGAL/Orthogonal_k_neighbor_search.h>
#include <CGAL/Point_set_processing_3/internal/neighbor_query.h>
#include <CGAL/Point_set_processing_3/internal/Neighbor_query.h>
#include <CGAL/property_map.h>
#include <CGAL/point_set_processing_assertions.h>
#include <functional>
@ -49,22 +47,21 @@ namespace internal {
/// @tparam Tree KD-tree.
///
/// @return computed distance.
template < typename Kernel,
typename Tree >
typename Kernel::FT
template <typename NeighborQuery>
typename NeighborQuery::Kernel::FT
compute_avg_knn_sq_distance_3(
const typename Kernel::Point_3& query, ///< 3D point to project
Tree& tree, ///< KD-tree
const typename NeighborQuery::Kernel::Point_3& query, ///< 3D point to project
NeighborQuery& neighbor_query, ///< KD-tree
unsigned int k, ///< number of neighbors
typename Kernel::FT neighbor_radius)
typename NeighborQuery::Kernel::FT neighbor_radius)
{
// geometric types
typedef typename NeighborQuery::Kernel Kernel;
typedef typename Kernel::FT FT;
typedef typename Kernel::Point_3 Point;
std::vector<Point> points;
CGAL::Point_set_processing_3::internal::neighbor_query
(query, tree, k, neighbor_radius, points);
neighbor_query.get_points (query, k, neighbor_radius, std::back_inserter(points));
// compute average squared distance
typename Kernel::Compute_squared_distance_3 sqd;
@ -162,15 +159,14 @@ remove_outliers(
typedef typename Kernel::FT FT;
// basic geometric types
typedef typename Kernel::Point_3 Point;
typedef typename PointRange::iterator iterator;
typedef typename iterator::value_type value_type;
// actual type of input points
typedef typename std::iterator_traits<typename PointRange::iterator>::value_type Enriched_point;
// types for K nearest neighbors search structure
typedef typename CGAL::Search_traits_3<Kernel> Tree_traits;
typedef typename CGAL::Orthogonal_k_neighbor_search<Tree_traits> Neighbor_search;
typedef typename Neighbor_search::Tree Tree;
typedef Point_set_processing_3::internal::Neighbor_query<Kernel, PointRange&, PointMap> Neighbor_query;
// precondition: at least one element in the container.
// to fix: should have at least three distinct points
@ -182,26 +178,22 @@ remove_outliers(
CGAL_point_set_processing_precondition(threshold_percent >= 0 && threshold_percent <= 100);
typename PointRange::iterator it;
Neighbor_query neighbor_query (points, point_map);
// Instanciate a KD-tree search.
// Note: We have to convert each input iterator to Point_3.
std::vector<Point> kd_tree_points;
for(it = points.begin(); it != points.end(); it++)
kd_tree_points.push_back( get(point_map, *it) );
Tree tree(kd_tree_points.begin(), kd_tree_points.end());
std::size_t nb_points = points.size();
// iterate over input points and add them to multimap sorted by distance to k
std::multimap<FT,Enriched_point> sorted_points;
std::size_t nb = 0;
for(it = points.begin(); it != points.end(); it++, ++ nb)
for(const value_type& vt : points)
{
FT sq_distance = internal::compute_avg_knn_sq_distance_3<Kernel>(
get(point_map,*it),
tree, k, neighbor_radius);
sorted_points.insert( std::make_pair(sq_distance, *it) );
if (callback && !callback ((nb+1) / double(kd_tree_points.size())))
FT sq_distance = internal::compute_avg_knn_sq_distance_3(
get(point_map, vt),
neighbor_query, k, neighbor_radius);
sorted_points.insert( std::make_pair(sq_distance, vt) );
if (callback && !callback ((nb+1) / double(nb_points)))
return points.end();
++ nb;
}
// Replaces [points.begin(), points.end()) range by the multimap content.

View File

@ -32,12 +32,8 @@
#include <cmath>
#include <ctime>
#ifdef CGAL_LINKED_WITH_TBB
#include <CGAL/Point_set_processing_3/internal/Parallel_callback.h>
#include <tbb/parallel_for.h>
#include <tbb/blocked_range.h>
#include <tbb/scalable_allocator.h>
#endif // CGAL_LINKED_WITH_TBB
#include <CGAL/Point_set_processing_3/internal/Callback_wrapper.h>
#include <CGAL/for_each.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Fuzzy_sphere.h>
@ -329,67 +325,6 @@ compute_density_weight_for_sample_point(
/// \endcond
#ifdef CGAL_LINKED_WITH_TBB
/// \cond SKIP_IN_MANUAL
/// This is for parallelization of function: compute_denoise_projection()
template <typename Kernel, typename Tree, typename RandomAccessIterator>
class Sample_point_updater
{
typedef typename Kernel::Point_3 Point;
typedef typename Kernel::FT FT;
std::vector<Point> &update_sample_points;
std::vector<Point> &sample_points;
const Tree &original_kd_tree;
const Tree &sample_kd_tree;
const typename Kernel::FT radius;
const std::vector<typename Kernel::FT> &original_densities;
const std::vector<typename Kernel::FT> &sample_densities;
cpp11::atomic<std::size_t>& advancement;
cpp11::atomic<bool>& interrupted;
public:
Sample_point_updater(
std::vector<Point> &out,
std::vector<Point> &in,
const Tree &_original_kd_tree,
const Tree &_sample_kd_tree,
const typename Kernel::FT _radius,
const std::vector<typename Kernel::FT> &_original_densities,
const std::vector<typename Kernel::FT> &_sample_densities,
cpp11::atomic<std::size_t>& advancement,
cpp11::atomic<bool>& interrupted):
update_sample_points(out),
sample_points(in),
original_kd_tree(_original_kd_tree),
sample_kd_tree(_sample_kd_tree),
radius(_radius),
original_densities(_original_densities),
sample_densities(_sample_densities),
advancement (advancement),
interrupted (interrupted) {}
void operator() ( const tbb::blocked_range<size_t>& r ) const
{
for (size_t i = r.begin(); i != r.end(); ++i)
{
if (interrupted)
break;
update_sample_points[i] = simplify_and_regularize_internal::
compute_update_sample_point<Kernel, Tree, RandomAccessIterator>(
sample_points[i],
original_kd_tree,
sample_kd_tree,
radius,
original_densities,
sample_densities);
++ advancement;
}
}
};
/// \endcond
#endif // CGAL_LINKED_WITH_TBB
// ----------------------------------------------------------------------------
// Public section
@ -529,7 +464,6 @@ wlop_simplify_and_regularize_point_set(
#endif
}
FT radius2 = radius * radius;
CGAL_point_set_processing_precondition(radius > 0);
// Initiate a KD-tree search for original points
@ -588,79 +522,50 @@ wlop_simplify_and_regularize_point_set(
sample_density_weights.push_back(density);
}
typedef boost::zip_iterator<boost::tuple<typename std::vector<Point>::iterator,
typename std::vector<Point>::iterator> > Zip_iterator;
typename std::vector<Point>::iterator update_iter = update_sample_points.begin();
#ifndef CGAL_LINKED_WITH_TBB
CGAL_static_assertion_msg (!(boost::is_convertible<ConcurrencyTag, Parallel_tag>::value),
"Parallel_tag is enabled but TBB is unavailable.");
#else
//parallel
if (boost::is_convertible<ConcurrencyTag, Parallel_tag>::value)
{
Point_set_processing_3::internal::Parallel_callback
parallel_callback (callback, iter_number * number_of_sample, iter_n * number_of_sample);
Point_set_processing_3::internal::Callback_wrapper<ConcurrencyTag>
callback_wrapper (callback, iter_number * number_of_sample, iter_n * number_of_sample);
tbb::blocked_range<size_t> block(0, number_of_sample);
Sample_point_updater<Kernel, Kd_Tree, typename PointRange::iterator> sample_updater(
update_sample_points,
sample_points,
original_kd_tree,
sample_kd_tree,
radius2,
original_density_weights,
sample_density_weights,
parallel_callback.advancement(),
parallel_callback.interrupted());
CGAL::for_each<ConcurrencyTag>
(CGAL::make_range (boost::make_zip_iterator (boost::make_tuple (sample_points.begin(), update_sample_points.begin())),
boost::make_zip_iterator (boost::make_tuple (sample_points.end(), update_sample_points.end()))),
[&](const typename Zip_iterator::reference& t)
{
if (callback_wrapper.interrupted())
return false;
tbb::parallel_for(block, sample_updater);
get<1>(t) = simplify_and_regularize_internal::
compute_update_sample_point<Kernel, Kd_Tree, typename PointRange::iterator>(
get<0>(t),
original_kd_tree,
sample_kd_tree,
radius,
original_density_weights,
sample_density_weights);
++ callback_wrapper.advancement();
bool interrupted = parallel_callback.interrupted();
return true;
});
// We interrupt by hand as counter only goes halfway and won't terminate by itself
parallel_callback.interrupted() = true;
parallel_callback.join();
bool interrupted = callback_wrapper.interrupted();
// If interrupted during this step, nothing is computed, we return NaN
if (interrupted)
return output;
}else
#endif
{
//sequential
std::size_t nb = iter_n * number_of_sample;
for (sample_iter = sample_points.begin();
sample_iter != sample_points.end(); ++sample_iter, ++update_iter, ++ nb)
{
*update_iter = simplify_and_regularize_internal::
compute_update_sample_point<Kernel,
Kd_Tree,
typename PointRange::iterator>
(*sample_iter,
original_kd_tree,
sample_kd_tree,
radius2,
original_density_weights,
sample_density_weights);
if (callback && !callback ((nb+1) / double(iter_number * number_of_sample)))
return output;
}
}
// We interrupt by hand as counter only goes halfway and won't terminate by itself
callback_wrapper.interrupted() = true;
callback_wrapper.join();
// If interrupted during this step, nothing is computed, we return NaN
if (interrupted)
return output;
sample_iter = sample_points.begin();
for (update_iter = update_sample_points.begin();
update_iter != update_sample_points.end();
++update_iter, ++sample_iter)
{
*sample_iter = *update_iter;
}
for (std::size_t i = 0; i < sample_points.size(); ++ i)
sample_points[i] = update_sample_points[i];
}
// final output
for(sample_iter = sample_points.begin();
sample_iter != sample_points.end(); ++sample_iter)
{
*output++ = *sample_iter;
}
std::copy (sample_points.begin(), sample_points.end(), output);
return output;
}

View File

@ -28,6 +28,11 @@ function of the inferred solid (Poisson Surface Reconstruction -
referred to as Poisson). Poisson is a two steps process: it requires
solving for the implicit function before function evaluation.
\note A \ref tuto_reconstruction "detailed tutorial on surface reconstruction"
is provided with a guide to choose the most appropriate method along
with pre- and post-processing.
\section Poisson_surface_reconstruction_3Common Common Reconstruction Pipeline
Surface reconstruction from point sets is often a sequential process

View File

@ -31,6 +31,8 @@ if ( CGAL_FOUND )
CGAL_target_use_Eigen(poisson_reconstruction)
create_single_source_cgal_program( "poisson_reconstruction_function.cpp" )
CGAL_target_use_Eigen(poisson_reconstruction_function)
create_single_source_cgal_program( "tutorial_example.cpp" )
CGAL_target_use_Eigen(tutorial_example)
else()
message(STATUS "NOTICE: The examples need Eigen 3.1 (or greater) will not be compiled.")
endif()

View File

@ -0,0 +1,3 @@
data/kitten.xyz
data/kitten.xyz 1
data/kitten.xyz 2

View File

@ -0,0 +1,223 @@
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Point_set_3.h>
#include <CGAL/Point_set_3/IO.h>
#include <CGAL/remove_outliers.h>
#include <CGAL/grid_simplify_point_set.h>
#include <CGAL/jet_smooth_point_set.h>
#include <CGAL/jet_estimate_normals.h>
#include <CGAL/mst_orient_normals.h>
#include <CGAL/poisson_surface_reconstruction.h>
#include <CGAL/Advancing_front_surface_reconstruction.h>
#include <CGAL/Scale_space_surface_reconstruction_3.h>
#include <CGAL/Scale_space_reconstruction_3/Jet_smoother.h>
#include <CGAL/Scale_space_reconstruction_3/Advancing_front_mesher.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h>
#include <cstdlib>
#include <vector>
#include <fstream>
// types
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::FT FT;
typedef Kernel::Point_3 Point_3;
typedef Kernel::Vector_3 Vector_3;
typedef Kernel::Sphere_3 Sphere_3;
typedef CGAL::Point_set_3<Point_3, Vector_3> Point_set;
int main(int argc, char*argv[])
{
///////////////////////////////////////////////////////////////////
//! [Reading input]
Point_set points;
if (argc < 2)
{
std::cerr << "Usage: " << argv[0] << " [input.xyz/off/ply/las]" << std::endl;
return EXIT_FAILURE;
}
const char* input_file = argv[1];
std::ifstream stream (input_file, std::ios_base::binary);
if (!stream)
{
std::cerr << "Error: cannot read file " << input_file << std::endl;
return EXIT_FAILURE;
}
stream >> points;
std::cout << "Read " << points.size () << " point(s)" << std::endl;
if (points.empty())
return EXIT_FAILURE;
//! [Reading input]
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
//! [Outlier removal]
CGAL::remove_outliers (points,
24, // Number of neighbors considered for evaluation
points.parameters().threshold_percent (5.0)); // Percentage of points to remove
std::cout << points.number_of_removed_points()
<< " point(s) are outliers." << std::endl;
// Applying point set processing algorithm to a CGAL::Point_set_3
// object does not erase the points from memory but place them in
// the garbage of the object: memory can be freeed by the user.
points.collect_garbage();
//! [Outlier removal]
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
//! [Simplification]
// Compute average spacing using neighborhood of 6 points
double spacing = CGAL::compute_average_spacing<CGAL::Sequential_tag> (points, 6);
// Simplify using a grid of size 2 * average spacing
CGAL::grid_simplify_point_set (points, 2. * spacing);
std::cout << points.number_of_removed_points()
<< " point(s) removed after simplification." << std::endl;
points.collect_garbage();
//! [Simplification]
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
//! [Smoothing]
CGAL::jet_smooth_point_set<CGAL::Sequential_tag> (points, 24);
//! [Smoothing]
///////////////////////////////////////////////////////////////////
unsigned int reconstruction_choice
= (argc < 3 ? 0 : atoi(argv[2]));
if (reconstruction_choice == 0) // Poisson
{
///////////////////////////////////////////////////////////////////
//! [Normal estimation]
CGAL::jet_estimate_normals<CGAL::Sequential_tag>
(points, 24); // Use 24 neighbors
// Orientation of normals, returns iterator to first unoriented point
typename Point_set::iterator unoriented_points_begin =
CGAL::mst_orient_normals(points, 24); // Use 24 neighbors
points.remove (unoriented_points_begin, points.end());
//! [Normal estimation]
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
//! [Poisson reconstruction]
CGAL::Surface_mesh<Point_3> output_mesh;
CGAL::poisson_surface_reconstruction_delaunay
(points.begin(), points.end(),
points.point_map(), points.normal_map(),
output_mesh, spacing);
//! [Poisson reconstruction]
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
//! [Output poisson]
std::ofstream f ("out.ply", std::ios_base::binary);
CGAL::set_binary_mode (f);
CGAL::write_ply (f, output_mesh);
f.close ();
//! [Output poisson]
///////////////////////////////////////////////////////////////////
}
else if (reconstruction_choice == 1) // Advancing front
{
///////////////////////////////////////////////////////////////////
//! [Advancing front reconstruction]
typedef std::array<std::size_t, 3> Facet; // Triple of indices
std::vector<Facet> facets;
// The function is called using directly the points raw iterators
CGAL::advancing_front_surface_reconstruction(points.points().begin(),
points.points().end(),
std::back_inserter(facets));
std::cout << facets.size ()
<< " facet(s) generated by reconstruction." << std::endl;
//! [Advancing front reconstruction]
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
//! [Output advancing front]
// copy points for random access
std::vector<Point_3> vertices;
vertices.reserve (points.size());
std::copy (points.points().begin(), points.points().end(), std::back_inserter (vertices));
CGAL::Surface_mesh<Point_3> output_mesh;
CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh (vertices, facets, output_mesh);
std::ofstream f ("out.off");
f << output_mesh;
f.close ();
//! [Output advancing front]
///////////////////////////////////////////////////////////////////
}
else if (reconstruction_choice == 2) // Scale space
{
///////////////////////////////////////////////////////////////////
//! [Scale space reconstruction]
CGAL::Scale_space_surface_reconstruction_3<Kernel> reconstruct
(points.points().begin(), points.points().end());
// Smooth using 4 iterations of Jet Smoothing
reconstruct.increase_scale (4, CGAL::Scale_space_reconstruction_3::Jet_smoother<Kernel>());
// Mesh with the Advancing Front mesher with a maximum facet length of 0.5
reconstruct.reconstruct_surface (CGAL::Scale_space_reconstruction_3::Advancing_front_mesher<Kernel>(0.5));
//! [Scale space reconstruction]
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
//! [Output scale space]
std::ofstream f ("out.off");
f << "OFF" << std::endl << points.size () << " "
<< reconstruct.number_of_facets() << " 0" << std::endl;
for (Point_set::Index idx : points)
f << points.point (idx) << std::endl;
for (const auto& facet : CGAL::make_range (reconstruct.facets_begin(), reconstruct.facets_end()))
f << "3 "<< facet << std::endl;
f.close ();
//! [Output scale space]
///////////////////////////////////////////////////////////////////
}
else // Handle error
{
std::cerr << "Error: invalid reconstruction id: " << reconstruction_choice << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -47,6 +47,7 @@
#include <boost/array.hpp>
#include <boost/type_traits/is_convertible.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/iterator/indirect_iterator.hpp>
/*!
\file Poisson_reconstruction_function.h
@ -104,18 +105,6 @@ struct Poisson_visitor {
{}
};
struct Poisson_skip_vertices {
double ratio;
Random& m_random;
Poisson_skip_vertices(const double ratio, Random& random)
: ratio(ratio), m_random(random) {}
template <typename Iterator>
bool operator()(Iterator) const {
return m_random.get_double() < ratio;
}
};
// Given f1 and f2, two sizing fields, that functor wrapper returns
// max(f1, f2*f2)
// The wrapper stores only pointers to the two functors.
@ -415,11 +404,15 @@ public:
// then the cell is considered as small enough, and the first sizing
// field, more costly, is not evaluated.
typedef Filter_iterator<typename Triangulation::Input_point_iterator,
Poisson_skip_vertices> Some_points_iterator;
//make it deterministic
Random random(0);
Poisson_skip_vertices skip(1.-approximation_ratio,random);
double ratio = 1.-approximation_ratio;
std::vector<typename Triangulation::Input_point_iterator> some_points;
for (typename Triangulation::Input_point_iterator
it = m_tr->input_points_begin(); it != m_tr->input_points_end(); ++ it)
if (random.get_double() >= ratio)
some_points.push_back (it);
CGAL_TRACE_STREAM << "SPECIAL PASS that uses an approximation of the result (approximation ratio: "
<< approximation_ratio << ")" << std::endl;
@ -427,11 +420,8 @@ public:
CGAL::Timer sizing_field_timer; sizing_field_timer.start();
Poisson_reconstruction_function<Geom_traits>
coarse_poisson_function(Some_points_iterator(m_tr->input_points_end(),
skip,
m_tr->input_points_begin()),
Some_points_iterator(m_tr->input_points_end(),
skip),
coarse_poisson_function(boost::make_indirect_iterator (some_points.begin()),
boost::make_indirect_iterator (some_points.end()),
Normal_of_point_with_normal_map<Geom_traits>() );
coarse_poisson_function.compute_implicit_function(solver, Poisson_visitor(),
0.);

View File

@ -596,6 +596,15 @@ It is for example used in the fucntion `keep_large_connected_components()`.
<b>Default:</b> `false`
\cgalNPEnd
\cgalNPBegin{maximum_number_of_faces} \anchor PMP_maximum_number_of_faces
Parameter that specifies the maximum number of faces that a component
of a mesh can have to be considered reversible \n
If it is left default, then it is ignored and all components are considered reversible. \n
<b>Type:</b> `size_type` \n
<b>Default:</b> 0
\cgalNPEnd
\cgalNPTableEnd
*/

View File

@ -141,6 +141,9 @@ and provides a list of the parameters that are used in this package.
- `CGAL::Polygon_mesh_processing::volume_connected_components()`
- `CGAL::Polygon_mesh_processing::is_outward_oriented()`
- `CGAL::Polygon_mesh_processing::reverse_face_orientations()`
- `CGAL::Polygon_mesh_processing::duplicate_non_manifold_edges_in_polygon_soup()`
- `CGAL::Polygon_mesh_processing::orient_triangle_soup_with_reference_triangle_mesh()`
- `CGAL::Polygon_mesh_processing::merge_reversible_connected_components()`
\cgalCRPSection{Combinatorial Repairing Functions}
- `CGAL::Polygon_mesh_processing::merge_duplicate_points_in_polygon_soup()`
@ -200,6 +203,7 @@ and provides a list of the parameters that are used in this package.
- `CGAL::Polygon_mesh_processing::approximate_max_distance_to_point_set()`
- `CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh()`
- `CGAL::Polygon_mesh_processing::sample_triangle_mesh()`
- `CGAL::Polygon_mesh_processing::sample_triangle_soup()`
\cgalCRPSection{Feature Detection Functions}
- `CGAL::Polygon_mesh_processing::sharp_edges_segmentation()`

View File

@ -601,9 +601,13 @@ As a consequence, the normal computed for each face (see Section
- The function `CGAL::Polygon_mesh_processing::volume_connected_components()` provides information about
the 3D arrangement of the surface connected components in a given triangle mesh. It comes with many
named parameter options making it also a more general version of `is_outward_oriented()`.
\subsection PolygonSoupExample Orientation Example
- The function `CGAL::Polygon_mesh_processing::duplicate_non_manifold_edges_in_polygon_soup()`
duplicates points and edges to make a soup orientable, without changing the orientation of the faces.
- The function `CGAL::Polygon_mesh_processing::orient_triangle_soup_with_reference_triangle_mesh()`
takes an input mesh as a reference and orients the triangles of a soup according to it.
- The function `CGAL::Polygon_mesh_processing::merge_reversible_connected_components()`
merges the connected components of a polygon mesh if possible.
\subsection PolygonSoupExample Orientation Examples
This example shows how to generate a mesh from a polygon soup.
The first step is to get a soup of consistently oriented faces, before
@ -614,6 +618,11 @@ Section \ref PMPOrientation.
\cgalExample{Polygon_mesh_processing/orient_polygon_soup_example.cpp}
This example shows how to correctly repair and orient a soup to get a mesh from a reference :
\cgalExample{Polygon_mesh_processing/orientation_pipeline_example.cpp}
****************************************
\section PMPRepairing Combinatorial Repairing

View File

@ -28,4 +28,5 @@
\example Polygon_mesh_processing/mesh_smoothing_example.cpp
\example Polygon_mesh_processing/shape_smoothing_example.cpp
\example Polygon_mesh_processing/locate_example.cpp
\example Polygon_mesh_processing/orientation_pipeline_example.cpp
*/

View File

@ -88,6 +88,7 @@ create_single_source_cgal_program( "manifoldness_repair_example.cpp" )
create_single_source_cgal_program( "repair_polygon_soup_example.cpp" )
create_single_source_cgal_program( "mesh_smoothing_example.cpp")
create_single_source_cgal_program( "locate_example.cpp")
create_single_source_cgal_program( "orientation_pipeline_example.cpp")
#create_single_source_cgal_program( "self_snapping_example.cpp")
#create_single_source_cgal_program( "snapping_example.cpp")
@ -109,8 +110,8 @@ target_link_libraries( point_inside_example_OM PRIVATE ${OPENMESH_LIBRARIES} )
create_single_source_cgal_program( "stitch_borders_example_OM.cpp" )
target_link_libraries( stitch_borders_example_OM PRIVATE ${OPENMESH_LIBRARIES} )
#create_single_source_cgal_program( "remove_degeneracies_example_OM.cpp")
#target_link_libraries( remove_degeneracies_example_OM PRIVATE ${OPENMESH_LIBRARIES} )
#create_single_source_cgal_program( "remove_degeneracies_example.cpp")
#target_link_libraries( remove_degeneracies_example PRIVATE ${OPENMESH_LIBRARIES} )
create_single_source_cgal_program( "triangulate_faces_example_OM.cpp")
target_link_libraries( triangulate_faces_example_OM PRIVATE ${OPENMESH_LIBRARIES} )
@ -118,8 +119,6 @@ endif(OpenMesh_FOUND)
find_package( TBB )
if( TBB_FOUND )
# CGAL_target_use_TBB(self_snapping_example)
# CGAL_target_use_TBB(snapping_example)
CGAL_target_use_TBB(self_intersections_example)
CGAL_target_use_TBB(hausdorff_distance_remeshing_example)
else()
@ -131,4 +130,3 @@ if(TARGET ceres)
target_compile_definitions( mesh_smoothing_example PRIVATE CGAL_PMP_USE_CERES_SOLVER )
target_link_libraries( mesh_smoothing_example PRIVATE ceres )
endif(TARGET ceres)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,114 @@
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/orientation.h>
#include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h>
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup_extension.h>
#include <CGAL/algorithm.h>
#include <CGAL/Timer.h>
#include <fstream>
#include <algorithm>
#include <cstdlib>
int main(int argc, char** argv)
{
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::Point_3 Point_3;
typedef CGAL::Surface_mesh<Point_3> Mesh;
std::vector<Point_3> points;
std::vector< std::vector<std::size_t> > polygons;
Mesh ref1;
const char* input_filename = argc<2 ? "data/blobby-shuffled.off" : argv[1];
std::ifstream input(input_filename);
if ( !input){
std::cerr << "Error: can not read input file.\n";
return 1;
}
typedef K::Point_3 Point_3;
CGAL::File_scanner_OFF scanner(input);
points.resize(scanner.size_of_vertices());
polygons.resize(scanner.size_of_facets());
//read points
for (std::size_t i = 0; i < scanner.size_of_vertices(); ++i)
{
double x, y, z, w;
scanner.scan_vertex( x, y, z, w);
points[i] = Point_3(x, y, z, w);
scanner.skip_to_next_vertex( i);
}
//read triangles
for (std::size_t i = 0; i < scanner.size_of_facets(); ++i)
{
std::size_t no;
scanner.scan_facet( no, i);
polygons[i].resize(no);
for(std::size_t j = 0; j < no; ++j) {
std::size_t id;
scanner.scan_facet_vertex_index(id, i);
if(id < scanner.size_of_vertices())
{
polygons[i][j] = id;
}
else
{
std::cerr << "Error: input file not valid.\n";
return 1;
}
}
}
input.close();
if(points.size() == 0 || polygons.size()==0)
{
std::cerr << "Error: input file not valid.\n";
return 1;
}
const char* reference_filename = argc<2 ? "data/blobby.off" : argv[2];
input.open(reference_filename);
if ( !input || !(input >> ref1)){
std::cerr << "Error: can not read reference file.\n";
return 1;
}
input.close();
std::cout << "Is the soup a polygon mesh ? : "
<< CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh(polygons)
<< std::endl;
CGAL::Polygon_mesh_processing::orient_triangle_soup_with_reference_triangle_mesh<CGAL::Sequential_tag>(ref1, points, polygons);
std::cout << "And now ? : "
<< CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh(polygons)
<< std::endl;
CGAL::Polygon_mesh_processing::duplicate_non_manifold_edges_in_polygon_soup(points, polygons);
std::cout << "And now ? : "
<< CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh(polygons)
<< std::endl;
Mesh poly;
CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(
points, polygons, poly);
typedef boost::property_map<Mesh, CGAL::dynamic_face_property_t<std::size_t> >::type Fccmap;
Fccmap fccmap = get(CGAL::dynamic_face_property_t<std::size_t>(), poly);
std::cout<<CGAL::Polygon_mesh_processing::
connected_components(poly, fccmap)<<" CCs before merge."<<std::endl;
CGAL::Polygon_mesh_processing::
merge_reversible_connected_components(poly);
std::cout<<CGAL::Polygon_mesh_processing::
connected_components(poly, fccmap)<<" remaining CCs."<<std::endl;
return 0;
}

View File

@ -1,75 +0,0 @@
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/internal/Snapping/snap.h>
#include <CGAL/Polygon_mesh_processing/stitch_borders.h>
#include <iostream>
#include <fstream>
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::FT FT;
typedef Kernel::Point_3 Point_3;
typedef Kernel::Vector_3 Vector_3;
typedef CGAL::Surface_mesh<Point_3> Surface_mesh;
typedef boost::graph_traits<Surface_mesh>::vertex_descriptor vertex_descriptor;
namespace PMP = CGAL::Polygon_mesh_processing;
int main(int argc, char** argv)
{
std::cout.precision(17);
if(argc != 3)
{
std::cerr << "Usage: " << argv[0] << " input_mesh tolerance" << std::endl;
return EXIT_FAILURE;
}
Surface_mesh sm;
std::ifstream in(argv[1]);
if(!in || in >> sm)
{
std::cerr << "Problem loading the input data" << std::endl;
return EXIT_FAILURE;
}
Surface_mesh::Property_map<vertex_descriptor, double> tolerance_map;
tolerance_map = sm.add_property_map<vertex_descriptor, double>("v:t").first;
for(vertex_descriptor v : vertices(sm))
put(tolerance_map, v, std::atof(argv[3]));
std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now();
// Snap
std::size_t nb_snapped = PMP::experimental::snap_borders<CGAL::Parallel_tag>(sm, tolerance_map);
std::cout << "#snapped: " << nb_snapped << std::endl;
std::chrono::steady_clock::time_point snap_time = std::chrono::steady_clock::now();
std::cout << "Time elapsed (snap): "
<< std::chrono::duration_cast<std::chrono::milliseconds>(snap_time - start_time).count()
<< "ms" << std::endl;
// Stitch
std::cout << "Stitch, #ne: " << edges(sm).size() << std::endl;
PMP::stitch_borders(sm);
std::chrono::steady_clock::time_point stitch_time = std::chrono::steady_clock::now();
std::cout << "Time elapsed (stitch): "
<< std::chrono::duration_cast<std::chrono::milliseconds>(stitch_time - snap_time).count()
<< "ms" << std::endl;
std::chrono::steady_clock::time_point end_time = std::chrono::steady_clock::now();
std::cout << "Total time elapsed: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count()
<< "ms" << std::endl;
std::cout << "#border: " << PMP::number_of_borders(sm) << std::endl;
std::cout << "Done!" << std::endl;
std::ofstream("snapped.off") << std::setprecision(17) << sm;
return EXIT_SUCCESS;
}

View File

@ -1,78 +0,0 @@
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/internal/Snapping/snap.h>
#include <iostream>
#include <fstream>
typedef CGAL::Simple_cartesian<double> Kernel;
typedef Kernel::Point_3 Point_3;
typedef CGAL::Surface_mesh<Point_3> Surface_mesh;
typedef boost::graph_traits<Surface_mesh>::vertex_descriptor vertex_descriptor;
namespace PMP = CGAL::Polygon_mesh_processing;
int main(int argc, char** argv)
{
if(argc != 4)
{
std::cerr << "Usage: " << argv[0] << " movable_mesh fixed_mesh tolerance" << std::endl;
return EXIT_FAILURE;
}
Surface_mesh movable_mesh, fixed_mesh;
std::ifstream in_m(argv[1]);
if(!in_m || in_m >> movable_mesh)
{
std::cerr << "Problem loading the input data" << std::endl;
return EXIT_FAILURE;
}
std::ifstream in_f(argv[2]);
if(!in_f || in_f >> fixed_mesh)
{
std::cerr << "Problem loading the input data" << std::endl;
return EXIT_FAILURE;
}
std::cout << "Movable mesh: " << num_vertices(movable_mesh) << std::endl;
std::cout << "Fixed mesh: " << num_vertices(fixed_mesh) << std::endl;
const double tolerance = std::atof(argv[3]);
Surface_mesh::Property_map<vertex_descriptor, double> movable_tolerance_map;
movable_tolerance_map = movable_mesh.add_property_map<vertex_descriptor, double>("v:t").first;
for(vertex_descriptor v : vertices(movable_mesh))
put(movable_tolerance_map, v, tolerance);
Surface_mesh::Property_map<vertex_descriptor, double> fixed_tolerance_map;
fixed_tolerance_map = fixed_mesh.add_property_map<vertex_descriptor, double>("v:t").first;
for(vertex_descriptor v : vertices(fixed_mesh))
put(fixed_tolerance_map, v, tolerance);
std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now();
// Choice of named parameters indicates that:
// - We want to simplify the boundary of the first mesh
// - The geometry of the second mesh cannot change
std::size_t nb_snapped = PMP::experimental::snap_borders(movable_mesh, movable_tolerance_map,
fixed_mesh, fixed_tolerance_map,
CGAL::parameters::do_simplify_border(true),
CGAL::parameters::do_lock_mesh(true));
std::chrono::steady_clock::time_point end_time = std::chrono::steady_clock::now();
std::cout << "Time elapsed: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count()
<< "ms" << std::endl;
std::cout << "#Snapped: " << nb_snapped << std::endl;
std::ofstream("snapped_movable.off") << std::setprecision(17) << movable_mesh;
std::ofstream("snapped_fixed.off") << std::setprecision(17) << fixed_mesh;
return EXIT_SUCCESS;
}

View File

@ -547,6 +547,65 @@ bool clip(TriangleMesh& tm,
return clip(tm, clipper, np, parameters::all_default());
}
/**
* \ingroup PMP_corefinement_grp
* clips `tm` by keeping the part that is inside `iso_cuboid`.
* If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volume` is set to `true`.
* See Subsection \ref coref_clip for more details.
*
* \note In the current implementation it is not possible to set the vertex point map and the default will be used. `Iso_cuboid_3` must be
* from the same %Kernel as the point of the vertex point map.
* \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm)` \endlink
*
* @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`.
* An internal property map for `CGAL::vertex_point_t` must be available.
*
* @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters"
*
* @param tm input triangulated surface mesh
* @param iso_cuboid iso-cuboid used to clip `tm`.
* @param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below
*
* \cgalNamedParamsBegin
* \cgalParamBegin{visitor} a class model of `PMPCorefinementVisitor`
* that is used to track the creation of new faces.
* \cgalParamEnd
* \cgalParamBegin{throw_on_self_intersection} if `true`,
* the set of triangles closed to the intersection of `tm` and `iso_cuboid` will be
* checked for self-intersections and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception`
* will be thrown if at least one is found.
* \cgalParamEnd
* \cgalParamBegin{clip_volume} if `true` and `tm` is closed, the clipping will be done on
* the volume \link coref_def_subsec bounded \endlink by `tm` rather than on its surface
* (i.e., `tm` will be kept closed).
* \cgalParamEnd
* \cgalParamBegin{use_compact_clipper} if `false` and `clip_volume` is `false` and `tm` is open, the parts of `tm` coplanar with `is_cuboid`
* will not be part of the output.
* \cgalNamedParamsEnd
*
* @return `true` if the output surface mesh is manifold.
* If `false` is returned `tm` is only refined by the intersection with `iso_cuboid`.
*/
template <class TriangleMesh,
class NamedParameters>
bool clip(TriangleMesh& tm,
#ifdef DOXYGEN_RUNNING
const Iso_cuboid_3& iso_cuboid,
#else
const typename GetGeomTraits<TriangleMesh, NamedParameters>::type::Iso_cuboid_3& iso_cuboid,
#endif
const NamedParameters& np)
{
if(boost::begin(faces(tm))==boost::end(faces(tm))) return true;
TriangleMesh clipper;
make_hexahedron(iso_cuboid[0], iso_cuboid[1], iso_cuboid[2], iso_cuboid[3],
iso_cuboid[4], iso_cuboid[5], iso_cuboid[6], iso_cuboid[7],
clipper);
triangulate_faces(clipper);
return clip(tm, clipper, np, parameters::all_default());
}
/*!
* \ingroup PMP_corefinement_grp
@ -635,9 +694,6 @@ void split(TriangleMesh& tm,
* @param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below
*
* \cgalNamedParamsBegin
* \cgalParamBegin{vertex_point_map}
* the property map with the points associated to the vertices of `tm`.
* \cgalParamEnd
* \cgalParamBegin{visitor} a class model of `PMPCorefinementVisitor`
* that is used to track the creation of new faces.
* \cgalParamEnd
@ -680,23 +736,22 @@ void split(TriangleMesh& tm,
//else nothing to do, no intersection.
}
/**
* \ingroup PMP_corefinement_grp
* clips `tm` by keeping the part that is inside `iso_cuboid`.
* If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volume` is set to `true`.
* See Subsection \ref coref_clip for more details.
* adds intersection edges of `iso_cuboid` and `tm` in `tm` and duplicates those edges.
*
* \note In the current implementation it is not possible to set the vertex point map and the default will be used. `Iso_cuboid_3` must be
* from the same %Kernel as the point of the vertex point map.
* \note In the current implementation it is not possible to set the vertex point map and the default will be used.
* \note `Iso_cuboid_3` must be from the same %Kernel as the point of the vertex point map.
* \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm)` \endlink
*
* @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`.
* @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`
* An internal property map for `CGAL::vertex_point_t` must be available.
*
* @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters"
*
* @param tm input triangulated surface mesh
* @param iso_cuboid iso-cuboid used to clip `tm`.
* @param iso_cuboid iso-cuboid used to split `tm`.
* @param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below
*
* \cgalNamedParamsBegin
@ -708,36 +763,26 @@ void split(TriangleMesh& tm,
* checked for self-intersections and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception`
* will be thrown if at least one is found.
* \cgalParamEnd
* \cgalParamBegin{clip_volume} if `true` and `tm` is closed, the clipping will be done on
* the volume \link coref_def_subsec bounded \endlink by `tm` rather than on its surface
* (i.e., `tm` will be kept closed).
* \cgalParamEnd
* \cgalParamBegin{use_compact_clipper} if `false` and `clip_volume` is `false` and `tm` is open, the parts of `tm` coplanar with `is_cuboid`
* will not be part of the output.
* \cgalNamedParamsEnd
*
* @return `true` if the output surface mesh is manifold.
* If `false` is returned `tm` is only refined by the intersection with `iso_cuboid`.
*/
template <class TriangleMesh,
class NamedParameters>
bool clip(TriangleMesh& tm,
#ifdef DOXYGEN_RUNNING
const Iso_cuboid_3& iso_cuboid,
#else
const typename GetGeomTraits<TriangleMesh, NamedParameters>::type::Iso_cuboid_3& iso_cuboid,
#endif
const NamedParameters& np)
void split(TriangleMesh& tm,
#ifdef DOXYGEN_RUNNING
const Iso_cuboid_3& iso_cuboid,
#else
const typename GetGeomTraits<TriangleMesh, NamedParameters>::type::Iso_cuboid_3& iso_cuboid,
#endif
const NamedParameters& np)
{
if(boost::begin(faces(tm))==boost::end(faces(tm))) return true;
TriangleMesh clipper;
TriangleMesh splitter;
make_hexahedron(iso_cuboid[0], iso_cuboid[1], iso_cuboid[2], iso_cuboid[3],
iso_cuboid[4], iso_cuboid[5], iso_cuboid[6], iso_cuboid[7],
clipper);
triangulate_faces(clipper);
iso_cuboid[4], iso_cuboid[5], iso_cuboid[6], iso_cuboid[7],
splitter);
triangulate_faces(splitter);
return clip(tm, clipper, np, parameters::all_default());
split(tm, splitter, np, parameters::all_default());
}
/// \cond SKIP_IN_MANUAL
@ -806,6 +851,13 @@ void split(TriangleMesh& tm,
split(tm, plane, parameters::all_default());
}
template <class TriangleMesh>
void split(TriangleMesh& tm,
const typename GetGeomTraits<TriangleMesh>::type::Iso_cuboid_3& iso_cuboid)
{
split(tm, iso_cuboid, parameters::all_default());
}
/// \endcond
} } //end of namespace CGAL::Polygon_mesh_processing

View File

@ -98,6 +98,12 @@ void sum_normals(const PM& pmesh,
const Point_ref pvnn = get(vpmap, target(next(he, pmesh), pmesh));
const Vector n = internal::triangle_normal(pv, pvn, pvnn, traits);
#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP
std::cout << "Normal of " << f << " pts: " << pv << " ; " << pvn << " ; " << pvnn << std::endl;
std::cout << " --> " << n << std::endl;
#endif
sum = traits.construct_sum_of_vectors_3_object()(sum, n);
he = next(he, pmesh);
@ -206,7 +212,7 @@ void compute_face_normals(const PolygonMesh& pmesh,
{
typename Kernel::Vector_3 vec = compute_face_normal(f, pmesh, np);
put(face_normals, f, vec);
#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG
#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP
std::cout << "normal at face " << f << " is " << get(face_normals, f) << std::endl;
#endif
}
@ -522,6 +528,10 @@ compute_vertex_normal_as_sum_of_weighted_normals(typename boost::graph_traits<Po
typename GT::Construct_vector_3 cv_3 = traits.construct_vector_3_object();
typename GT::Compute_squared_length_3 csl_3 = traits.compute_squared_length_3_object();
#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP
std::cout << "Compute normal as weighted sums; type: " << vn_type << std::endl;
#endif
Vector_3 normal = cv_3(CGAL::NULL_VECTOR);
halfedge_descriptor h = halfedge(v, pmesh);
@ -548,7 +558,13 @@ compute_vertex_normal_as_sum_of_weighted_normals(typename boost::graph_traits<Po
const FT den = CGAL::approximate_sqrt(csl_3(v1) * csl_3(v2));
if(den == FT(0))
{
#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP
std::cout << "Null denominator, switching to no weights" << std::endl;
#endif
return compute_vertex_normal_as_sum_of_weighted_normals(v, NO_WEIGHT, face_normals, vpmap, pmesh, traits);
}
n = traits.construct_scaled_vector_3_object()(n, FT(1) / den);
normal = traits.construct_sum_of_vectors_3_object()(normal, n);
@ -632,9 +648,7 @@ compute_vertex_normal(typename boost::graph_traits<PolygonMesh>::vertex_descript
const bool must_compute_face_normals = is_default_parameter(get_parameter(np, internal_np::face_normal));
#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP
std::cout << std::endl << std::endl;
std::cout << "----------------------------------------------------------------------" << std::endl;
std::cout << "compute vertex at " << get(vpmap, v)
std::cout << "<----- compute vertex normal at " << get(vpmap, v)
<< ", must compute face normals? " << must_compute_face_normals << std::endl;
#endif
@ -654,7 +668,7 @@ compute_vertex_normal(typename boost::graph_traits<PolygonMesh>::vertex_descript
}
}
#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG
#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP
std::cout << "Incident face normals:" << std::endl;
for(halfedge_descriptor h : CGAL::halfedges_around_target(v, pmesh))
{

View File

@ -299,7 +299,8 @@ template <typename PolygonMesh,
std::size_t number_of_connected_components(const PolygonMesh& pmesh,
const CGAL_PMP_NP_CLASS& np)
{
typedef CGAL::dynamic_face_property_t<std::size_t> Face_property_tag;
typedef typename boost::graph_traits<PolygonMesh>::faces_size_type faces_size_type;
typedef CGAL::dynamic_face_property_t<faces_size_type> Face_property_tag;
typedef typename boost::property_map<PolygonMesh, Face_property_tag >::const_type Patch_ids_map;
Patch_ids_map patch_ids_map = get(Face_property_tag(), pmesh);
@ -931,23 +932,49 @@ struct No_mark
template < class PolygonMesh, class PolygonMeshRange,
class FIMap, class VIMap,
class HIMap, class Ecm >
class HIMap, class Ecm, class NamedParameters >
void split_connected_components_impl(FIMap fim,
HIMap him,
VIMap vim,
Ecm ecm,
PolygonMeshRange& range,
const PolygonMesh& tm)
const PolygonMesh& tm,
const NamedParameters& np)
{
typename boost::template property_map<
PolygonMesh, CGAL::dynamic_face_property_t<int > >::const_type
pidmap = get(CGAL::dynamic_face_property_t<int>(), tm);
typedef typename boost::graph_traits<PolygonMesh>::faces_size_type faces_size_type;
typedef typename internal_np::Lookup_named_param_def <
internal_np::face_patch_t,
NamedParameters,
typename boost::template property_map<
PolygonMesh, CGAL::dynamic_face_property_t<faces_size_type > >::const_type> ::type
Fpm;
int nb_patches = CGAL::Polygon_mesh_processing::connected_components(
tm, pidmap, CGAL::parameters::face_index_map(fim)
.edge_is_constrained_map(ecm));
using parameters::choose_parameter;
using parameters::get_parameter;
using parameters::is_default_parameter;
for(int i=0; i<nb_patches; ++i)
Fpm pidmap = choose_parameter(get_parameter(np, internal_np::face_patch),
get(CGAL::dynamic_face_property_t<faces_size_type>(), tm));
faces_size_type nb_patches = 0;
if(is_default_parameter(get_parameter(np, internal_np::face_patch)))
{
nb_patches = CGAL::Polygon_mesh_processing::connected_components(
tm, pidmap, CGAL::parameters::face_index_map(fim)
.edge_is_constrained_map(ecm));
}
else
{
for(const auto& f : faces(tm))
{
faces_size_type patch_id = get(pidmap, f);
if(patch_id > nb_patches)
nb_patches = patch_id;
}
nb_patches+=1;
}
for(faces_size_type i=0; i<nb_patches; ++i)
{
CGAL::Face_filtered_graph<PolygonMesh, FIMap, VIMap, HIMap>
filter_graph(tm, i, pidmap, CGAL::parameters::face_index_map(fim)
@ -986,6 +1013,11 @@ void split_connected_components_impl(FIMap fim,
* \cgalNPBegin{halfedge_index_map}
* a property map containing a unique index for each halfedge initialized 0 to `num_halfedges(pm)`
* \cgalNPEnd
* \cgalParamBegin{face_patch_map} a property map with the patch id's associated to the
faces of `pm`. Instance of a class model of `ReadPropertyMap`.
If not provided, an internal map will be filled with a call to
`connected_components()` with `edge_is_constrained_map()` (if provided).
* \cgalParamEnd
* \cgalNamedParamsEnd
*
*/
@ -1009,7 +1041,7 @@ void split_connected_components(const PolygonMesh& pm,
internal::split_connected_components_impl(CGAL::get_initialized_face_index_map(pm, np),
CGAL::get_initialized_halfedge_index_map(pm, np),
CGAL::get_initialized_vertex_index_map(pm, np),
ecm, cc_meshes, pm);
ecm, cc_meshes, pm, np);
}
template <class PolygonMesh, class PolygonMeshRange>

View File

@ -170,7 +170,7 @@ public:
// calls compute once to factorize with the preconditioner
if(!solver.factor(A, D))
{
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cerr << "Could not factorize linear system with preconditioner." << std::endl;
#endif
return false;
@ -180,7 +180,7 @@ public:
!solver.linear_solver(by, Xy) ||
!solver.linear_solver(bz, Xz))
{
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cerr << "Could not solve linear system." << std::endl;
#endif
return false;

View File

@ -21,7 +21,7 @@
#endif
#include <CGAL/Polygon_mesh_processing/compute_normal.h>
#include <CGAL/Polygon_mesh_processing/repair.h>
#include <CGAL/Polygon_mesh_processing/shape_predicates.h>
#include <CGAL/AABB_tree.h>
#include <CGAL/AABB_traits.h>
@ -50,9 +50,11 @@ namespace Polygon_mesh_processing {
namespace internal {
template <typename V, typename GT>
double get_radian_angle(const V& v1, const V& v2, const GT& gt)
typename GT::FT get_radian_angle(const V& v1, const V& v2, const GT& gt)
{
return gt.compute_approximate_angle_3_object()(v1, v2) * CGAL_PI / 180.;
typedef typename GT::FT FT;
return gt.compute_approximate_angle_3_object()(v1, v2) * CGAL_PI / FT(180);
}
// super naive for now. Not sure it even makes sense to do something like that for surfaces
@ -68,6 +70,7 @@ class Delaunay_edge_flipper
typedef typename boost::graph_traits<TriangleMesh>::face_descriptor face_descriptor;
typedef typename boost::property_traits<VertexPointMap>::reference Point_ref;
typedef typename GeomTraits::FT FT;
typedef typename GeomTraits::Vector_3 Vector;
public:
@ -86,20 +89,13 @@ public:
const halfedge_descriptor h = halfedge(e, mesh_);
const halfedge_descriptor opp_h = opposite(h, mesh_);
vertex_descriptor v0 = source(h, mesh_);
vertex_descriptor v1 = target(h, mesh_);
vertex_descriptor v2 = target(next(h, mesh_), mesh_);
vertex_descriptor v3 = target(next(opp_h, mesh_), mesh_);
const Point_ref p0 = get(vpmap_, v0);
const Point_ref p1 = get(vpmap_, v1);
const Point_ref p2 = get(vpmap_, v2);
const Point_ref p3 = get(vpmap_, v3);
const vertex_descriptor v0 = source(h, mesh_);
const vertex_descriptor v1 = target(h, mesh_);
const vertex_descriptor v2 = target(next(h, mesh_), mesh_);
const vertex_descriptor v3 = target(next(opp_h, mesh_), mesh_);
double alpha = get_radian_angle(Vector(p0 - p2), Vector(p1 - p2), traits_);
double beta = get_radian_angle(Vector(p1 - p3), Vector(p0 - p3), traits_);
// not local Delaunay if the sum of the angles is greater than pi
if(alpha + beta <= CGAL_PI)
std::set<vertex_descriptor> unique_vs { v0, v1, v2, v3 };
if(unique_vs.size() != 4)
return false;
// Don't want to flip if the other diagonal already exists
@ -108,7 +104,16 @@ public:
if(other_hd_already_exists.second)
return false;
return true;
// not local Delaunay := sum of the opposite angles is greater than pi
const Point_ref p0 = get(vpmap_, v0);
const Point_ref p1 = get(vpmap_, v1);
const Point_ref p2 = get(vpmap_, v2);
const Point_ref p3 = get(vpmap_, v3);
FT alpha = get_radian_angle(Vector(p0 - p2), Vector(p1 - p2), traits_);
FT beta = get_radian_angle(Vector(p1 - p3), Vector(p0 - p3), traits_);
return (alpha + beta > CGAL_PI);
}
template <typename Marked_edges_map, typename EdgeRange>
@ -127,6 +132,10 @@ public:
template <typename FaceRange>
void operator()(const FaceRange& face_range)
{
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cout << "Flipping edges" << std::endl;
#endif
// edges to consider
std::vector<edge_descriptor> edge_range;
edge_range.reserve(3 * face_range.size());
@ -166,6 +175,10 @@ public:
++flipped_n;
halfedge_descriptor h = halfedge(e, mesh_);
#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
std::cout << "Flipping " << edge(h, mesh_) << std::endl;
#endif
Euler::flip_edge(h, mesh_);
add_to_stack_if_unmarked(edge(next(h, mesh_), mesh_), marks, edge_range);
@ -174,6 +187,10 @@ public:
add_to_stack_if_unmarked(edge(prev(opposite(h, mesh_), mesh_), mesh_), marks, edge_range);
}
}
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cout << flipped_n << " flips" << std::endl;
#endif
}
private:
@ -190,6 +207,7 @@ class Angle_smoother
typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor halfedge_descriptor;
typedef typename boost::property_traits<VertexPointMap>::reference Point_ref;
typedef typename GeomTraits::FT FT;
typedef typename GeomTraits::Vector_3 Vector;
typedef std::pair<halfedge_descriptor, halfedge_descriptor> He_pair;
@ -231,7 +249,7 @@ public:
Vector operator()(const vertex_descriptor v) const
{
Vector move = CGAL::NULL_VECTOR;
double weights_sum = 0.;
FT weights_sum = FT(0);
for(halfedge_descriptor main_he : halfedges_around_source(v, mesh_))
{
@ -249,26 +267,30 @@ public:
Vector right_v(pt, right_pt);
// rotate
double angle = get_radian_angle(right_v, left_v, traits_);
CGAL_warning(angle != 0.); // no degenerate faces is a precondition
if(angle == 0.)
continue;
Vector bisector = rotate_edge(main_he, incident_pair);
double scaling_factor = CGAL::approximate_sqrt(
traits_.compute_squared_distance_3_object()(get(vpmap_, source(main_he, mesh_)),
get(vpmap_, target(main_he, mesh_))));
FT scaling_factor = CGAL::approximate_sqrt(
traits_.compute_squared_distance_3_object()(get(vpmap_, source(main_he, mesh_)),
get(vpmap_, target(main_he, mesh_))));
bisector = traits_.construct_scaled_vector_3_object()(bisector, scaling_factor);
Vector ps_psi(ps, traits_.construct_translated_point_3_object()(pt, bisector));
FT angle = get_radian_angle(right_v, left_v, traits_);
if(angle == FT(0))
{
// no degenerate faces is a precondition, angle can be 0 but it should be a numerical error
CGAL_warning(!is_degenerate_triangle_face(face(main_he, mesh_), mesh_));
return ps_psi; // since a small angle gives more weight, a null angle give priority (?)
}
// small angles carry more weight
double weight = 1. / (angle*angle);
FT weight = 1. / CGAL::square(angle);
weights_sum += weight;
move += weight * ps_psi;
}
if(weights_sum != 0.)
if(weights_sum != FT(0))
move /= weights_sum;
return move;
@ -288,6 +310,7 @@ class Area_smoother
typedef typename boost::property_traits<VertexPointMap>::value_type Point;
typedef typename boost::property_traits<VertexPointMap>::reference Point_ref;
typedef typename GeomTraits::FT FT;
typedef typename GeomTraits::Vector_3 Vector;
public:
@ -298,27 +321,27 @@ public:
{ }
private:
double element_area(const vertex_descriptor v1,
const vertex_descriptor v2,
const vertex_descriptor v3) const
FT element_area(const vertex_descriptor v1,
const vertex_descriptor v2,
const vertex_descriptor v3) const
{
return CGAL::to_double(CGAL::approximate_sqrt(traits_.compute_squared_area_3_object()(get(vpmap_, v1),
get(vpmap_, v2),
get(vpmap_, v3))));
return CGAL::approximate_sqrt(traits_.compute_squared_area_3_object()(get(vpmap_, v1),
get(vpmap_, v2),
get(vpmap_, v3)));
}
double element_area(const Point& P,
const vertex_descriptor v2,
const vertex_descriptor v3) const
FT element_area(const Point& P,
const vertex_descriptor v2,
const vertex_descriptor v3) const
{
return CGAL::to_double(CGAL::approximate_sqrt(traits_.compute_squared_area_3_object()(P,
get(vpmap_, v2),
get(vpmap_, v3))));
return CGAL::approximate_sqrt(traits_.compute_squared_area_3_object()(P,
get(vpmap_, v2),
get(vpmap_, v3)));
}
double compute_average_area_around(const vertex_descriptor v) const
FT compute_average_area_around(const vertex_descriptor v) const
{
double sum_areas = 0.;
FT sum_areas = 0;
unsigned int number_of_edges = 0;
for(halfedge_descriptor h : halfedges_around_source(v, mesh_))
@ -327,7 +350,7 @@ private:
vertex_descriptor vi = source(next(h, mesh_), mesh_);
vertex_descriptor vj = target(next(h, mesh_), mesh_);
double S = element_area(v, vi, vj);
FT S = element_area(v, vi, vj);
sum_areas += S;
++number_of_edges;
}
@ -337,7 +360,7 @@ private:
struct Face_energy
{
Face_energy(const Point& pi, const Point& pj, const double s_av)
Face_energy(const Point& pi, const Point& pj, const FT s_av)
:
qx(pi.x()), qy(pi.y()), qz(pi.z()),
rx(pj.x()), ry(pj.y()), rz(pj.z()),
@ -346,7 +369,7 @@ private:
// next two functions are just for convencience, the only thing ceres cares about is the operator()
template <typename T>
double area(const T x, const T y, const T z) const
FT area(const T x, const T y, const T z) const
{
return CGAL::approximate_sqrt(CGAL::squared_area(Point(x, y, z),
Point(qx, qy, qz),
@ -354,7 +377,7 @@ private:
}
template <typename T>
double evaluate(const T x, const T y, const T z) const { return area(x, y, z) - s_av; }
FT evaluate(const T x, const T y, const T z) const { return area(x, y, z) - s_av; }
template <typename T>
bool operator()(const T* const x, const T* const y, const T* const z,
@ -390,9 +413,9 @@ private:
}
private:
const double qx, qy, qz;
const double rx, ry, rz;
const double s_av;
const FT qx, qy, qz;
const FT rx, ry, rz;
const FT s_av;
};
public:
@ -401,12 +424,12 @@ public:
#ifdef CGAL_PMP_USE_CERES_SOLVER
const Point_ref vp = get(vpmap_, v);
const double S_av = compute_average_area_around(v);
const FT S_av = compute_average_area_around(v);
const double initial_x = vp.x();
const double initial_y = vp.y();
const double initial_z = vp.z();
double x = initial_x, y = initial_y, z = initial_z;
const FT initial_x = vp.x();
const FT initial_y = vp.y();
const FT initial_z = vp.z();
FT x = initial_x, y = initial_y, z = initial_z;
ceres::Problem problem;
@ -523,7 +546,8 @@ public:
Optimizer compute_move(mesh_, vpmap_, traits_);
#ifdef CGAL_PMP_SMOOTHING_DEBUG
double total_displacement = 0;
FT total_displacement = 0;
std::cout << "apply_moves_in_single_batch: " << apply_moves_in_single_batch << std::endl;
#endif
std::size_t moved_points = 0;
@ -532,6 +556,10 @@ public:
if(is_border(v, mesh_) || is_constrained(v))
continue;
#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
std::cout << "Considering " << v << " pos: " << get(vpmap_, v) << std::endl;
#endif
// compute normal to v
Vector vn = compute_vertex_normal(v, mesh_, CGAL::parameters::vertex_point_map(vpmap_)
.geom_traits(traits_));
@ -542,17 +570,17 @@ public:
// Gram Schmidt so that the new location is on the tangent plane of v (i.e. do mv -= (mv*n)*n)
const FT sp = traits_.compute_scalar_product_3_object()(vn, move);
move = traits_.construct_sum_of_vectors_3_object()(move,
traits_.construct_scaled_vector_3_object()(vn, - sp));
move = traits_.construct_sum_of_vectors_3_object()(
move, traits_.construct_scaled_vector_3_object()(vn, - sp));
Point new_pos = pos + move;
if((!use_sanity_checks || !does_move_create_bad_faces(v, new_pos)) &&
const Point new_pos = pos + move;
if(move != CGAL::NULL_VECTOR &&
!does_move_create_degenerate_faces(v, new_pos) &&
(!use_sanity_checks || !does_move_create_bad_faces(v, new_pos)) &&
(!enforce_no_min_angle_regression || does_improve_min_angle_in_star(v, new_pos)))
{
#ifdef CGAL_PMP_SMOOTHING_DEBUG
#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
std::cout << "moving " << get(vpmap_, v) << " to " << new_pos << std::endl;
total_displacement += CGAL::approximate_sqrt(traits_.compute_squared_length_3_object()(move));
#endif
if(apply_moves_in_single_batch)
@ -560,11 +588,15 @@ public:
else
put(vpmap_, v, new_pos);
#ifdef CGAL_PMP_SMOOTHING_DEBUG
total_displacement += CGAL::approximate_sqrt(traits_.compute_squared_length_3_object()(move));
#endif
++moved_points;
}
else // some sanity check failed
{
#ifdef CGAL_PMP_SMOOTHING_DEBUG
#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
std::cout << "move is rejected!" << std::endl;
#endif
if(apply_moves_in_single_batch)
@ -607,6 +639,10 @@ public:
Point_ref p_query = get(vpmap_, v);
const Point projected = tree.closest_point(p_query);
#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
std::cout << p_query << " to " << projected << std::endl;
#endif
put(vpmap_, v, projected);
}
}
@ -617,11 +653,10 @@ private:
return get(vcmap_, v);
}
// check for degenerate or inversed faces
bool does_move_create_bad_faces(const vertex_descriptor v,
const Point& new_pos) const
// Null faces are bad because they make normal computation difficult
bool does_move_create_degenerate_faces(const vertex_descriptor v,
const Point& new_pos) const
{
// check for null faces and face inversions
for(halfedge_descriptor main_he : halfedges_around_source(v, mesh_))
{
const halfedge_descriptor prev_he = prev(main_he, mesh_);
@ -630,6 +665,23 @@ private:
if(traits_.collinear_3_object()(lpt, rpt, new_pos))
return true;
}
return false;
}
// check for degenerate or inversed faces
bool does_move_create_bad_faces(const vertex_descriptor v,
const Point& new_pos) const
{
// check for face inversions
for(halfedge_descriptor main_he : halfedges_around_source(v, mesh_))
{
const halfedge_descriptor prev_he = prev(main_he, mesh_);
const Point_ref lpt = get(vpmap_, target(main_he, mesh_));
const Point_ref rpt = get(vpmap_, source(prev_he, mesh_));
CGAL_assertion(!traits_.collinear_3_object()(lpt, rpt, new_pos)); // checked above
const Point_ref old_pos = get(vpmap_, v);
Vector ov_1 = traits_.construct_vector_3_object()(old_pos, lpt);
@ -641,7 +693,7 @@ private:
if(!is_positive(traits_.compute_scalar_product_3_object()(old_n, new_n)))
{
#ifdef CGAL_PMP_SMOOTHING_DEBUG
#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
std::cout << "Moving vertex would result in the inversion of a face normal!" << std::endl;
#endif
return true;
@ -655,7 +707,7 @@ private:
const Point& new_pos) const
{
// check if the minimum angle of the star has not deteriorated
double old_min_angle = CGAL_PI;
FT old_min_angle = CGAL_PI;
for(halfedge_descriptor main_he : halfedges_around_source(v, mesh_))
{
const Point_ref old_pos = get(vpmap_, v);
@ -683,7 +735,7 @@ private:
get_radian_angle(Vector(lpt, rpt), Vector(lpt, new_pos), traits_) < old_min_angle ||
get_radian_angle(Vector(rpt, new_pos), Vector(rpt, lpt), traits_) < old_min_angle)
{
#ifdef CGAL_PMP_SMOOTHING_DEBUG
#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
const Point_ref old_pos = get(vpmap_, v);
std::cout << "deterioration of min angle in the star!" << std::endl;

View File

@ -61,7 +61,7 @@ public:
angles_.push_back(traits_.compute_approximate_angle_3_object()(a, b, c));
}
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cout << "angles_ size = " << angles_.size() << std::endl;
#endif
}
@ -79,7 +79,7 @@ public:
for(face_descriptor f : faces(mesh_))
areas_.push_back(face_area(f, mesh_));
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cout << "areas_ size = " << areas_.size() << std::endl;
#endif
}
@ -97,7 +97,7 @@ public:
for(face_descriptor f : faces(mesh_))
aspect_ratios_.push_back(CGAL::Polygon_mesh_processing::face_aspect_ratio(f, mesh_));
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cout << "aspect_ratios_ size = " << aspect_ratios_.size() << std::endl;
#endif
}

View File

@ -1185,13 +1185,13 @@ std::size_t snap_borders(TriangleMesh& tm_A,
{
typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor halfedge_descriptor;
std::vector<halfedge_descriptor> border_vertices_1;
border_halfedges(tm_A, std::back_inserter(border_vertices_1));
std::vector<halfedge_descriptor> border_vertices_2;
border_halfedges(tm_B, std::back_inserter(border_vertices_2));
std::vector<halfedge_descriptor> border_vertices_A;
border_halfedges(tm_A, std::back_inserter(border_vertices_A));
std::vector<halfedge_descriptor> border_vertices_B;
border_halfedges(tm_B, std::back_inserter(border_vertices_B));
return internal::snap_non_conformal<ConcurrencyTag>(border_vertices_1, tm_A, tolerance_map_A,
border_vertices_2, tm_B, tolerance_map_B,
return internal::snap_non_conformal<ConcurrencyTag>(border_vertices_A, tm_A, tolerance_map_A,
border_vertices_B, tm_B, tolerance_map_B,
false /*not self snapping*/, np_A, np_B);
}
@ -1211,19 +1211,19 @@ std::size_t snap_borders(TriangleMesh& tm_A,
typedef CGAL::dynamic_vertex_property_t<FT> Vertex_property_tag;
typedef typename boost::property_map<TriangleMesh, Vertex_property_tag>::type Tolerance_map;
std::vector<halfedge_descriptor> border_vertices_1;
std::vector<halfedge_descriptor> border_vertices_2;
border_halfedges(tm_A, std::back_inserter(border_vertices_1));
border_halfedges(tm_B, std::back_inserter(border_vertices_2));
std::vector<halfedge_descriptor> border_vertices_A;
std::vector<halfedge_descriptor> border_vertices_B;
border_halfedges(tm_A, std::back_inserter(border_vertices_A));
border_halfedges(tm_B, std::back_inserter(border_vertices_B));
const FT tol_mx(std::numeric_limits<double>::max());
Tolerance_map tolerance_map_A = get(Vertex_property_tag(), tm_A);
internal::assign_tolerance_with_local_edge_length_bound(border_vertices_1, tolerance_map_A, tol_mx, tm_A, np_A);
Tolerance_map tolerance_map_B = get(Vertex_property_tag(), tm_A);
internal::assign_tolerance_with_local_edge_length_bound(border_vertices_2, tolerance_map_B, tol_mx, tm_B, np_B);
internal::assign_tolerance_with_local_edge_length_bound(border_vertices_A, tolerance_map_A, tol_mx, tm_A, np_A);
Tolerance_map tolerance_map_B = get(Vertex_property_tag(), tm_B);
internal::assign_tolerance_with_local_edge_length_bound(border_vertices_B, tolerance_map_B, tol_mx, tm_B, np_B);
return internal::snap_non_conformal<ConcurrencyTag>(border_vertices_1, tm_A, tolerance_map_A,
border_vertices_2, tm_B, tolerance_map_B,
return internal::snap_non_conformal<ConcurrencyTag>(border_vertices_A, tm_A, tolerance_map_A,
border_vertices_B, tm_B, tolerance_map_B,
false /*no self snapping*/, np_A, np_B);
}

View File

@ -1,709 +0,0 @@
// Copyright (c) 2019 GeometryFactory (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s) : Sebastien Loriot,
// Mael Rouxel-Labbé
#ifndef CGAL_POLYGON_MESH_PROCESSING_REMOVE_DEGENERACIES_H
#define CGAL_POLYGON_MESH_PROCESSING_REMOVE_DEGENERACIES_H
#include <CGAL/license/Polygon_mesh_processing/repair.h>
#include <CGAL/Polygon_mesh_processing/shape_predicates.h>
#include <CGAL/Polygon_mesh_processing/measure.h>
#include <CGAL/Dynamic_property_map.h>
#include <CGAL/boost/graph/Euler_operations.h>
#include <CGAL/boost/graph/Named_function_parameters.h>
#include <CGAL/property_map.h>
#include <boost/graph/graph_traits.hpp>
#include <iostream>
#include <set>
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
#include <sstream>
#include <fstream>
#endif
namespace CGAL {
namespace Polygon_mesh_processing {
namespace internal {
template <typename TriangleMesh, typename VPM, typename ECM, typename Traits>
std::array<typename boost::graph_traits<TriangleMesh>::halfedge_descriptor, 2>
is_badly_shaped(const typename boost::graph_traits<TriangleMesh>::face_descriptor f,
TriangleMesh& tmesh,
const VPM& vpm,
const ECM& ecm,
const Traits& gt,
const double cap_threshold, // angle over 160° ==> cap
const double needle_threshold, // longest edge / shortest edge over this ratio ==> needle
const double collapse_length_threshold) // max length of edges allowed to be collapsed
{
namespace PMP = CGAL::Polygon_mesh_processing;
typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor halfedge_descriptor;
const halfedge_descriptor null_h = boost::graph_traits<TriangleMesh>::null_halfedge();
halfedge_descriptor res = PMP::is_needle_triangle_face(f, tmesh, needle_threshold,
parameters::vertex_point_map(vpm)
.geom_traits(gt));
if(res != null_h && !get(ecm, edge(res, tmesh)))
{
// don't want to collapse edges that are too large
if(collapse_length_threshold == 0 ||
edge_length(res, tmesh, parameters::vertex_point_map(vpm).geom_traits(gt)) <= collapse_length_threshold)
{
return make_array(res, null_h);
}
}
else // let's not make it possible to have a face be both a cap and a needle (for now)
{
res = PMP::is_cap_triangle_face(f, tmesh, cap_threshold, parameters::vertex_point_map(vpm).geom_traits(gt));
if(res != null_h && !get(ecm, edge(res, tmesh)))
return make_array(null_h, res);
}
return make_array(null_h, null_h);
}
template <typename TriangleMesh, typename EdgeContainer,
typename VPM, typename ECM, typename Traits>
void collect_badly_shaped_triangles(const typename boost::graph_traits<TriangleMesh>::face_descriptor f,
TriangleMesh& tmesh,
const VPM& vpm,
const ECM& ecm,
const Traits& gt,
const double cap_threshold, // angle over this threshold (as a cosine) ==> cap
const double needle_threshold, // longest edge / shortest edge over this ratio ==> needle
const double collapse_length_threshold, // max length of edges allowed to be collapsed
EdgeContainer& edges_to_collapse,
EdgeContainer& edges_to_flip)
{
typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor halfedge_descriptor;
std::array<halfedge_descriptor, 2> res = is_badly_shaped(f, tmesh, vpm, ecm, gt, cap_threshold,
needle_threshold, collapse_length_threshold);
if(res[0] != boost::graph_traits<TriangleMesh>::null_halfedge())
{
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
std::cout << "add new needle: " << edge(res[0], tmesh) << std::endl;
#endif
edges_to_collapse.insert(edge(res[0], tmesh));
}
else // let's not make it possible to have a face be both a cap and a needle (for now)
{
if(res[1] != boost::graph_traits<TriangleMesh>::null_halfedge())
{
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
std::cout << "add new cap: " << edge(res[1],tmesh) << std::endl;
#endif
edges_to_flip.insert(edge(res[1], tmesh));
}
}
}
/*
// Following Ronfard et al. 96 we look at variation of the normal after the collapse
// the collapse must be topologically valid
template <class TriangleMesh, class NamedParameters>
bool is_collapse_geometrically_valid(typename boost::graph_traits<TriangleMesh>::halfedge_descriptor h,
const TriangleMesh& tmesh,
const NamedParameters& np)
{
using CGAL::parameters::choose_parameter;
using CGAL::parameters::get_parameter;
typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor halfedge_descriptor;
typedef typename GetVertexPointMap<TriangleMesh, NamedParameters>::const_type VPM;
typedef typename boost::property_traits<VPM>::reference Point_ref;
typedef typename GetGeomTraits<TriangleMesh, NamedParameters>::type Traits;
VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point),
get_const_property_map(vertex_point, tmesh));
Traits gt = choose_parameter<Traits>(get_parameter(np, internal_np::geom_traits));
/// @todo handle boundary edges
h = opposite(h, tmesh); // Euler::collapse edge keeps the target and removes the source
// source is kept, target is removed
CGAL_assertion(target(h, tmesh) == vertex_removed);
Point_ref kept = get(vpm, source(h, tmesh));
Point_ref removed= get(vpm, target(h, tmesh));
// consider triangles incident to the vertex removed
halfedge_descriptor stop = prev(opposite(h, tmesh), tmesh);
halfedge_descriptor hi = opposite(next(h, tmesh), tmesh);
std::vector<halfedge_descriptor> triangles;
while(hi != stop)
{
if(!is_border(hi, tmesh))
{
Point_ref a = get(vpm, target(next(hi, tmesh), tmesh));
Point_ref b = get(vpm, source(hi, tmesh));
//ack a-b-point_remove and a-b-point_kept has a compatible orientation
/// @todo use a predicate
typename Traits::Vector_3 n1 = gt.construct_cross_product_vector_3_object()(removed-a, b-a);
typename Traits::Vector_3 n2 = gt.construct_cross_product_vector_3_object()(kept-a, b-a);
if(gt.compute_scalar_product_3_object()(n1, n2) <= 0)
return false;
}
hi = opposite(next(hi, tmesh), tmesh);
}
return true;
}
*/
template <class TriangleMesh, typename VPM, typename Traits>
boost::optional<double> get_collapse_volume(typename boost::graph_traits<TriangleMesh>::halfedge_descriptor h,
const TriangleMesh& tmesh,
const VPM& vpm,
const Traits& gt)
{
typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor halfedge_descriptor;
typedef typename boost::property_traits<VPM>::reference Point_ref;
typedef typename Traits::Vector_3 Vector_3;
const typename Traits::Point_3 origin(ORIGIN);
/// @todo handle boundary edges
h = opposite(h, tmesh); // Euler::collapse edge keeps the target and removes the source
// source is kept, target is removed
Point_ref kept = get(vpm, source(h, tmesh));
Point_ref removed= get(vpm, target(h, tmesh));
// init volume with incident triangles (reversed orientation
double delta_vol = volume(removed, kept, get(vpm, target(next(h, tmesh), tmesh)), origin) +
volume(kept, removed, get(vpm, target(next(opposite(h, tmesh), tmesh), tmesh)), origin);
// consider triangles incident to the vertex removed
halfedge_descriptor stop = prev(opposite(h, tmesh), tmesh);
halfedge_descriptor hi = opposite(next(h, tmesh), tmesh);
std::vector<halfedge_descriptor> triangles;
while(hi != stop)
{
if(!is_border(hi, tmesh))
{
Point_ref a = get(vpm, target(next(hi, tmesh), tmesh));
Point_ref b = get(vpm, source(hi, tmesh));
//ack a-b-point_remove and a-b-point_kept has a compatible orientation
/// @todo use a predicate
Vector_3 v_ab = gt.construct_vector_3_object()(a, b);
Vector_3 v_ar = gt.construct_vector_3_object()(a, removed);
Vector_3 v_ak = gt.construct_vector_3_object()(a, kept);
Vector_3 n1 = gt.construct_cross_product_vector_3_object()(v_ar, v_ab);
Vector_3 n2 = gt.construct_cross_product_vector_3_object()(v_ak, v_ab);
if(gt.compute_scalar_product_3_object()(n1, n2) <= 0)
return boost::none;
delta_vol += volume(b, a, removed, origin) + volume(a, b, kept, origin); // opposite orientation
}
hi = opposite(next(hi, tmesh), tmesh);
}
return CGAL::abs(delta_vol);
}
template <typename TriangleMesh, typename VPM, typename VCM, typename Traits>
typename boost::graph_traits<TriangleMesh>::halfedge_descriptor
get_best_edge_orientation(typename boost::graph_traits<TriangleMesh>::edge_descriptor e,
const TriangleMesh& tmesh,
const VPM& vpm,
const VCM& vcm,
const Traits& gt)
{
typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor halfedge_descriptor;
halfedge_descriptor h = halfedge(e, tmesh), ho = opposite(h, tmesh);
CGAL_assertion(!get(vcm, source(h, tmesh)) || !get(vcm, target(h, tmesh)));
boost::optional<double> dv1 = get_collapse_volume(h, tmesh, vpm, gt);
boost::optional<double> dv2 = get_collapse_volume(ho, tmesh, vpm, gt);
// the resulting point of the collapse of a halfedge is the target of the halfedge before collapse
if(get(vcm, source(h, tmesh)))
return dv2 != boost::none ? ho
: boost::graph_traits<TriangleMesh>::null_halfedge();
if(get(vcm, target(h, tmesh)))
return dv1 != boost::none ? h
: boost::graph_traits<TriangleMesh>::null_halfedge();
if(dv1 != boost::none)
{
if(dv2 != boost::none)
return (*dv1 < *dv2) ? h : ho;
return h;
}
if(dv2 != boost::none)
return ho;
return boost::graph_traits<TriangleMesh>::null_halfedge();
}
// adapted from triangulate_faces
template <typename TriangleMesh, typename VPM, typename Traits>
bool should_flip(typename boost::graph_traits<TriangleMesh>::edge_descriptor e,
const TriangleMesh& tmesh,
const VPM& vpm,
const Traits& gt)
{
typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor halfedge_descriptor;
typedef typename boost::property_traits<VPM>::reference Point_ref;
typedef typename Traits::Vector_3 Vector_3;
CGAL_precondition(!is_border(e, tmesh));
halfedge_descriptor h = halfedge(e, tmesh);
Point_ref p0 = get(vpm, target(h, tmesh));
Point_ref p1 = get(vpm, target(next(h, tmesh), tmesh));
Point_ref p2 = get(vpm, source(h, tmesh));
Point_ref p3 = get(vpm, target(next(opposite(h, tmesh), tmesh), tmesh));
/* Chooses the diagonal that will split the quad in two triangles that maximize
* the scalar product of of the un-normalized normals of the two triangles.
* The lengths of the un-normalized normals (computed using cross-products of two vectors)
* are proportional to the area of the triangles.
* Maximize the scalar product of the two normals will avoid skinny triangles,
* and will also taken into account the cosine of the angle between the two normals.
* In particular, if the two triangles are oriented in different directions,
* the scalar product will be negative.
*/
// CGAL::cross_product(p2-p1, p3-p2) * CGAL::cross_product(p0-p3, p1-p0);
// CGAL::cross_product(p1-p0, p1-p2) * CGAL::cross_product(p3-p2, p3-p0);
const Vector_3 v01 = gt.construct_vector_3_object()(p0, p1);
const Vector_3 v12 = gt.construct_vector_3_object()(p1, p2);
const Vector_3 v23 = gt.construct_vector_3_object()(p2, p3);
const Vector_3 v30 = gt.construct_vector_3_object()(p3, p0);
const double p1p3 = gt.compute_scalar_product_3_object()(
gt.construct_cross_product_vector_3_object()(v12, v23),
gt.construct_cross_product_vector_3_object()(v30, v01));
const Vector_3 v21 = gt.construct_opposite_vector_3_object()(v12);
const Vector_3 v03 = gt.construct_opposite_vector_3_object()(v30);
const double p0p2 = gt.compute_scalar_product_3_object()(
gt.construct_cross_product_vector_3_object()(v01, v21),
gt.construct_cross_product_vector_3_object()(v23, v03));
return p0p2 <= p1p3;
}
} // namespace internal
namespace experimental {
// @todo check what to use as priority queue with removable elements, set might not be optimal
template <typename FaceRange, typename TriangleMesh, typename NamedParameters>
bool remove_almost_degenerate_faces(const FaceRange& face_range,
TriangleMesh& tmesh,
const double cap_threshold,
const double needle_threshold,
const double collapse_length_threshold,
const NamedParameters& np)
{
using CGAL::parameters::choose_parameter;
using CGAL::parameters::get_parameter;
typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor vertex_descriptor;
typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor halfedge_descriptor;
typedef typename boost::graph_traits<TriangleMesh>::edge_descriptor edge_descriptor;
typedef typename boost::graph_traits<TriangleMesh>::face_descriptor face_descriptor;
typedef Constant_property_map<vertex_descriptor, bool> Default_VCM;
typedef typename internal_np::Lookup_named_param_def<internal_np::vertex_is_constrained_t,
NamedParameters,
Default_VCM>::type VCM;
VCM vcm_np = choose_parameter(get_parameter(np, internal_np::vertex_is_constrained), Default_VCM(false));
typedef Constant_property_map<edge_descriptor, bool> Default_ECM;
typedef typename internal_np::Lookup_named_param_def<internal_np::edge_is_constrained_t,
NamedParameters,
Default_ECM>::type ECM;
ECM ecm = choose_parameter(get_parameter(np, internal_np::edge_is_constrained), Default_ECM(false));
typedef typename GetVertexPointMap<TriangleMesh, NamedParameters>::const_type VPM;
VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point),
get_const_property_map(vertex_point, tmesh));
typedef typename GetGeomTraits<TriangleMesh, NamedParameters>::type Traits;
Traits gt = choose_parameter<Traits>(get_parameter(np, internal_np::geom_traits));
// Vertex property map that combines the VCM and the fact that extremities of a constrained edge should be constrained
typedef CGAL::dynamic_vertex_property_t<bool> Vertex_property_tag;
typedef typename boost::property_map<TriangleMesh, Vertex_property_tag>::type DVCM;
DVCM vcm = get(Vertex_property_tag(), tmesh);
for(face_descriptor f : face_range)
{
if(f == boost::graph_traits<TriangleMesh>::null_face())
continue;
for(halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(f, tmesh), tmesh))
{
if(get(ecm, edge(h, tmesh)))
{
put(vcm, source(h, tmesh), true);
put(vcm, target(h, tmesh), true);
}
else if(get(vcm_np, target(h, tmesh)))
{
put(vcm, target(h, tmesh), true);
}
}
}
// Start the process of removing bad elements
std::set<edge_descriptor> edges_to_collapse;
std::set<edge_descriptor> edges_to_flip;
// @todo could probably do something a bit better by looping edges, consider the incident faces
// f1 / f2 and look at f1 if f1<f2, and the edge is smaller than the two other edges...
for(face_descriptor f : face_range)
internal::collect_badly_shaped_triangles(f, tmesh, vpm, ecm, gt,
cap_threshold, needle_threshold, collapse_length_threshold,
edges_to_collapse, edges_to_flip);
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
int iter = 0;
#endif
for(;;)
{
bool something_was_done = false;
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
std::cout << edges_to_collapse.size() << " needles and " << edges_to_flip.size() << " caps" << std::endl;
std::ostringstream oss;
oss << "degen_cleaning_iter_" << iter++ << ".off";
std::ofstream out(oss.str().c_str());
out << std::setprecision(17);
out << tmesh;
out.close();
#endif
if(edges_to_collapse.empty() && edges_to_flip.empty())
return true;
/// @todo maybe using a priority queue handling the more almost degenerate elements should be used
std::set<edge_descriptor> next_edges_to_collapse;
std::set<edge_descriptor> next_edges_to_flip;
// treat needles
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA
int kk=0;
std::ofstream(std::string("tmp/n-00000.off")) << tmesh;
#endif
while(!edges_to_collapse.empty())
{
edge_descriptor e = *edges_to_collapse.begin();
edges_to_collapse.erase(edges_to_collapse.begin());
CGAL_assertion(!get(ecm, e));
if(get(vcm, source(e, tmesh)) && get(vcm, target(e, tmesh)))
continue;
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
std::cout << " treat needle: " << e << " (" << tmesh.point(source (e, tmesh))
<< " --- " << tmesh.point(target(e, tmesh)) << ")" << std::endl;
#endif
if(CGAL::Euler::does_satisfy_link_condition(e, tmesh))
{
// the following edges are removed by the collapse
halfedge_descriptor h = halfedge(e, tmesh);
CGAL_assertion(!is_border(h, tmesh)); // because extracted from a face
std::array<halfedge_descriptor, 2> nc =
internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt,
cap_threshold, needle_threshold, collapse_length_threshold);
if(nc[0] != h)
{
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
std::cerr << "Warning: Needle criteria no longer verified " << tmesh.point(source(e, tmesh)) << " "
<< tmesh.point(target(e, tmesh)) << std::endl;
#endif
// the opposite edge might also have been inserted in the set and might still be a needle
h = opposite(h, tmesh);
if(is_border(h, tmesh))
continue;
nc = internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt,
cap_threshold, needle_threshold,
collapse_length_threshold);
if(nc[0] != h)
continue;
}
for(int i=0; i<2; ++i)
{
if(!is_border(h, tmesh))
{
edge_descriptor pe = edge(prev(h, tmesh), tmesh);
edges_to_flip.erase(pe);
next_edges_to_collapse.erase(pe);
edges_to_collapse.erase(pe);
}
h = opposite(h, tmesh);
}
// pick the orientation of edge to keep the vertex minimizing the volume variation
h = internal::get_best_edge_orientation(e, tmesh, vpm, vcm, gt);
if(h == boost::graph_traits<TriangleMesh>::null_halfedge())
{
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
std::cerr << "Warning: geometrically invalid edge collapse! "
<< tmesh.point(source(e, tmesh)) << " "
<< tmesh.point(target(e, tmesh)) << std::endl;
#endif
next_edges_to_collapse.insert(e);
continue;
}
edges_to_flip.erase(e);
next_edges_to_collapse.erase(e); // for edges added in faces incident to a vertex kept after a collapse
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA
std::cerr << " " << kk << " -- Collapsing " << tmesh.point(source(h, tmesh)) << " "
<< tmesh.point(target(h, tmesh)) << std::endl;
#endif
// moving to the midpoint is not a good idea. On a circle for example you might endpoint with
// a bad geometry because you iteratively move one point
// auto mp = midpoint(tmesh.point(source(h, tmesh)), tmesh.point(target(h, tmesh)));
vertex_descriptor v = Euler::collapse_edge(edge(h, tmesh), tmesh);
//tmesh.point(v) = mp;
// examine all faces incident to the vertex kept
for(halfedge_descriptor hv : halfedges_around_target(v, tmesh))
{
if(!is_border(hv, tmesh))
{
internal::collect_badly_shaped_triangles(face(hv, tmesh), tmesh, vpm, ecm, gt,
cap_threshold, needle_threshold, collapse_length_threshold,
edges_to_collapse, edges_to_flip);
}
}
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA
std::string nb = std::to_string(++kk);
if(kk<10) nb = std::string("0")+nb;
if(kk<100) nb = std::string("0")+nb;
if(kk<1000) nb = std::string("0")+nb;
if(kk<10000) nb = std::string("0")+nb;
std::ofstream(std::string("tmp/n-")+nb+std::string(".off")) << tmesh;
#endif
something_was_done = true;
}
else
{
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
std::cerr << "Warning: uncollapsable edge! " << tmesh.point(source(e, tmesh)) << " "
<< tmesh.point(target(e, tmesh)) << std::endl;
#endif
next_edges_to_collapse.insert(e);
}
}
// treat caps
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA
kk=0;
std::ofstream(std::string("tmp/c-000.off")) << tmesh;
#endif
while(!edges_to_flip.empty())
{
edge_descriptor e = *edges_to_flip.begin();
edges_to_flip.erase(edges_to_flip.begin());
CGAL_assertion(!get(ecm, e));
if(get(vcm, source(e, tmesh)) && get(vcm, target(e, tmesh)))
continue;
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
std::cout << "treat cap: " << e << " (" << tmesh.point(source(e, tmesh))
<< " --- " << tmesh.point(target(e, tmesh)) << ")" << std::endl;
#endif
halfedge_descriptor h = halfedge(e, tmesh);
std::array<halfedge_descriptor,2> nc = internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt,
cap_threshold, needle_threshold,
collapse_length_threshold);
// First check the triangle is still a cap
if(nc[1] != h)
{
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
std::cerr << "Warning: Cap criteria no longer verified " << tmesh.point(source(e, tmesh)) << " --- "
<< tmesh.point(target(e, tmesh)) << std::endl;
#endif
// the opposite edge might also have been inserted in the set and might still be a cap
h = opposite(h, tmesh);
if(is_border(h, tmesh))
continue;
nc = internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt,
cap_threshold, needle_threshold, collapse_length_threshold);
if(nc[1] != h)
continue;
}
// special case on the border
if(is_border(opposite(h, tmesh), tmesh))
{
// remove the triangle
edges_to_flip.erase(edge(prev(h, tmesh), tmesh));
edges_to_flip.erase(edge(next(h, tmesh), tmesh));
next_edges_to_collapse.erase(edge(prev(h, tmesh), tmesh));
next_edges_to_collapse.erase(edge(next(h, tmesh), tmesh));
Euler::remove_face(h, tmesh);
something_was_done = true;
continue;
}
// condition for the flip to be valid (the edge to be created does not already exist)
if(!halfedge(target(next(h, tmesh), tmesh),
target(next(opposite(h, tmesh), tmesh), tmesh), tmesh).second)
{
if(!internal::should_flip(e, tmesh, vpm, gt))
{
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
std::cout << "Flipping prevented: not the best diagonal" << std::endl;
#endif
next_edges_to_flip.insert(e);
continue;
}
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
std::cout << "Flipping" << std::endl;
#endif
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA
std::cerr << "step " << kk << "\n";
std::cerr << " Flipping " << tmesh.point(source(h, tmesh)) << " "
<< tmesh.point(target(h, tmesh)) << std::endl;
#endif
Euler::flip_edge(h, tmesh);
CGAL_assertion(edge(h, tmesh) == e);
// handle face updates
for(int i=0; i<2; ++i)
{
CGAL_assertion(!is_border(h, tmesh));
std::array<halfedge_descriptor, 2> nc =
internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt,
cap_threshold, needle_threshold, collapse_length_threshold);
if(nc[1] != boost::graph_traits<TriangleMesh>::null_halfedge())
{
if(edge(nc[1], tmesh) != e)
next_edges_to_flip.insert(edge(nc[1], tmesh));
}
else
{
if(nc[0] != boost::graph_traits<TriangleMesh>::null_halfedge())
{
next_edges_to_collapse.insert(edge(nc[0], tmesh));
}
}
h = opposite(h, tmesh);
}
something_was_done = true;
}
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES
else
{
std::cerr << "Warning: unflippable edge! " << tmesh.point(source(h, tmesh)) << " --- "
<< tmesh.point(target(h, tmesh)) << std::endl;
next_edges_to_flip.insert(e);
}
#endif
#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA
std::string nb = std::to_string(++kk);
if(kk<10) nb = std::string("0")+nb;
if(kk<100) nb = std::string("0")+nb;
if(kk<1000) nb = std::string("0")+nb;
if(kk<10000) nb = std::string("0")+nb;
std::ofstream(std::string("tmp/c-")+nb+std::string(".off")) << tmesh;
#endif
}
std::swap(edges_to_collapse, next_edges_to_collapse);
std::swap(edges_to_flip, next_edges_to_flip);
if(!something_was_done)
return false;
}
return false;
}
template <typename FaceRange, typename TriangleMesh>
bool remove_almost_degenerate_faces(const FaceRange& face_range,
TriangleMesh& tmesh,
const double cap_threshold,
const double needle_threshold,
const double collapse_length_threshold)
{
return remove_almost_degenerate_faces(face_range, tmesh,
cap_threshold, needle_threshold, collapse_length_threshold,
CGAL::parameters::all_default());
}
template <typename TriangleMesh, typename CGAL_PMP_NP_TEMPLATE_PARAMETERS>
bool remove_almost_degenerate_faces(TriangleMesh& tmesh,
const double cap_threshold,
const double needle_threshold,
const double collapse_length_threshold,
const CGAL_PMP_NP_CLASS& np)
{
return remove_almost_degenerate_faces(faces(tmesh), tmesh, cap_threshold, needle_threshold,
collapse_length_threshold, np);
}
template<class TriangleMesh>
bool remove_almost_degenerate_faces(TriangleMesh& tmesh,
const double cap_threshold,
const double needle_threshold,
const double collapse_length_threshold)
{
return remove_almost_degenerate_faces(faces(tmesh), tmesh,
cap_threshold, needle_threshold, collapse_length_threshold,
CGAL::parameters::all_default());
}
} // namespace experimental
} // namespace Polygon_mesh_processing
} // namespace CGAL
#endif // CGAL_POLYGON_MESH_PROCESSING_REMOVE_DEGENERACIES_H

View File

@ -0,0 +1,461 @@
// Copyright (c) 2015-2019 GeometryFactory (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s) : Sebastien Loriot,
// Mael Rouxel-Labbé
//
#ifndef CGAL_POLYGON_MESH_PROCESSING_MANIFOLDNESS_H
#define CGAL_POLYGON_MESH_PROCESSING_MANIFOLDNESS_H
#include <CGAL/license/Polygon_mesh_processing/repair.h>
#include <CGAL/Polygon_mesh_processing/internal/named_function_params.h>
#include <CGAL/Polygon_mesh_processing/internal/named_params_helper.h>
#include <CGAL/algorithm.h>
#include <CGAL/assertions.h>
#include <CGAL/boost/graph/helpers.h>
#include <CGAL/Dynamic_property_map.h>
#include <CGAL/iterator.h>
#include <CGAL/property_map.h>
#include <iterator>
#include <map>
#include <utility>
#include <vector>
namespace CGAL {
namespace Polygon_mesh_processing {
/// \ingroup PMP_repairing_grp
/// checks whether a vertex of a polygon mesh is non-manifold.
///
/// @tparam PolygonMesh a model of `HalfedgeListGraph`
///
/// @param v a vertex of `pm`
/// @param pm a triangle mesh containing `v`
///
/// \warning This function has linear runtime with respect to the size of the mesh.
///
/// \sa `duplicate_non_manifold_vertices()`
///
/// \return `true` if the vertex is non-manifold, `false` otherwise.
template <typename PolygonMesh>
bool is_non_manifold_vertex(typename boost::graph_traits<PolygonMesh>::vertex_descriptor v,
const PolygonMesh& pm)
{
typedef typename boost::graph_traits<PolygonMesh>::halfedge_descriptor halfedge_descriptor;
typedef CGAL::dynamic_halfedge_property_t<bool> Halfedge_property_tag;
typedef typename boost::property_map<PolygonMesh, Halfedge_property_tag>::const_type Visited_halfedge_map;
// Dynamic pmaps do not have default initialization values (yet)
Visited_halfedge_map visited_halfedges = get(Halfedge_property_tag(), pm);
for(halfedge_descriptor h : halfedges(pm))
put(visited_halfedges, h, false);
std::size_t incident_null_faces_counter = 0;
for(halfedge_descriptor h : halfedges_around_target(v, pm))
{
put(visited_halfedges, h, true);
if(CGAL::is_border(h, pm))
++incident_null_faces_counter;
}
if(incident_null_faces_counter > 1)
{
// The vertex is the sole connection between two connected components --> non-manifold
return true;
}
for(halfedge_descriptor h : halfedges(pm))
{
if(v == target(h, pm))
{
// Haven't seen that halfedge yet ==> more than one umbrella incident to 'v' ==> non-manifold
if(!get(visited_halfedges, h))
return true;
}
}
return false;
}
namespace internal {
template <typename G>
struct Vertex_collector
{
typedef typename boost::graph_traits<G>::vertex_descriptor vertex_descriptor;
bool has_old_vertex(const vertex_descriptor v) const { return collections.count(v) != 0; }
void tag_old_vertex(const vertex_descriptor v)
{
CGAL_precondition(!has_old_vertex(v));
collections[v];
}
void collect_vertices(vertex_descriptor v1, vertex_descriptor v2)
{
std::vector<vertex_descriptor>& verts = collections[v1];
if(verts.empty())
verts.push_back(v1);
verts.push_back(v2);
}
template<typename OutputIterator>
void dump(OutputIterator out)
{
typedef std::pair<const vertex_descriptor, std::vector<vertex_descriptor> > Pair_type;
for(const Pair_type& p : collections)
*out++ = p.second;
}
void dump(Emptyset_iterator) { }
std::map<vertex_descriptor, std::vector<vertex_descriptor> > collections;
};
template <typename PolygonMesh, typename VPM, typename ConstraintMap>
typename boost::graph_traits<PolygonMesh>::vertex_descriptor
create_new_vertex_for_sector(typename boost::graph_traits<PolygonMesh>::halfedge_descriptor sector_begin_h,
typename boost::graph_traits<PolygonMesh>::halfedge_descriptor sector_last_h,
PolygonMesh& pm,
const VPM& vpm,
const ConstraintMap& cmap)
{
typedef typename boost::graph_traits<PolygonMesh>::vertex_descriptor vertex_descriptor;
typedef typename boost::graph_traits<PolygonMesh>::halfedge_descriptor halfedge_descriptor;
vertex_descriptor old_vd = target(sector_begin_h, pm);
vertex_descriptor new_vd = add_vertex(pm);
put(vpm, new_vd, get(vpm, old_vd));
put(cmap, new_vd, true);
set_halfedge(new_vd, sector_begin_h, pm);
halfedge_descriptor h = sector_begin_h;
do
{
set_target(h, new_vd, pm);
if(h == sector_last_h)
break;
else
h = prev(opposite(h, pm), pm);
}
while(h != sector_begin_h); // for safety
CGAL_assertion(h != sector_begin_h);
return new_vd;
}
template <typename PolygonMesh, typename NamedParameters>
std::size_t make_umbrella_manifold(typename boost::graph_traits<PolygonMesh>::halfedge_descriptor h,
PolygonMesh& pm,
internal::Vertex_collector<PolygonMesh>& dmap,
const NamedParameters& np)
{
typedef typename boost::graph_traits<PolygonMesh>::vertex_descriptor vertex_descriptor;
typedef typename boost::graph_traits<PolygonMesh>::halfedge_descriptor halfedge_descriptor;
using parameters::get_parameter;
using parameters::choose_parameter;
typedef typename GetVertexPointMap<PolygonMesh, NamedParameters>::type VertexPointMap;
VertexPointMap vpm = choose_parameter(get_parameter(np, internal_np::vertex_point),
get_property_map(vertex_point, pm));
typedef typename internal_np::Lookup_named_param_def<internal_np::vertex_is_constrained_t,
NamedParameters,
Constant_property_map<vertex_descriptor, bool> // default (no constraint pmap)
>::type VerticesMap;
VerticesMap cmap = choose_parameter(get_parameter(np, internal_np::vertex_is_constrained),
Constant_property_map<vertex_descriptor, bool>(false));
std::size_t nb_new_vertices = 0;
vertex_descriptor old_v = target(h, pm);
put(cmap, old_v, true); // store the duplicates
// count the number of borders
int border_counter = 0;
halfedge_descriptor ih = h, done = ih, border_h = h;
do
{
if(is_border(ih, pm))
{
border_h = ih;
++border_counter;
}
ih = prev(opposite(ih, pm), pm);
}
while(ih != done);
bool is_non_manifold_within_umbrella = (border_counter > 1);
if(!is_non_manifold_within_umbrella)
{
const bool first_time_meeting_v = !dmap.has_old_vertex(old_v);
if(first_time_meeting_v)
{
// The star is manifold, so if it is the first time we have met that vertex,
// there is nothing to do, we just keep the same vertex.
set_halfedge(old_v, h, pm); // to ensure halfedge(old_v, pm) stays valid
dmap.tag_old_vertex(old_v); // so that we know we have met old_v already, next time, we'll have to duplicate
}
else
{
// This is not the canonical star associated to 'v'.
// Create a new vertex, and move the whole star to that new vertex
halfedge_descriptor last_h = opposite(next(h, pm), pm);
vertex_descriptor new_v = create_new_vertex_for_sector(h, last_h, pm, vpm, cmap);
dmap.collect_vertices(old_v, new_v);
nb_new_vertices = 1;
}
}
// if there is more than one sector, look at each sector and split them away from the main one
else
{
// the first manifold sector, described by two halfedges
halfedge_descriptor sector_start_h = border_h;
CGAL_assertion(is_border(border_h, pm));
bool should_stop = false;
bool is_main_sector = true;
do
{
CGAL_assertion(is_border(sector_start_h, pm));
// collect the sector and split it away if it must be
halfedge_descriptor sector_last_h = sector_start_h;
do
{
halfedge_descriptor next_h = prev(opposite(sector_last_h, pm), pm);
if(is_border(next_h, pm))
break;
sector_last_h = next_h;
}
while(sector_last_h != sector_start_h);
CGAL_assertion(!is_border(sector_last_h, pm));
CGAL_assertion(sector_last_h != sector_start_h);
halfedge_descriptor next_start_h = prev(opposite(sector_last_h, pm), pm);
// there are multiple CCs incident to this particular vertex, and we should create a new vertex
// if it's not the first umbrella around 'old_v' or not the first sector, but only not if it's
// both the first umbrella and first sector.
bool must_create_new_vertex = (!is_main_sector || dmap.has_old_vertex(old_v));
// In any case, we must set up the next pointer correctly
set_next(sector_start_h, opposite(sector_last_h, pm), pm);
if(must_create_new_vertex)
{
vertex_descriptor new_v = create_new_vertex_for_sector(sector_start_h, sector_last_h, pm, vpm, cmap);
dmap.collect_vertices(old_v, new_v);
++nb_new_vertices;
}
else
{
// We are in the first sector and first star, ensure that halfedge(old_v, pm) stays valid
set_halfedge(old_v, sector_start_h, pm);
}
is_main_sector = false;
sector_start_h = next_start_h;
should_stop = (sector_start_h == border_h);
}
while(!should_stop);
}
return nb_new_vertices;
}
} // end namespace internal
/// \ingroup PMP_repairing_grp
/// collects the non-manifold vertices (if any) present in the mesh. A non-manifold vertex `v` is returned
/// via one incident halfedge `h` such that `target(h, pm) = v` for all the umbrellas that `v` apppears in
/// (an <i>umbrella</i> being the set of faces incident to all the halfedges reachable by walking around `v`
/// using `hnext = prev(opposite(h, pm), pm)`, starting from `h`).
///
/// @tparam PolygonMesh a model of `HalfedgeListGraph`
/// @tparam OutputIterator a model of `OutputIterator` holding objects of type
/// `boost::graph_traits<PolygonMesh>::%halfedge_descriptor`
///
/// @param pm a triangle mesh
/// @param out the output iterator that collects halfedges incident to `v`
///
/// \sa `is_non_manifold_vertex()`
/// \sa `duplicate_non_manifold_vertices()`
///
/// \return the output iterator.
template <typename PolygonMesh, typename OutputIterator>
OutputIterator non_manifold_vertices(const PolygonMesh& pm,
OutputIterator out)
{
// Non-manifoldness can appear either:
// - if 'pm' is pinched at a vertex. While traversing the incoming halfedges at this vertex,
// we will meet strictly more than one border halfedge.
// - if there are multiple umbrellas around a vertex. In that case, we will find a non-visited
// halfedge that has for target a vertex that is already visited.
typedef typename boost::graph_traits<PolygonMesh>::vertex_descriptor vertex_descriptor;
typedef typename boost::graph_traits<PolygonMesh>::halfedge_descriptor halfedge_descriptor;
typedef CGAL::dynamic_vertex_property_t<bool> Vertex_bool_tag;
typedef typename boost::property_map<PolygonMesh, Vertex_bool_tag>::const_type Known_manifold_vertex_map;
typedef CGAL::dynamic_vertex_property_t<halfedge_descriptor> Vertex_halfedge_tag;
typedef typename boost::property_map<PolygonMesh, Vertex_halfedge_tag>::const_type Visited_vertex_map;
typedef CGAL::dynamic_halfedge_property_t<bool> Halfedge_property_tag;
typedef typename boost::property_map<PolygonMesh, Halfedge_property_tag>::const_type Visited_halfedge_map;
Known_manifold_vertex_map known_nm_vertices = get(Vertex_bool_tag(), pm);
Visited_vertex_map visited_vertices = get(Vertex_halfedge_tag(), pm);
Visited_halfedge_map visited_halfedges = get(Halfedge_property_tag(), pm);
halfedge_descriptor null_h = boost::graph_traits<PolygonMesh>::null_halfedge();
// Dynamic pmaps do not have default initialization values (yet)
for(vertex_descriptor v : vertices(pm))
{
put(known_nm_vertices, v, false);
put(visited_vertices, v, null_h);
}
for(halfedge_descriptor h : halfedges(pm))
put(visited_halfedges, h, false);
for(halfedge_descriptor h : halfedges(pm))
{
// If 'h' is not visited yet, we walk around the target of 'h' and mark these
// halfedges as visited. Thus, if we are here and the target is already marked as visited,
// it means that the vertex is non manifold.
if(!get(visited_halfedges, h))
{
put(visited_halfedges, h, true);
bool is_non_manifold = false;
vertex_descriptor v = target(h, pm);
if(get(visited_vertices, v) != null_h) // already seen this vertex, but not from this star
{
is_non_manifold = true;
// if this is the second time we visit that vertex and the first star was manifold, we have
// never reported the first star, but we must now
if(!get(known_nm_vertices, v))
*out++ = get(visited_vertices, v); // that's a halfedge of the first star we've seen 'v' in
}
else
{
// first time we meet this vertex, just mark it so, and keep the halfedge we found the vertex with in memory
put(visited_vertices, v, h);
}
// While walking the star of this halfedge, if we meet a border halfedge more than once,
// it means the mesh is pinched and we are also in the case of a non-manifold situation
halfedge_descriptor ih = h, done = ih;
int border_counter = 0;
do
{
put(visited_halfedges, ih, true);
if(is_border(ih, pm))
++border_counter;
ih = prev(opposite(ih, pm), pm);
}
while(ih != done);
if(border_counter > 1)
is_non_manifold = true;
if(is_non_manifold)
{
*out++ = h;
put(known_nm_vertices, v, true);
}
}
}
return out;
}
/// \ingroup PMP_repairing_grp
/// duplicates all the non-manifold vertices of the input mesh.
///
/// @tparam PolygonMesh a model of `HalfedgeListGraph` and `MutableHalfedgeGraph`
/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters"
///
/// @param pm the surface mesh to be repaired
/// @param np optional \ref pmp_namedparameters "Named Parameters" described below
///
/// \cgalNamedParamsBegin
/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`.
/// The type of this map is model of `ReadWritePropertyMap`.
/// If this parameter is omitted, an internal property map for
/// `CGAL::vertex_point_t` should be available in `PolygonMesh`
/// \cgalParamEnd
/// \cgalParamBegin{vertex_is_constrained_map} a writable property map with `vertex_descriptor`
/// as key and `bool` as `value_type`. `put(pmap, v, true)` will be called for each duplicated
/// vertices, as well as the original non-manifold vertex in the input mesh.
/// \cgalParamEnd
/// \cgalParamBegin{output_iterator} a model of `OutputIterator` with value type
/// `std::vector<vertex_descriptor>`. The first vertex of each vector is a non-manifold vertex
/// of the input mesh, followed by the new vertices that were created to fix this precise
/// non-manifold configuration.
/// \cgalParamEnd
/// \cgalNamedParamsEnd
///
/// \return the number of vertices created.
template <typename PolygonMesh, typename NamedParameters>
std::size_t duplicate_non_manifold_vertices(PolygonMesh& pm,
const NamedParameters& np)
{
using parameters::get_parameter;
using parameters::choose_parameter;
typedef boost::graph_traits<PolygonMesh> GT;
typedef typename GT::halfedge_descriptor halfedge_descriptor;
typedef typename internal_np::Lookup_named_param_def<internal_np::output_iterator_t,
NamedParameters,
Emptyset_iterator>::type Output_iterator;
Output_iterator out = choose_parameter(get_parameter(np, internal_np::output_iterator),
Emptyset_iterator());
std::vector<halfedge_descriptor> non_manifold_cones;
non_manifold_vertices(pm, std::back_inserter(non_manifold_cones));
internal::Vertex_collector<PolygonMesh> dmap;
std::size_t nb_new_vertices = 0;
if(!non_manifold_cones.empty())
{
for(halfedge_descriptor h : non_manifold_cones)
nb_new_vertices += internal::make_umbrella_manifold(h, pm, dmap, np);
dmap.dump(out);
}
return nb_new_vertices;
}
template <class PolygonMesh>
std::size_t duplicate_non_manifold_vertices(PolygonMesh& pm)
{
return duplicate_non_manifold_vertices(pm, parameters::all_default());
}
} // namespace Polygon_mesh_processing
} // namespace CGAL
#endif // CGAL_POLYGON_MESH_PROCESSING_MANIFOLDNESS_H

View File

@ -0,0 +1,196 @@
// Copyright (c) 2019 GeometryFactory (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s) : Sebastien Loriot and Maxime Gimeno
#ifndef CGAL_ORIENT_POLYGON_SOUP_EXTENSION_H
#define CGAL_ORIENT_POLYGON_SOUP_EXTENSION_H
#include <CGAL/license/Polygon_mesh_processing/repair.h>
#include <CGAL/AABB_tree.h>
#include <CGAL/AABB_face_graph_triangle_primitive.h>
#include <CGAL/AABB_traits.h>
#include <CGAL/Polygon_mesh_processing/shape_predicates.h>
#include <CGAL/Polygon_mesh_processing/compute_normal.h>
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup.h>
#include <boost/iterator/counting_iterator.hpp>
#include <boost/iterator/filter_iterator.hpp>
#ifdef CGAL_LINKED_WITH_TBB
#include <tbb/parallel_for.h>
#endif // CGAL_LINKED_WITH_TBB
namespace CGAL {
namespace Polygon_mesh_processing {
/*!
* \ingroup PMP_orientation_grp
* duplicates each point \a p at which the intersection
* of an infinitesimally small ball centered at \a p
* with the polygons incident to it is not a topological disk.
*
* @tparam PointRange a model of the concepts `RandomAccessContainer`
* and `BackInsertionSequence` whose `value_type` is the point type.
* @tparam PolygonRange a model of the concept `RandomAccessContainer`
* whose `value_type` is a model of the concept `RandomAccessContainer`
* whose `value_type` is `std::size_t`, and is also a model of `BackInsertionSequence`.
*
* @param points points of the soup of polygons. Some additional points might be pushed back to resolve
* non-manifoldness or non-orientability issues.
* @param polygons each element in the vector describes a polygon using the indices of the points in `points`.
* If needed the order of the indices of a polygon might be reversed.
* @return `false` if some points were duplicated, thus producing a self-intersecting surface mesh.
* @return `true` otherwise.
* @sa `orient_polygon_soup()`
*/
template <class PointRange, class PolygonRange>
bool
duplicate_non_manifold_edges_in_polygon_soup(PointRange& points,
PolygonRange& polygons)
{
std::size_t inital_nb_pts = points.size();
typedef CGAL::Polygon_mesh_processing::internal::
Polygon_soup_orienter<PointRange, PolygonRange> Orienter;
Orienter orienter(points, polygons);
orienter.fill_edge_map();
// make edges to duplicate
for(std::size_t i1=0;i1<points.size();++i1)
for(const typename Orienter::Internal_map_type::value_type& i2_and_pids : orienter.edges[i1])
if (i2_and_pids.second.size() > 1)
orienter.set_edge_marked(i1,i2_and_pids.first,orienter.marked_edges);
orienter.duplicate_singular_vertices();
return inital_nb_pts==points.size();
}
/*!
* \ingroup PMP_orientation_grp
* orients each triangle of a triangle soup using the orientation of its
* closest non degenerate triangle in `tm_ref`.
* \tparam Concurrency_tag enables sequential versus parallel orientation.
Possible values are `Sequential_tag` (the default),
`Parallel_if_available_tag`, and `Parallel_tag`.
* \tparam PointRange a model of the concepts `RandomAccessContainer`
* and `BackInsertionSequence` whose value type is the point type.
* @tparam TriangleRange a model of the concept `RandomAccessContainer`
* whose `value_type` is a model of the concept `RandomAccessContainer`
* whose `value_type` is `std::size_t`and of size 3.
* @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` .
*
* \param tm_ref the reference triangle_mesh.
* \param points the points of the soup.
* \param triangles the triangles of the soup.
* @param np optional sequence of \ref pmp_namedparameters among the ones listed below
*
* \cgalNamedParamsBegin
* \cgalParamBegin{vertex_point_map}
* the property map with the points associated to the vertices of `tm_ref`.
* If this parameter is omitted, an internal property map for
* `CGAL::vertex_point_t` must be available in `TriangleMesh`
* \cgalParamEnd
* \cgalParamBegin{geom_traits} a geometric traits class instance.
* The traits class must provide the nested functor `Collinear_3`
* to check whether three points are collinear.
* \cgalParamEnd
* \cgalNamedParamsEnd
*
* \attention The types of points in `PointRange`, `geom_traits` and `vertex_point_map` must be the same.
*/
template <class Concurrency_tag = Sequential_tag, class PointRange, class TriangleRange,
class TriangleMesh, class NamedParameters>
void
orient_triangle_soup_with_reference_triangle_mesh(
const TriangleMesh& tm_ref,
PointRange& points,
TriangleRange& triangles,
const NamedParameters& np)
{
namespace PMP = CGAL::Polygon_mesh_processing;
typedef boost::graph_traits<TriangleMesh> GrT;
typedef typename GrT::face_descriptor face_descriptor;
typedef typename PointRange::value_type Point_3;
typedef typename GetGeomTraits<TriangleMesh, NamedParameters>::type K;
typedef typename
GetVertexPointMap<TriangleMesh, NamedParameters>::const_type Vpm;
Vpm vpm = parameters::choose_parameter(parameters::get_parameter(np, internal_np::vertex_point),
get_const_property_map(CGAL::vertex_point, tm_ref));
typedef std::function<bool(face_descriptor)> Face_predicate;
Face_predicate is_not_deg =
[&tm_ref, np](face_descriptor f)
{
return !PMP::is_degenerate_triangle_face(f, tm_ref, np);
};
// build a tree filtering degenerate faces
typedef CGAL::AABB_face_graph_triangle_primitive<TriangleMesh, Vpm> Primitive;
typedef CGAL::AABB_traits<K, Primitive> Tree_traits;
boost::filter_iterator<Face_predicate, typename GrT::face_iterator>
begin(is_not_deg, faces(tm_ref).begin(), faces(tm_ref).end()),
end(is_not_deg, faces(tm_ref).end(), faces(tm_ref).end());
CGAL::AABB_tree<Tree_traits> tree(begin, end, tm_ref, vpm);
// now orient the faces
tree.build();
tree.accelerate_distance_queries();
auto process_facet =
[&points, &tree, &tm_ref, &triangles](std::size_t fid) {
const Point_3& p0 = points[triangles[fid][0]];
const Point_3& p1 = points[triangles[fid][1]];
const Point_3& p2 = points[triangles[fid][2]];
const Point_3 mid = CGAL::centroid(p0, p1, p2);
std::pair<Point_3, face_descriptor> pt_and_f =
tree.closest_point_and_primitive(mid);
auto face_ref_normal = PMP::compute_face_normal(pt_and_f.second, tm_ref);
if(face_ref_normal * cross_product(p1-p0, p2-p0) < 0) {
std::swap(triangles[fid][1], triangles[fid][2]);
}
};
#if !defined(CGAL_LINKED_WITH_TBB)
CGAL_static_assertion_msg (!(boost::is_convertible<Concurrency_tag, CGAL::Parallel_tag>::value),
"Parallel_tag is enabled but TBB is unavailable.");
#else
if (boost::is_convertible<Concurrency_tag,CGAL::Parallel_tag>::value)
tbb::parallel_for(std::size_t(0), triangles.size(), std::size_t(1), process_facet);
else
#endif
std::for_each(
boost::counting_iterator<std::size_t> (0),
boost::counting_iterator<std::size_t> (triangles.size()),
process_facet);
}
template <class Concurrency_tag = Sequential_tag, class PointRange, class TriangleRange,
class TriangleMesh>
void
orient_triangle_soup_with_reference_triangle_mesh(
const TriangleMesh& tm_ref,
PointRange& points,
TriangleRange& triangles)
{
orient_triangle_soup_with_reference_triangle_mesh<Concurrency_tag>(tm_ref, points, triangles, CGAL::parameters::all_default());
}
}}//end namespace CGAL::Polygon_mesh_processing
#endif // CGAL_ORIENT_POLYGON_SOUP_EXTENSION_H

View File

@ -19,6 +19,7 @@
#include <algorithm>
#include <CGAL/Polygon_mesh_processing/connected_components.h>
#include <CGAL/Polygon_mesh_processing/stitch_borders.h>
#include <CGAL/Polygon_mesh_processing/compute_normal.h>
#include <CGAL/Polygon_mesh_processing/internal/named_function_params.h>
#include <CGAL/Polygon_mesh_processing/internal/named_params_helper.h>
@ -1337,6 +1338,206 @@ void orient_to_bound_a_volume(TriangleMesh& tm)
{
orient_to_bound_a_volume(tm, parameters::all_default());
}
/*!
* \ingroup PMP_orientation_grp
* reverses the connected components of `tm` having compatible boundary cycles
* that could be merged if their orientation were made compatible, and stitches them.
* Connected components are examined by increasing number of faces.
*
* @tparam PolygonMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`.
* @tparam NamedParameters a sequence of \ref pmp_namedparameters
*
* @param pm a surface mesh
* @param np optional sequence of \ref pmp_namedparameters among the ones listed below
*
* \cgalNamedParamsBegin
* \cgalParamBegin{vertex_point_map}
* the property map with the points associated to the vertices of `pm`.
* If this parameter is omitted, an internal property map for
* `CGAL::vertex_point_t` must be available in `PolygonMesh`
* \cgalParamEnd
* \cgalParamBegin{face_index_map}
* a property map containing an index for each face initialized from 0 to num_faces(pm).
* \cgalParamEnd
* \cgalParamBegin{maximum_number_of_faces}
* if not 0 (default), a connected component is considered reversible only
* if it has no more faces than the value given. Otherwise, it is always considered reversible.
* \cgalParamEnd
* \cgalNamedParamsEnd
*/
template <class PolygonMesh, class NamedParameters>
void merge_reversible_connected_components(PolygonMesh& pm,
const NamedParameters& np)
{
typedef boost::graph_traits<PolygonMesh> GrT;
typedef typename GrT::face_descriptor face_descriptor;
typedef typename GrT::halfedge_descriptor halfedge_descriptor;
typedef typename GetVertexPointMap<PolygonMesh, NamedParameters>::const_type Vpm;
typedef typename boost::property_traits<Vpm>::value_type Point_3;
Vpm vpm = parameters::choose_parameter(parameters::get_parameter(np, internal_np::vertex_point),
get_const_property_map(vertex_point, pm));
typedef std::size_t F_cc_id;
typedef std::size_t B_cc_id;
typedef typename CGAL::GetInitializedFaceIndexMap<PolygonMesh, NamedParameters>::const_type Fidmap;
Fidmap fim = CGAL::get_initialized_face_index_map(pm, np);
typedef dynamic_face_property_t<F_cc_id> Face_property_tag;
typedef typename boost::property_map<PolygonMesh, Face_property_tag>::type Face_cc_map;
Face_cc_map f_cc_ids = get(Face_property_tag(), pm);
F_cc_id nb_cc = connected_components(pm, f_cc_ids, parameters::face_index_map(fim));
std::vector<std::size_t> nb_faces_per_cc(nb_cc, 0);
for (face_descriptor f : faces(pm))
nb_faces_per_cc[ get(f_cc_ids, f) ]+=1;
std::map< std::pair<Point_3, Point_3>, std::vector<halfedge_descriptor> > border_hedges_map;
std::vector<halfedge_descriptor> border_hedges;
typedef typename boost::property_map<PolygonMesh, dynamic_halfedge_property_t<B_cc_id> >::type H_to_bcc_id;
H_to_bcc_id h_bcc_ids = get(dynamic_halfedge_property_t<B_cc_id>(), pm);
const B_cc_id base_value(-1);
const B_cc_id FILTERED_OUT(-2);
// collect border halfedges
for (halfedge_descriptor h : halfedges(pm))
if ( is_border(h, pm) )
{
put(h_bcc_ids, h, base_value);
border_hedges.push_back(h);
}
// compute the border cc id of all halfedges and mark those duplicated in their own cycle
B_cc_id bcc_id=0;
for (halfedge_descriptor h : border_hedges)
{
if (get(h_bcc_ids,h) == base_value)
{
typedef std::map< std::pair<Point_3, Point_3>, halfedge_descriptor> Hmap;
Hmap hmap;
for (halfedge_descriptor hh : halfedges_around_face(h, pm))
{
std::pair< typename Hmap::iterator, bool > insert_res =
hmap.insert(
std::make_pair(
make_sorted_pair(get(vpm, source(hh, pm)),
get(vpm, target(hh,pm))), hh) );
if (insert_res.second)
put(h_bcc_ids, hh, bcc_id);
else
{
put(h_bcc_ids, hh, FILTERED_OUT);
put(h_bcc_ids, insert_res.first->second, FILTERED_OUT);
}
}
++bcc_id;
}
}
// fill endpoints -> hedges
for (halfedge_descriptor h : border_hedges)
{
if ( get(h_bcc_ids, h) != FILTERED_OUT)
border_hedges_map[std::make_pair(get(vpm, source(h, pm)), get(vpm, target(h, pm)))].push_back(h);
}
// max nb of faces for a CC to be reversed
const std::size_t threshold =
parameters::choose_parameter( parameters::get_parameter(np, internal_np::maximum_number_of_faces), 0);
std::vector<bool> border_cycle_to_ignore(bcc_id, false);
std::vector<F_cc_id> cycle_f_cc_id(bcc_id);
std::vector< std::vector<F_cc_id> > patch_neighbors(nb_cc);
for (const auto& p : border_hedges_map)
{
const std::vector<halfedge_descriptor>& hedges = p.second;
switch(hedges.size())
{
case 1:
// isolated border hedge nothing to do
break;
case 2:
{
F_cc_id cc_id_0 = get(f_cc_ids, face(opposite(hedges[0], pm), pm)),
cc_id_1 = get(f_cc_ids, face(opposite(hedges[1], pm), pm));
if (cc_id_0!=cc_id_1)
{
cycle_f_cc_id[ get(h_bcc_ids, hedges[0]) ] = cc_id_0;
cycle_f_cc_id[ get(h_bcc_ids, hedges[1]) ] = cc_id_1;
// WARNING: we might have duplicates here but it is not important for our usage
patch_neighbors[cc_id_0].push_back(cc_id_1);
patch_neighbors[cc_id_1].push_back(cc_id_0);
break;
}
CGAL_FALLTHROUGH;
}
default:
for (halfedge_descriptor h : hedges)
border_cycle_to_ignore[get(h_bcc_ids, h)]=true;
}
}
// sort the connected components with potential matches using their number
// of faces (sorted by decreasing number of faces)
std::set<F_cc_id> ccs_to_reverse;
std::vector<bool> reversible(nb_cc, false);
std::set< F_cc_id, std::function<bool(F_cc_id,F_cc_id)> > queue(
[&nb_faces_per_cc](F_cc_id i, F_cc_id j)
{return nb_faces_per_cc[i]==nb_faces_per_cc[j] ? i<j : nb_faces_per_cc[i]>nb_faces_per_cc[j];}
);
for (B_cc_id i=0; i<bcc_id; ++i)
{
if ( !border_cycle_to_ignore[i] )
{
reversible[ cycle_f_cc_id[i] ] = true;
queue.insert(cycle_f_cc_id[i]);
}
}
// consider largest CC selected and reverse the neighbor patches if
// not already reversed or not marked as reversible
while( !queue.empty() )
{
F_cc_id f_cc_id = *queue.begin();
queue.erase( queue.begin() );
CGAL_assertion( reversible[f_cc_id] );
for (F_cc_id id : patch_neighbors[f_cc_id])
{
if (reversible[id] && (threshold==0 || threshold >= nb_faces_per_cc[id]))
{
CGAL_assertion( nb_faces_per_cc[f_cc_id] >= nb_faces_per_cc[id] );
ccs_to_reverse.insert(id);
reversible[id]=false;
queue.erase(id);
}
}
}
// reverse ccs and stitches boundaries
std::vector<face_descriptor> faces_to_reverse;
for (face_descriptor f : faces(pm))
if ( ccs_to_reverse.count( get(f_cc_ids, f) ) != 0 )
faces_to_reverse.push_back(f);
if ( !faces_to_reverse.empty() )
{
reverse_face_orientations(faces_to_reverse, pm);
stitch_borders(pm, np);
}
}
template <class PolygonMesh>
void merge_reversible_connected_components(PolygonMesh& pm)
{
merge_reversible_connected_components(pm, parameters::all_default());
}
} // namespace Polygon_mesh_processing
} // namespace CGAL
#endif // CGAL_ORIENT_POLYGON_MESH_H

View File

@ -257,7 +257,7 @@ void polygon_soup_to_polygon_mesh(const PointRange& points,
const NamedParameters_PM& np_pm)
{
CGAL_precondition_msg(is_polygon_soup_a_polygon_mesh(polygons),
"Input soup needs to be a polygon mesh!");
"Input soup needs to define a valid polygon mesh! See is_polygon_soup_a_polygon_mesh() for further information.");
using parameters::choose_parameter;
using parameters::get_parameter;

View File

@ -23,6 +23,7 @@
#include <CGAL/boost/graph/iterator.h>
#include <CGAL/boost/graph/helpers.h>
#include <boost/range/has_range_iterator.hpp>
#include <boost/graph/graph_traits.hpp>
#include <limits>
@ -85,6 +86,81 @@ bool is_degenerate_edge(typename boost::graph_traits<PolygonMesh>::edge_descript
return is_degenerate_edge(e, pm, parameters::all_default());
}
/// \ingroup PMP_repairing_grp
/// collects the degenerate edges within a given range of edges.
///
/// @tparam EdgeRange a model of `Range` with value type `boost::graph_traits<TriangleMesh>::%edge_descriptor`
/// @tparam TriangleMesh a model of `HalfedgeGraph`
/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters"
///
/// @param edges a subset of edges of `tm`
/// @param tm a triangle mesh
/// @param out an output iterator in which the degenerate edges are written
/// @param np optional \ref pmp_namedparameters "Named Parameters" described below
///
/// \cgalNamedParamsBegin
/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`.
/// The type of this map is model of `ReadWritePropertyMap`.
/// If this parameter is omitted, an internal property map for
/// `CGAL::vertex_point_t` should be available in `TriangleMesh`
/// \cgalParamEnd
/// \cgalParamBegin{geom_traits} a geometric traits class instance.
/// The traits class must provide the nested type `Point_3`,
/// and the nested functor `Equal_3` to check whether two points are identical.
/// \cgalParamEnd
/// \cgalNamedParamsEnd
template <typename EdgeRange, typename TriangleMesh, typename OutputIterator, typename NamedParameters>
OutputIterator degenerate_edges(const EdgeRange& edges,
const TriangleMesh& tm,
OutputIterator out,
const NamedParameters& np)
{
typedef typename boost::graph_traits<TriangleMesh>::edge_descriptor edge_descriptor;
for(edge_descriptor ed : edges)
if(is_degenerate_edge(ed, tm, np))
*out++ = ed;
return out;
}
template <typename EdgeRange, typename TriangleMesh, typename OutputIterator>
OutputIterator degenerate_edges(const EdgeRange& edges,
const TriangleMesh& tm,
OutputIterator out,
typename boost::enable_if<
typename boost::has_range_iterator<EdgeRange>
>::type* = 0)
{
return degenerate_edges(edges, tm, out, CGAL::parameters::all_default());
}
/// \ingroup PMP_repairing_grp
/// calls the function `degenerate_edges()` with the range: `edges(tm)`.
///
/// See above for the comprehensive description of the parameters.
///
template <typename TriangleMesh, typename OutputIterator, typename NamedParameters>
OutputIterator degenerate_edges(const TriangleMesh& tm,
OutputIterator out,
const NamedParameters& np
#ifndef DOXYGEN_RUNNING
,
typename boost::disable_if<
boost::has_range_iterator<TriangleMesh>
>::type* = 0
#endif
)
{
return degenerate_edges(edges(tm), tm, out, np);
}
template <typename TriangleMesh, typename OutputIterator>
OutputIterator degenerate_edges(const TriangleMesh& tm, OutputIterator out)
{
return degenerate_edges(edges(tm), tm, out, CGAL::parameters::all_default());
}
/// \ingroup PMP_repairing_grp
/// checks whether a triangle face is degenerate.
/// A triangle face is considered degenerate if the geometric positions of its vertices are collinear.
@ -142,6 +218,83 @@ bool is_degenerate_triangle_face(typename boost::graph_traits<TriangleMesh>::fac
return CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, tm, parameters::all_default());
}
/// \ingroup PMP_repairing_grp
/// collects the degenerate faces within a given range of faces.
///
/// @tparam FaceRange a model of `Range` with value type `boost::graph_traits<TriangleMesh>::%face_descriptor`
/// @tparam TriangleMesh a model of `FaceGraph`
/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters"
///
/// @param faces a subset of faces of `tm`
/// @param tm a triangle mesh
/// @param out an output iterator in which the degenerate faces are put
/// @param np optional \ref pmp_namedparameters "Named Parameters" described below
///
/// \cgalNamedParamsBegin
/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`.
/// The type of this map is model of `ReadWritePropertyMap`.
/// If this parameter is omitted, an internal property map for
/// `CGAL::vertex_point_t` should be available in `TriangleMesh`
/// \cgalParamEnd
/// \cgalParamBegin{geom_traits} a geometric traits class instance.
/// The traits class must provide the nested functor `Collinear_3`
/// to check whether three points are collinear.
/// \cgalParamEnd
/// \cgalNamedParamsEnd
///
template <typename FaceRange, typename TriangleMesh, typename OutputIterator, typename NamedParameters>
OutputIterator degenerate_faces(const FaceRange& faces,
const TriangleMesh& tm,
OutputIterator out,
const NamedParameters& np)
{
typedef typename boost::graph_traits<TriangleMesh>::face_descriptor face_descriptor;
for(face_descriptor fd : faces)
{
if(is_degenerate_triangle_face(fd, tm, np))
*out++ = fd;
}
return out;
}
template <typename FaceRange, typename TriangleMesh, typename OutputIterator>
OutputIterator degenerate_faces(const FaceRange& faces,
const TriangleMesh& tm,
OutputIterator out,
typename boost::enable_if<
boost::has_range_iterator<FaceRange>
>::type* = 0)
{
return degenerate_faces(faces, tm, out, CGAL::parameters::all_default());
}
/// \ingroup PMP_repairing_grp
/// calls the function `degenerate_faces()` with the range: `faces(tm)`.
///
/// See above for the comprehensive description of the parameters.
///
template <typename TriangleMesh, typename OutputIterator, typename NamedParameters>
OutputIterator degenerate_faces(const TriangleMesh& tm,
OutputIterator out,
const NamedParameters& np
#ifndef DOXYGEN_RUNNING
,
typename boost::disable_if<
boost::has_range_iterator<TriangleMesh>
>::type* = 0
#endif
)
{
return degenerate_faces(faces(tm), tm, out, np);
}
template <typename TriangleMesh, typename OutputIterator>
OutputIterator degenerate_faces(const TriangleMesh& tm, OutputIterator out)
{
return degenerate_faces(faces(tm), tm, out, CGAL::parameters::all_default());
}
/// \ingroup PMP_repairing_grp
/// checks whether a triangle face is needle.
/// A triangle is said to be a <i>needle</i> if its longest edge is much longer than its shortest edge.

View File

@ -182,7 +182,7 @@ void smooth_mesh(const FaceRange& faces,
}
ECMap ecmap = choose_parameter(get_parameter(np, internal_np::edge_is_constrained),
Constant_property_map<edge_descriptor, bool>(false));
Constant_property_map<edge_descriptor, bool>(false));
// a constrained edge has constrained extremities
for(face_descriptor f : faces)
@ -234,8 +234,16 @@ void smooth_mesh(const FaceRange& faces,
for(unsigned int i=0; i<nb_iterations; ++i)
{
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cout << "Iteration #" << i << std::endl;
#endif
if(use_area_smoothing)
{
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cout << "Smooth areas..." << std::endl;
#endif
// First apply area smoothing...
area_smoother.optimize(use_safety_constraints /*check for bad faces*/,
false /*apply moves as soon as they're calculated*/,
@ -244,7 +252,7 @@ void smooth_mesh(const FaceRange& faces,
{
if(use_safety_constraints && does_self_intersect(tmesh))
{
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cerr << "Cannot re-project as there are self-intersections in the mesh!\n";
#endif
break;
@ -260,6 +268,10 @@ void smooth_mesh(const FaceRange& faces,
// ... then angle smoothing
if(use_angle_smoothing)
{
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cout << "Smooth angles..." << std::endl;
#endif
angle_smoother.optimize(use_safety_constraints /*check for bad faces*/,
true /*apply all moves at once*/,
use_safety_constraints /*check if the min angle is improved*/);
@ -268,7 +280,7 @@ void smooth_mesh(const FaceRange& faces,
{
if(use_safety_constraints && does_self_intersect(tmesh))
{
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cerr << "Can't do re-projection, there are self-intersections in the mesh!\n";
#endif
break;

View File

@ -149,7 +149,7 @@ void smooth_shape(const FaceRange& faces,
for(unsigned int iter=0; iter<nb_iterations; ++iter)
{
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cout << "iteration #" << iter << std::endl;
#endif
@ -161,7 +161,7 @@ void smooth_shape(const FaceRange& faces,
}
else
{
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cerr << "Failed to solve system!" << std::endl;
#endif
break;

View File

@ -841,7 +841,7 @@ std::size_t stitch_borders(PolygonMesh& pmesh,
template <typename PolygonMesh,
typename HalfedgePairsRange>
std::size_t stitch_borders(PolygonMesh& pmesh,
const HalfedgePairsRange& hedge_pairs_to_stitch)
const HalfedgePairsRange& hedge_pairs_to_stitch)
{
return stitch_borders(pmesh, hedge_pairs_to_stitch, CGAL::parameters::all_default());
}
@ -890,9 +890,9 @@ std::size_t stitch_borders(PolygonMesh& pmesh,
halfedge_descriptor;
std::vector< std::pair<halfedge_descriptor, halfedge_descriptor> > hedge_pairs_to_stitch;
typedef typename GetVertexPointMap<PolygonMesh, CGAL_PMP_NP_CLASS>::const_type VPMap;
VPMap vpm = choose_parameter(get_parameter(np, internal_np::vertex_point),
get_const_property_map(vertex_point, pmesh));
typedef typename GetVertexPointMap<PolygonMesh, CGAL_PMP_NP_CLASS>::const_type VPM;
VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point),
get_const_property_map(vertex_point, pmesh));
#ifdef CGAL_PMP_STITCHING_DEBUG
std::cout << "------- Stitch cycles..." << std::endl;
@ -907,7 +907,7 @@ std::size_t stitch_borders(PolygonMesh& pmesh,
internal::collect_duplicated_stitchable_boundary_edges(pmesh,
std::back_inserter(hedge_pairs_to_stitch),
internal::Less_for_halfedge<PolygonMesh, VPMap>(pmesh, vpm),
internal::Less_for_halfedge<PolygonMesh, VPM>(pmesh, vpm),
vpm, np);
res += stitch_borders(pmesh, hedge_pairs_to_stitch, np);

View File

@ -33,6 +33,8 @@
#include <CGAL/Polygon_mesh_processing/bbox.h>
#include <CGAL/Polygon_mesh_processing/border.h>
#include <CGAL/Polygon_mesh_processing/repair.h>
#include <CGAL/Polygon_mesh_processing/repair_degeneracies.h>
#include <CGAL/Polygon_mesh_processing/repair_self_intersections.h>
#include <CGAL/Polygon_mesh_processing/remesh.h>
#include <CGAL/Polygon_mesh_processing/corefinement.h>
#include <CGAL/Polygon_mesh_processing/detect_features.h>
@ -46,7 +48,7 @@
#include <CGAL/Polygon_mesh_processing/merge_border_vertices.h>
#include <CGAL/Polygon_mesh_processing/smooth_mesh.h>
#include <CGAL/Polygon_mesh_processing/smooth_shape.h>
#include <CGAL/Polygon_mesh_processing/internal/remove_degeneracies.h>
#include <CGAL/Polygon_mesh_processing/manifoldness.h>
// the named parameter header being not documented the doc is put here for now
#ifdef DOXYGEN_RUNNING

View File

@ -97,13 +97,16 @@ endif()
create_single_source_cgal_program("test_pmp_locate.cpp")
create_single_source_cgal_program("test_pmp_snapping.cpp")
create_single_source_cgal_program("test_pmp_non_conforming_snapping.cpp")
create_single_source_cgal_program("remove_degeneracies_test.cpp")
create_single_source_cgal_program("test_pmp_repair_degeneracies.cpp")
create_single_source_cgal_program("test_pmp_manifoldness.cpp")
create_single_source_cgal_program("test_mesh_smoothing.cpp")
create_single_source_cgal_program("test_remove_caps_needles.cpp")
# create_single_source_cgal_program("test_pmp_repair_self_intersections.cpp")
if( TBB_FOUND )
include(CGAL_target_use_TBB)
CGAL_target_use_TBB(test_pmp_distance)
CGAL_target_use_TBB(orient_polygon_soup_test)
CGAL_target_use_TBB(self_intersection_surface_mesh_test)
else()
message( STATUS "NOTICE: Intel TBB was not found. test_pmp_distance will use sequential code." )
@ -118,6 +121,9 @@ find_package(Ceres QUIET)
if(TARGET ceres)
target_compile_definitions( test_mesh_smoothing PRIVATE CGAL_PMP_USE_CERES_SOLVER )
target_link_libraries( test_mesh_smoothing PRIVATE ceres )
# target_compile_definitions( test_pmp_repair_self_intersections PRIVATE CGAL_PMP_USE_CERES_SOLVER )
# target_link_libraries( test_pmp_repair_self_intersections PRIVATE ceres )
endif(TARGET ceres)
if(BUILD_TESTING)

View File

@ -4,7 +4,9 @@
#include <CGAL/Polyhedron_3.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/orientation.h>
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup.h>
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup_extension.h>
#include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h>
#include <CGAL/algorithm.h>
@ -134,9 +136,70 @@ int test_orient(const bool save_oriented) {
return 0;
}
template <class K, class Tag>
int test_pipeline()
{
typedef typename K::Point_3 Point_3;
typedef CGAL::Polyhedron_3<K> Polyhedron;
std::vector<Point_3> points;
std::vector< std::vector<std::size_t> > polygons;
Polyhedron ref1;
shuffle_off("data/elephant.off", "elephant-shuffled.off");
std::ifstream input("elephant-shuffled.off");
if ( !input || !read_soup<K>(input, points, polygons)){
std::cerr << "Error: can not shuffled file.\n";
return 1;
}
input.close();
input.open("data/elephant.off");
if ( !input || !(input >> ref1)){
std::cerr << "Error: can not read reference file.\n";
return 1;
}
input.close();
CGAL::Polygon_mesh_processing::orient_triangle_soup_with_reference_triangle_mesh<Tag>(ref1, points, polygons);
CGAL::Polygon_mesh_processing::duplicate_non_manifold_edges_in_polygon_soup(points, polygons);
Polyhedron poly;
CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(
points, polygons, poly);
typedef typename boost::property_map<Polyhedron, CGAL::dynamic_face_property_t<std::size_t> >::type Fccmap;
Fccmap fim = get(CGAL::dynamic_face_property_t<std::size_t>(), poly);
std::size_t id =0;
for(auto f : faces(poly))
{
put(fim, f, id++);
}
CGAL::Polygon_mesh_processing::
merge_reversible_connected_components(poly,
CGAL::parameters::face_index_map(fim));
Fccmap fccmap = get(CGAL::dynamic_face_property_t<std::size_t>(), poly);
assert(CGAL::Polygon_mesh_processing::
connected_components(poly, fccmap,
CGAL::parameters::face_index_map(fim)) == 1);
return 0;
}
int main()
{
assert(test_orient<Epic>(false) == 0);
assert(test_orient<Epec>(false) == 0);
int res = test_pipeline<Epic, CGAL::Sequential_tag>();
assert(res == 0);
res = test_pipeline<Epec, CGAL::Sequential_tag>();
assert(res == 0);
#if defined(CGAL_LINKED_WITH_TBB)
res = test_pipeline<Epic, CGAL::Parallel_tag>();
assert(res == 0);
//res = test_pipeline<Epec, CGAL::Parallel_tag>();
//assert(res == 0);
#endif
return 0;
}

View File

@ -528,9 +528,16 @@ void test_split()
std::vector<TriangleMesh> meshes;
PMP::split(tm1, tm2);
//try with np
typedef typename boost::graph_traits<TriangleMesh>::faces_size_type faces_size_type;
typename boost::template property_map<
TriangleMesh, CGAL::dynamic_face_property_t<faces_size_type> >::type
pidmap = get(CGAL::dynamic_face_property_t<faces_size_type>(), tm1);
CGAL::Polygon_mesh_processing::connected_components(
tm1, pidmap, CGAL::parameters::all_default());
PMP::split_connected_components(tm1,
meshes,
params::all_default());
params::face_patch_map(pidmap));
CGAL_assertion(meshes.size() == 5);
//if the order is not deterministc, put the num_vertices in a list and check
@ -540,6 +547,7 @@ void test_split()
CGAL_assertion(num_vertices(meshes[2]) == 142);
CGAL_assertion(num_vertices(meshes[3]) == 83);
CGAL_assertion(num_vertices(meshes[4]) == 104);
CGAL_assertion(tm1.is_valid());
CGAL::clear(tm1);
CGAL::clear(tm2);
@ -580,14 +588,59 @@ void test_split()
//if the list does contain all those numbers.
CGAL_assertion(num_vertices(meshes[0]) == 588);
CGAL_assertion(num_vertices(meshes[1]) == 50);
CGAL_assertion(tm1.is_valid());
CGAL::clear(tm1);
CGAL::clear(tm2);
meshes.clear();
}
template <class TriangleMesh>
void test_isocuboid()
{
TriangleMesh tm;
//closed intersection curves
std::ifstream input("data-coref/elephant.off");
input >> tm;
if(!input)
{
std::cerr<<"File not found. Aborting."<<std::endl;
CGAL_assertion(false);
return ;
}
input.close();
std::vector<TriangleMesh> meshes;
K::Iso_cuboid_3 splitter(K::Point_3(-0.3, -0.45, -0.25),
K::Point_3( 0.3, 0.45, 0.25));
PMP::split(tm, splitter);
PMP::split_connected_components(tm,
meshes);
CGAL_assertion(meshes.size() == 10);
//if the order is not deterministc, put the num_vertices in a list and check
//if the list does contain all those numbers.
CGAL_assertion(num_vertices(meshes[0]) == 2657);
CGAL_assertion(num_vertices(meshes[1]) == 131 );
CGAL_assertion(num_vertices(meshes[2]) == 32 );
CGAL_assertion(num_vertices(meshes[3]) == 123 );
CGAL_assertion(num_vertices(meshes[4]) == 220 );
CGAL_assertion(num_vertices(meshes[5]) == 107 );
CGAL_assertion(num_vertices(meshes[6]) == 121 );
CGAL_assertion(num_vertices(meshes[7]) == 56 );
CGAL_assertion(num_vertices(meshes[8]) == 49 );
CGAL_assertion(num_vertices(meshes[9]) == 13 );
CGAL_assertion(tm.is_valid());
CGAL::clear(tm);
meshes.clear();
}
int main()
{
std::cout << "Surface Mesh" << std::endl;
test<Surface_mesh>();
@ -597,6 +650,9 @@ int main()
std::cout << "running test_split with Surface_mesh\n";
test_split<Surface_mesh>();
std::cout << "running test_iso_cuboid with Surface_mesh\n";
test_isocuboid<Surface_mesh>();
std::cout << "running test_split_plane with Surface_mesh\n";
test_split_plane<Surface_mesh>();
@ -606,6 +662,8 @@ int main()
std::cout << "running test_split_plane with Polyhedron\n";
test_split_plane<Polyhedron>();
std::cout << "running test_iso_cuboid with Polyhedron\n";
test_isocuboid<Polyhedron>();
std::cout << "Done!" << std::endl;
return EXIT_SUCCESS;

View File

@ -1,6 +1,7 @@
#include <CGAL/Surface_mesh.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Real_timer.h>
#include <CGAL/IO/OFF_reader.h>
#include <CGAL/boost/graph/property_maps.h>
@ -258,6 +259,11 @@ void general_tests(const TriangleMesh& m1,
std::cout << "Max distance to triangle mesh (sequential) "
<< PMP::max_distance_to_triangle_mesh<CGAL::Sequential_tag>(points,m1)
<< "\n";
std::vector<typename GeomTraits::Point_3> samples;
PMP::sample_triangle_mesh(m1, std::back_inserter(samples));
std::cout << samples.size()<<" points sampled on mesh."<<std::endl;
}
void test_concept()
@ -267,16 +273,21 @@ void test_concept()
general_tests<CK>(m1,m2);
}
int main(int, char** argv)
int main(int argc, char** argv)
{
if(argc != 3)
{
std::cerr << "Missing input meshes" << std::endl;
return EXIT_FAILURE;
}
Mesh m1,m2;
std::ifstream input(argv[1]);
input >> m1;
input.close();
input.open(argv[2]);
input >> m2;
input.close();
std::cout << "First mesh has " << num_faces(m1) << " faces\n";
std::cout << "Second mesh has " << num_faces(m2) << " faces\n";
@ -304,5 +315,16 @@ int main(int, char** argv)
test_concept();
std::vector<std::vector<std::size_t> > faces;
std::vector<K::Point_3> points;
input.open(argv[1]);
CGAL::read_OFF(input, points, faces);
input.close();
std::vector<K::Point_3> samples;
PMP::sample_triangle_soup(points, faces, std::back_inserter(samples));
std::cout<<samples.size()<<" points sampled on soup."<<std::endl;
return 0;
}

View File

@ -0,0 +1,132 @@
// #define CGAL_PMP_SMOOTHING_DEBUG
#define CGAL_PMP_COMPUTE_NORMAL_DEBUG
#define CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG
#define CGAL_PMP_REMOVE_SELF_INTERSECTION_OUTPUT
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/repair.h>
#include <CGAL/Polygon_mesh_processing/self_intersections.h>
#include <iostream>
#include <fstream>
#include <unordered_map>
namespace PMP = ::CGAL::Polygon_mesh_processing;
namespace CP = ::CGAL::parameters;
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Surface_mesh<K::Point_3> Surface_mesh;
typedef boost::graph_traits<Surface_mesh>::edge_descriptor edge_descriptor;
typedef boost::graph_traits<Surface_mesh>::face_descriptor face_descriptor;
void fix_self_intersections(const char* mesh_filename,
const char* mesh_selection_filename = nullptr)
{
std::cout << std::endl << "---------------" << std::endl;
std::cout << "Test " << mesh_filename << std::endl;
if(mesh_selection_filename)
std::cout << "With selection " << mesh_selection_filename << std::endl;
std::ifstream input(mesh_filename);
Surface_mesh mesh;
if(!input || !(input >> mesh) || mesh.is_empty())
{
std::cerr << "Error: " << mesh_filename << " is not a valid off file.\n";
std::exit(1);
}
std::list<face_descriptor> selected_faces;
if(mesh_selection_filename)
{
std::ifstream selection_input(mesh_selection_filename);
std::string line;
// skip the first line (faces are on the second line)
if(!selection_input || !std::getline(selection_input, line) || !std::getline(selection_input, line))
{
std::cerr << "Error: could not read selection: " << mesh_selection_filename << std::endl;
std::exit(1);
}
std::istringstream face_line(line);
std::size_t face_id;
while(face_line >> face_id)
selected_faces.push_back(*(faces(mesh).begin() + face_id));
std::cout << selected_faces.size() << " faces selected" << std::endl;
PMP::experimental::remove_self_intersections(selected_faces, mesh);
}
else
{
PMP::experimental::remove_self_intersections(mesh);
}
std::ofstream out("post_repair.off");
out.precision(17);
out << mesh;
out.close();
assert(CGAL::is_valid_polygon_mesh(mesh));
assert(!PMP::does_self_intersect(mesh));
}
void fix_local_self_intersections(const char* fname)
{
std::cout << std::endl << "-----LOCAL------" << std::endl;
std::cout << "Test " << fname << std::endl;
std::ifstream input(fname);
Surface_mesh mesh;
if(!input || !(input >> mesh) || mesh.is_empty())
{
std::cerr << "Error: " << fname << " is not a valid off file.\n";
std::exit(1);
}
PMP::experimental::remove_self_intersections(mesh, CP::apply_per_connected_component(true));
std::ofstream out("post_local_repair.off");
out.precision(17);
out << mesh;
out.close();
assert(CGAL::is_valid_polygon_mesh(mesh));
assert(!PMP::does_self_intersect(mesh));
}
int main(int, char**)
{
std::cout.precision(17);
std::cerr.precision(17);
#if 0
fix_local_self_intersections("data_repair/brain.off");
fix_self_intersections("data_repair/brain.off")
fix_self_intersections("data_repair/flute.off");
fix_self_intersections("data_repair/dinosaur.off");
fix_self_intersections("data_repair/hemispheres.off");
// selection is adjacent to a self-intersection but does not contain any intersection
fix_self_intersections("data_repair/brain.off", "data_repair/brain-complete.selection.txt");
// selection covers nicely a self-intersection
fix_self_intersections("data_repair/brain.off", "data_repair/brain-adjacent.selection.txt");
// selection contains part of a self intersection
fix_self_intersections("data_repair/brain.off", "data_repair/brain-partial.selection.txt");
// selection contains disjoint parts of a self intersection
fix_self_intersections("data_repair/brain.off", "data_repair/brain-disjoint.selection.txt");
// Remove only self-intersections within a single hemisphere
fix_self_intersections("data_repair/hemispheres.off");
fix_self_intersections("data_repair/hemispheres.off", "data_repair/hemispheres-half.selection.txt");
#endif
return 0;
}

View File

@ -3,7 +3,7 @@
#include <CGAL/Polygon_mesh_processing/self_intersections.h>
#include <fstream>
#include <CGAL/Polygon_mesh_processing/internal/remove_degeneracies.h>
#include <CGAL/Polygon_mesh_processing/repair_degeneracies.h>
#include <iostream>
#include <vector>

View File

@ -1,4 +1,4 @@
#define CGAL_PMP_SMOOTHING_VERBOSE
#define CGAL_PMP_SMOOTHING_DEBUG
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
@ -31,7 +31,7 @@ bool equal_doubles(double d1, double d2, double e)
template <typename Mesh>
void test_implicit_constrained_devil(Mesh mesh)
{
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cout << "-- test_implicit_constrained_devil --" << std::endl;
#endif
@ -69,7 +69,7 @@ void test_implicit_constrained_devil(Mesh mesh)
++i;
}
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::ofstream out("output_implicit_constrained_devil.off");
out << mesh;
out.close();
@ -79,7 +79,7 @@ void test_implicit_constrained_devil(Mesh mesh)
template <typename Mesh>
void test_implicit_constrained_elephant(Mesh mesh)
{
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cout << "-- test_implicit_constrained_elephant --" << std::endl;
#endif
@ -117,7 +117,7 @@ void test_implicit_constrained_elephant(Mesh mesh)
++i;
}
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::ofstream out("output_implicit_constrained_elephant.off");
out << mesh;
out.close();
@ -127,14 +127,14 @@ void test_implicit_constrained_elephant(Mesh mesh)
template <typename Mesh>
void test_curvature_flow_time_step(Mesh mesh)
{
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cout << "-- test_curvature_flow_time_step --" << std::endl;
#endif
const double time_step = 1e-15;
PMP::smooth_shape(mesh, time_step);
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::ofstream out("output_devil_time_step.off");
out << mesh;
out.close();
@ -144,14 +144,14 @@ void test_curvature_flow_time_step(Mesh mesh)
template <typename Mesh>
void test_curvature_flow(Mesh mesh)
{
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::cout << "-- test_curvature_flow --" << std::endl;
#endif
const double time_step = 1.0;
PMP::smooth_shape(mesh, time_step);
#ifdef CGAL_PMP_SMOOTHING_VERBOSE
#ifdef CGAL_PMP_SMOOTHING_DEBUG
std::ofstream out("output_precision_elephant.off");
out << mesh;
out.close();

View File

@ -406,6 +406,11 @@ struct Update_vertex
typedef typename Tr2::Vertex V2;
typedef typename Tr2::Point Point;
V2 operator()(const V1&)
{
return V2();
}
bool operator()(const V1& v1, V2& v2)
{
v2.set_point(Point(v1.point()));
@ -418,7 +423,7 @@ struct Update_vertex
const Sp_index sp_index = boost::get<Sp_index>(index);
v2.set_index((std::max)(sp_index.first, sp_index.second));
}
break;
break;
default:// -1, 0, 1, 3
v2.set_index(boost::get<int>(v1.index()));
}
@ -427,7 +432,9 @@ struct Update_vertex
}; // end struct Update_vertex
struct Update_cell {
typedef Fake_mesh_domain::Surface_patch_index Sp_index;
template <typename C1, typename C2>
bool operator()(const C1& c1, C2& c2) {
c2.set_subdomain_index(c1.subdomain_index());
@ -442,7 +449,6 @@ struct Update_cell {
}
}; // end struct Update_cell
#include <CGAL/Triangulation_file_input.h>
template <typename Tr1, typename Tr2>
struct Update_vertex_from_CDT_3 {
@ -452,24 +458,28 @@ struct Update_vertex_from_CDT_3 {
typedef typename Tr2::Vertex V2;
typedef typename Tr2::Point Point;
bool operator()(const V1& v1, V2& v2)
V2 operator()(const V1&)
{
return V2();
}
void operator()(const V1& v1, V2& v2)
{
v2.set_point(Point(v1.point()));
v2.set_dimension(2);
v2.set_special(false);
return true;
}
}; // end struct Update_vertex
struct Update_cell_from_CDT_3 {
typedef Fake_mesh_domain::Surface_patch_index Sp_index;
template <typename C1, typename C2>
bool operator()(const C1& c1, C2& c2) {
template <typename C1,typename C2>
void operator()(const C1& c1, C2& c2) {
c2.set_subdomain_index(1);
for(int i = 0; i < 4; ++i) {
c2.set_surface_patch_index(i, c1.constrained_facet[i]);
}
return true;
}
}; // end struct Update_cell
@ -500,11 +510,10 @@ try_load_a_cdt_3(std::istream& is, C3t3& c3t3)
}
}
if(binary) CGAL::set_binary_mode(is);
if(CGAL::file_input<
if(c3t3.triangulation().file_input<
Fake_CDT_3,
C3t3::Triangulation,
Update_vertex_from_CDT_3<Fake_CDT_3, C3t3::Triangulation>,
Update_cell_from_CDT_3>(is, c3t3.triangulation()))
Update_cell_from_CDT_3>(is))
{
c3t3.rescan_after_load_of_triangulation();
std::cerr << "Try load a CDT_3... DONE";
@ -544,11 +553,10 @@ try_load_other_binary_format(std::istream& is, C3t3& c3t3)
}
if(binary) CGAL::set_binary_mode(is);
else CGAL::set_ascii_mode(is);
std::istream& f_is = CGAL::file_input<
std::istream& f_is = c3t3.triangulation().file_input<
Fake_c3t3::Triangulation,
C3t3::Triangulation,
Update_vertex<Fake_c3t3::Triangulation, C3t3::Triangulation>,
Update_cell>(is, c3t3.triangulation());
Update_cell>(is);
c3t3.rescan_after_load_of_triangulation();
return f_is.good();

View File

@ -15,7 +15,7 @@
#include <CGAL/Polygon_mesh_processing/repair.h>
#include <CGAL/Polygon_mesh_processing/internal/repair_extra.h>
#include <CGAL/Polygon_mesh_processing/corefinement.h>
#include <CGAL/Polygon_mesh_processing/internal/remove_degeneracies.h>
#include <CGAL/Polygon_mesh_processing/repair_degeneracies.h>
#include <CGAL/Polygon_mesh_processing/merge_border_vertices.h>
#include "ui_RemoveNeedlesDialog.h"
@ -220,7 +220,7 @@ void Polyhedron_demo_repair_polyhedron_plugin::on_actionRemoveSelfIntersections_
if (poly_item)
{
bool solved =
CGAL::Polygon_mesh_processing::remove_self_intersections(
CGAL::Polygon_mesh_processing::experimental::remove_self_intersections(
*poly_item->polyhedron());
if (!solved)
CGAL::Three::Three::information(tr("Some self-intersection could not be fixed"));

Some files were not shown because too many files have changed in this diff Show More