WIP to add Hausdorff distance to a mesh

This commit is contained in:
Sébastien Loriot 2016-07-18 15:02:06 +02:00
parent 853d89d953
commit 2577fd912e
4 changed files with 359 additions and 41 deletions

View File

@ -40,6 +40,12 @@
/// Functions to orient polygon soups and to stitch geometrically identical boundaries.
/// \ingroup PkgPolygonMeshProcessing
/// \defgroup PMP_distance_grp Combinatorial Repairing
/// Functions to compute the distance between meshes.
/// \ingroup PkgPolygonMeshProcessing
/*!
\addtogroup PkgPolygonMeshProcessing
@ -126,6 +132,10 @@ and provides a list of the parameters that are used in this package.
- \link measure_grp `CGAL::Polygon_mesh_processing::edge_length()` \endlink
- \link measure_grp `CGAL::Polygon_mesh_processing::face_border_length()` \endlink
## Distance Functions ##
- `CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance()`
- `CGAL::Polygon_mesh_processing::approximated_symmetric_Hausdorff_distance()`
## Miscellaneous ##
- `CGAL::Polygon_mesh_slicer`
- `CGAL::Side_of_triangle_mesh`

View File

@ -28,8 +28,18 @@
#include <CGAL/AABB_triangle_primitive.h>
#include <CGAL/utility.h>
#include <boost/foreach.hpp>
#include <CGAL/Polygon_mesh_processing/internal/named_function_params.h>
#include <CGAL/Polygon_mesh_processing/internal/named_params_helper.h>
#include <CGAL/spatial_sort.h>
#ifdef CGAL_LINKED_WITH_TBB
#include <tbb/parallel_for.h>
#include <tbb/blocked_range.h>
#include <CGAL/atomic.h>
#endif // CGAL_LINKED_WITH_TBB
/// \cond SKIP_IN_MANUAL
namespace CGAL{
namespace Polygon_mesh_processing {
@ -37,17 +47,17 @@ namespace internal{
template <class Kernel, class OutputIterator>
OutputIterator
triangle_grid_sampling(const typename Kernel::Point_3& p0,
const typename Kernel::Point_3& p1,
const typename Kernel::Point_3& p2,
double distance,
OutputIterator out)
triangle_grid_sampling( const typename Kernel::Point_3& p0,
const typename Kernel::Point_3& p1,
const typename Kernel::Point_3& p2,
double distance,
OutputIterator out)
{
const double d_p0p1 = CGAL::sqrt( CGAL::squared_distance(p0, p1) );
const double d_p0p2 = CGAL::sqrt( CGAL::squared_distance(p0, p2) );
const double n = (std::max)(std::ceil( d_p0p1 / distance ),
std::ceil( d_p0p2 / distance ));
std::ceil( d_p0p2 / distance ));
for (double i=1; i<n; ++i)
for (double j=1; j<n-i; ++j)
@ -116,7 +126,88 @@ sample_triangles(const TriangleRange& triangles, double distance, OutputIterator
return out;
}
template <class Kernel, class TriangleRange1, class TriangleRange2>
#ifdef CGAL_LINKED_WITH_TBB
template <class AABB_tree, class Point_3>
struct Distance_computation{
const AABB_tree& tree;
const std::vector<Point_3>& sample_points;
cpp11::atomic<double>* distance;
Distance_computation(const AABB_tree& tree, const std::vector<Point_3>& sample_points, cpp11::atomic<double>* d)
: tree(tree)
, sample_points(sample_points)
, distance(d)
{}
void
operator()(const tbb::blocked_range<std::size_t>& range) const
{
double hdist = 0;
Point_3 hint = sample_points.front();
for( std::size_t i = range.begin(); i != range.end(); ++i)
{
hint = tree.closest_point(sample_points[i], hint);
double d = CGAL::sqrt( squared_distance(hint,sample_points[i]) );
if (d>hdist) hdist=d;
}
if (hdist > distance->load(cpp11::memory_order_acquire))
distance->store(hdist, cpp11::memory_order_release);
}
};
#endif
/// \todo update me to work with a triangulated surface mesh
template <class Concurrency_tag, class Kernel, class TriangleRange>
double approximated_Hausdorff_distance(
std::vector<typename Kernel::Point_3>& sample_points,
const TriangleRange& triangles)
{
#ifdef CGAL_HAUSDORFF_DEBUG
std::cout << "Nb sample points " << sample_points.size() << "\n";
#endif
spatial_sort(sample_points.begin(), sample_points.end());
/// \todo shall we use Simple_cartesian for the distance computation?
/// check if this can be problematic to have non-exact predicates.
typedef typename TriangleRange::const_iterator Triangle_iterator;
typedef AABB_triangle_primitive<Kernel, Triangle_iterator> Primitive;
typedef AABB_traits<Kernel, Primitive> Traits;
typedef AABB_tree< Traits > Tree;
Tree tree(triangles.begin(), triangles.end());
tree.accelerate_distance_queries();
tree.build();
#ifndef CGAL_LINKED_WITH_TBB
CGAL_static_assertion_msg (!(boost::is_convertible<Concurrency_tag, Parallel_tag>::value),
"Parallel_tag is enabled but TBB is unavailable.");
#else
if (boost::is_convertible<Concurrency_tag,Parallel_tag>::value)
{
cpp11::atomic<double> distance(0);
Distance_computation<Tree, typename Kernel::Point_3> f(tree, sample_points, &distance);
tbb::parallel_for(tbb::blocked_range<std::size_t>(0, sample_points.size()), f);
return distance;
}
else
#endif
{
double hdist = 0;
typename Traits::Point_3 hint = sample_points.front();
BOOST_FOREACH(const typename Kernel::Point_3& pt, sample_points)
{
hint = tree.closest_point(pt, hint);
double d = CGAL::sqrt( squared_distance(hint,pt) );
if (d>hdist) hdist=d;
}
return hdist;
}
}
/// \todo remove me (except if you find an easy way avoid the ambiguity with TriangleMesh overload
template <class Concurrency_tag, class Kernel, class TriangleRange1, class TriangleRange2>
double approximated_Hausdorff_distance(
const TriangleRange1& triangles_1,
const TriangleRange2& triangles_2,
@ -124,32 +215,15 @@ double approximated_Hausdorff_distance(
)
{
std::vector<typename Kernel::Point_3> sample_points;
sample_triangles<Kernel>( triangles_1,
targeted_precision,
std::back_inserter(sample_points) );
// std::ofstream out("/tmp/samples.xyz");
// std::copy(sample_points.begin(), sample_points.end(), std::ostream_iterator<typename Kernel::Point_3>(out,"\n"));
typedef typename TriangleRange2::const_iterator Triangle_iterator;
typedef AABB_triangle_primitive<Kernel, Triangle_iterator> Primitive;
typedef AABB_traits<Kernel, Primitive> Traits;
typedef AABB_tree< Traits > Tree;
Tree tree(triangles_2.begin(), triangles_2.end());
tree.accelerate_distance_queries();
double hdist = 0;
BOOST_FOREACH(const typename Kernel::Point_3& pt, sample_points)
{
double d = CGAL::sqrt( tree.squared_distance(pt) );
// if ( d > 1e-1 ) std::cout << pt << "\n";
if (d>hdist) hdist=d;
}
return hdist;
return approximated_Hausdorff_distance<Concurrency_tag, Kernel>(sample_points, triangles_2);
}
template <class Kernel, class TriangleRange1, class TriangleRange2>
/// \todo remove me (except if you find an easy way avoid the ambiguity with TriangleMesh overload
template <class Concurrency_tag, class Kernel, class TriangleRange1, class TriangleRange2>
double approximated_symmetric_Hausdorff_distance(
const TriangleRange1& triangles_1,
const TriangleRange2& triangles_2,
@ -157,26 +231,189 @@ double approximated_symmetric_Hausdorff_distance(
)
{
return (std::max)(
approximated_Hausdorff_distance<Kernel>(triangles_1, triangles_2, targeted_precision),
approximated_Hausdorff_distance<Kernel>(triangles_2, triangles_1, targeted_precision)
approximated_Hausdorff_distance<Concurrency_tag, Kernel>(triangles_1, triangles_2, targeted_precision),
approximated_Hausdorff_distance<Concurrency_tag, Kernel>(triangles_2, triangles_1, targeted_precision)
);
}
///\todo add approximated_Hausdorff_distance(FaceGraph, FaceGraph)
///\todo add approximated_Hausdorff_distance(TriangleRange, FaceGraph)
///\todo add approximated_Hausdorff_distance(FaceGraph, TriangleRange)
///\todo add approximated_Hausdorff_distance(PointRange, FaceGraph)
///\todo add approximated_Hausdorff_distance(PointRange, TriangleRange)
///\todo add barycentric_triangle_sampling(Triangle, PointPerAreaUnit)
///\todo add random_triangle_sampling(Triangle, PointPerAreaUnit)
/// \todo maybe we should use a sampling using epec to avoid being too far from the sampled triangle
/// \todo add a function `sample_triangle_mesh()` (or better name) that provide different sampling methods:
/// - random uniform ( points/area unit)
/// - grid (warning mininum 1 pt / triangle) (parameter = distance between points?)
/// - monte carlo? (random points in each triangle proportional to the face area with 1 pt mini per triangle)
/// The goal is to test different strategies and put the better one in `approximated_Hausdorff_distance()`
/// for particular cases one can still use a specific sampling method together with `max_distance_to_triangle_mesh()`
/// \todo add a plugin in the demo to display the distance betweem 2 meshes as a texture (if not complicated)
// documented functions
/**
* \ingroup PMP_distance_grp
* computes the approximated Hausdorff distance of `tm1` from `tm2` by
* by generating a uniform random point sampling on `tm1`, and by then
* returning the distance of the furthest point from `tm2`.
*
* A parallel version is provided and requires the executable to be
* linked against the <a href="http://www.threadingbuildingblocks.org">Intel TBB library</a>.
* To control the number of threads used, the user may use the `tbb::task_scheduler_init` class.
* See the <a href="http://www.threadingbuildingblocks.org/documentation">TBB documentation</a>
* for more details.
*
* @tparam Concurrency_tag enables sequential versus parallel algorithm.
* Possible values are `Sequential_tag`
* and `Parallel_tag`.
* @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map
* for `CGAL::vertex_point_t`
* @tparam NamedParameters1 a sequence of \ref namedparameters for `tm1`
* @tparam NamedParameters2 a sequence of \ref namedparameters for `tm2`
*
* @param tm1 the triangle mesh that will be sampled
* @param tm2 the triangle mesh to compute the distance to
* @param precision TODO: either the number of points per squared area unit or something else depending on the result of the testing
* @param np1 optional sequence of \ref namedparameters for `tm1` among the ones listed below
* @param np2 optional sequence of \ref namedparameters for `tm2` among the ones listed below
*
* \cgalNamedParamsBegin
* \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh` \cgalParamEnd
* \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd
* \cgalNamedParamsEnd
* \todo When using TBB, runtime is slower. The chunk size should probably be tuned. see PMP/test/test_pmp_distance.cpp
*/
template< class Concurrency_tag,
class TriangleMesh,
class NamedParameters1,
class NamedParameters2>
double approximated_Hausdorff_distance( const TriangleMesh& tm1,
const TriangleMesh& tm2,
double precision,
const NamedParameters1& np1,
const NamedParameters2& np2)
{
typedef typename GetGeomTraits<TriangleMesh,
NamedParameters1>::type Geom_traits;
/// \todo implement the missing function
return approximated_Hausdorff_distance<Concurrency_tag, Geom_traits>(
tm1, tm2,
precision,
choose_const_pmap(get_param(np1, boost::vertex_point),
tm1,
vertex_point),
choose_const_pmap(get_param(np2, boost::vertex_point),
tm2,
vertex_point)
);
}
/**
* \ingroup PMP_distance_grp
* computes the approximated symmetric Hausdorff distance between `tm1` and `tm2`.
* It returns the maximum of `approximated_Hausdorff_distance(tm1,tm2)` and
* `approximated_Hausdorff_distance(tm1,tm2)`.
*
* \copydetails CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance()
*/
template< class Concurrency_tag,
class TriangleMesh,
class NamedParameters1,
class NamedParameters2>
double approximated_symmetric_Hausdorff_distance(
const TriangleMesh& tm1,
const TriangleMesh& tm2,
double precision,
const NamedParameters1& np1,
const NamedParameters2& np2)
{
return (std::max)(
approximated_Hausdorff_distance<Concurrency_tag>(tm1,tm2,precision,np1,np2),
approximated_Hausdorff_distance<Concurrency_tag>(tm2,tm1,precision,np2,np1)
);
}
/// \todo document and implement me
template< class Concurrency_tag,
class TriangleMesh,
class PointRange,
class NamedParameters>
double max_distance_to_triangle_mesh(const PointRange& points,
const TriangleMesh& tm,
const NamedParameters& np)
{
return 0;
}
/// \todo document and implement me
/// see with @sgiraudot for the implementation
/// that should be put in a seperate header file
/// with copyright INRIA
/// Maybe find a better name too
template< class Concurrency_tag,
class TriangleMesh,
class PointRange,
class NamedParameters>
double max_distance_to_point_set(const TriangleMesh& tm,
const PointRange& points,
const NamedParameters& np)
{
return 0;
}
// convenience functions with default parameters
template< class Concurrency_tag,
class TriangleMesh,
class NamedParameters1>
double approximated_Hausdorff_distance( const TriangleMesh& tm1,
const TriangleMesh& tm2,
double precision,
const NamedParameters1& np1)
{
return approximated_Hausdorff_distance<Concurrency_tag>(
tm1, tm2, precision, np1, parameters::all_default());
}
template< class Concurrency_tag,
class TriangleMesh,
class NamedParameters1>
double approximated_Hausdorff_distance( const TriangleMesh& tm1,
const TriangleMesh& tm2,
double precision)
{
return approximated_Hausdorff_distance<Concurrency_tag>(
tm1, tm2, precision,
parameters::all_default(),
parameters::all_default());
}
template< class Concurrency_tag,
class TriangleMesh,
class NamedParameters1>
double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1,
const TriangleMesh& tm2,
double precision,
const NamedParameters1& np1)
{
return approximated_symmetric_Hausdorff_distance<Concurrency_tag>(
tm1, tm2, precision, np1, parameters::all_default());
}
template< class Concurrency_tag,
class TriangleMesh,
class NamedParameters1>
double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1,
const TriangleMesh& tm2,
double precision)
{
return approximated_symmetric_Hausdorff_distance<Concurrency_tag>(
tm1, tm2, precision,
parameters::all_default(),
parameters::all_default());
}
} } // end of namespace CGAL::Polygon_mesh_processing
/// \endcond
#endif //CGAL_POLYGON_MESH_PROCESSING_DISTANCE_H

View File

@ -51,6 +51,14 @@ include_directories( BEFORE ../../include )
find_package(Eigen3 3.1.0) #(requires 3.1.0 or greater)
find_package( TBB )
if( TBB_FOUND )
include( ${TBB_USE_FILE} )
list( APPEND CGAL_3RD_PARTY_LIBRARIES ${TBB_LIBRARIES} )
else()
message( STATUS "NOTICE: Intel TBB was not found. test_pmp_distance will use sequential code." )
endif()
if (EIGEN3_FOUND)
# Executables that require Eigen 3.1
include( ${EIGEN3_USE_FILE} )
@ -77,6 +85,7 @@ if (EIGEN3_FOUND)
create_single_source_cgal_program("measures_test.cpp")
create_single_source_cgal_program("triangulate_faces_test.cpp")
create_single_source_cgal_program("test_pmp_remove_border_edge.cpp")
create_single_source_cgal_program("test_pmp_distance.cpp")
if(NOT (${EIGEN3_VERSION} VERSION_LESS 3.2.0))
create_single_source_cgal_program("fairing_test.cpp")

View File

@ -0,0 +1,62 @@
#include <CGAL/Surface_mesh.h>
#include <CGAL/boost/graph/graph_traits_Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/distance.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Timer.h>
#include <CGAL/boost/graph/property_maps.h>
#include <fstream>
#include <ostream>
// typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Simple_cartesian<double> K;
typedef CGAL::Surface_mesh<K::Point_3> Mesh;
int main(int argc, char** argv)
{
Mesh m1,m2;
std::ifstream input(argv[1]);
input >> m1;
input.close();
input.open(argv[2]);
input >> m2;
std::cout << "First mesh has " << num_faces(m1) << " faces\n";
std::cout << "Second mesh has " << num_faces(m2) << " faces\n";
std::vector<K::Triangle_3> t1;
t1.reserve(num_faces(m1));
CGAL::Triangle_from_face_descriptor_map<Mesh> map1(&m1);
BOOST_FOREACH(Mesh::Face_index f, faces(m1))
t1.push_back(get(map1,f));
std::vector<K::Triangle_3> t2;
t2.reserve(num_faces(m2));
CGAL::Triangle_from_face_descriptor_map<Mesh> map2(&m2);
BOOST_FOREACH(Mesh::Face_index f, faces(m2))
t2.push_back(get(map2,f));
CGAL::Timer time;
time.start();
std::cout << "Distance between meshes (parallel)"
<< CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance<CGAL::Parallel_tag, K>(t1,t2,0.001)
<< "\n";
time.stop();
std::cout << "done in " << time.time() << "s.\n";
time.reset();
time.start();
std::cout << "Distance between meshes (sequential)"
<< CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance<CGAL::Sequential_tag, K>(t1,t2,0.001)
<< "\n";
time.stop();
std::cout << "done in " << time.time() << "s.\n";
}