mirror of https://github.com/CGAL/cgal
Merge remote-tracking branch 'cgal/master'
This commit is contained in:
commit
d845d6ce8d
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
||||
|
|
|
|||
|
|
@ -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} \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()`
|
||||
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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" )
|
||||
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -652,4 +652,3 @@ bool is_selection_a_topological_disk(const FaceRange& face_selection,
|
|||
} //end of namespace CGAL
|
||||
|
||||
#endif //CGAL_BOOST_GRAPH_SELECTION_H
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -20,4 +20,3 @@ STL_Extension
|
|||
Solver_interface
|
||||
Spatial_searching
|
||||
Stream_support
|
||||
Surface_mesh_segmentation
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
@ -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 |
|
|
@ -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.},
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
@ -116,7 +148,6 @@ Release date: June 2020
|
|||
- 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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 >
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
data/kitten.xyz
|
||||
data/kitten.xyz 1
|
||||
data/kitten.xyz 2
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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.);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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()`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue