1 // Copyright (c) 2014  GeometryFactory Sarl (France).
2 // All rights reserved.
3 //
4 // This file is part of CGAL (www.cgal.org).
5 //
6 // $URL: https://github.com/CGAL/cgal/blob/v5.3/Surface_mesh_segmentation/include/CGAL/internal/Surface_mesh_segmentation/SDF_calculation.h $
7 // $Id: SDF_calculation.h e893ac1 2020-08-18T10:06:51+02:00 Sébastien Loriot
8 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
9 //
10 // Author(s)     : Ilker O. Yaz
11 
12 
13 #ifndef CGAL_SURFACE_MESH_SEGMENTATION_SDF_CALCULATION_H
14 #define CGAL_SURFACE_MESH_SEGMENTATION_SDF_CALCULATION_H
15 
16 #include <CGAL/license/Surface_mesh_segmentation.h>
17 
18 
19 #include <CGAL/AABB_tree.h>
20 #include <CGAL/AABB_face_graph_triangle_primitive.h>
21 #include <CGAL/internal/Surface_mesh_segmentation/AABB_traversal_traits.h>
22 #include <CGAL/internal/Surface_mesh_segmentation/AABB_traits.h>
23 #include <CGAL/internal/Surface_mesh_segmentation/Disk_samplers.h>
24 #include <CGAL/constructions/kernel_ftC3.h>
25 #include <vector>
26 #include <algorithm>
27 
28 #include <boost/tuple/tuple.hpp>
29 #include <boost/optional.hpp>
30 
31 #define CGAL_NUMBER_OF_MAD 1.5
32 
33 namespace CGAL
34 {
35 /// @cond CGAL_DOCUMENT_INTERNAL
36 namespace internal
37 {
38 
39 template<class ID>
40 struct SkipPrimitiveFunctor {
41 
operatorSkipPrimitiveFunctor42   bool operator()(const ID& skip_me) const {
43     return skip_me == skip;
44   }
45 
SkipPrimitiveFunctorSkipPrimitiveFunctor46   SkipPrimitiveFunctor(const ID& skip) : skip(skip) { }
47 
48   ID skip;
49 };
50 
51 template<class ID>
52 struct FirstIntersectionVisitor {
53 
operatorFirstIntersectionVisitor54   void operator()(const ID& /*closest*/, double /*distance*/) const {
55   }
56 };
57 
58 /**
59  * @brief Responsible for calculating Shape Diameter Function over surface of the mesh.
60  *
61  * @tparam Polyhedron a CGAL polyhedron
62  * @tparam GeomTraits a model of SegmentationGeomTraits
63  */
64 template <
65       class Polyhedron,
66       class VertexPointPmap,
67       class GeomTraits = typename Polyhedron::Traits,
68       bool fast_bbox_intersection = true
69       >
70 class SDF_calculation
71 {
72 //type definitions
73 private:
74 
75   typedef typename GeomTraits::Vector_3   Vector;
76   typedef typename GeomTraits::Point_3    Point;
77   typedef typename GeomTraits::Ray_3      Ray;
78   typedef typename GeomTraits::Plane_3    Plane;
79   typedef typename GeomTraits::Segment_3  Segment;
80   typedef typename GeomTraits::FT         FT;
81 
82   typedef typename boost::graph_traits<Polyhedron>::face_iterator face_iterator;
83   typedef typename boost::graph_traits<Polyhedron>::face_descriptor   face_handle;
84   typedef typename boost::graph_traits<Polyhedron>::halfedge_descriptor   halfedge_handle;
85 
86   typedef AABB_face_graph_triangle_primitive<Polyhedron, VertexPointPmap> Primitive;
87   typedef AABB_traits_SDF<GeomTraits, Primitive, fast_bbox_intersection>
88   AABB_traits_internal;
89   typedef typename CGAL::AABB_tree<AABB_traits_internal>                 Tree;
90   typedef typename Tree::Primitive_id Primitive_id;
91 
92   // Sampled points from disk, t1 = coordinate-x, t2 = coordinate-y, t3 = weight.
93   typedef boost::tuple<double, double, double> Disk_sample;
94   typedef std::vector<Disk_sample>             Disk_samples_list;
95 
96   // DiskSampling class responsible for the sampling points in a disk. It is used for generating rays in the cones. For different example see Disk_samplers.h
97   typedef Vogel_disk_sampling<boost::tuple<double, double, double> >
98   Default_sampler;
99 
100 // member variables
101 private:
102   static AABB_traits_internal
create_traits(const Polyhedron & mesh,VertexPointPmap vertex_point_map)103   create_traits(const Polyhedron& mesh,
104                 VertexPointPmap vertex_point_map )
105   {
106     AABB_traits_internal traits;
107     traits.set_shared_data(mesh, vertex_point_map);
108     return traits;
109   }
110   GeomTraits traits;
111   const Polyhedron& mesh;
112   VertexPointPmap vertex_point_map;
113 
114   typename GeomTraits::Angle_3                         angle_functor;
115   typename GeomTraits::Construct_scaled_vector_3       scale_functor;
116   typename GeomTraits::Construct_sum_of_vectors_3      sum_functor;
117   typename GeomTraits::Construct_normal_3              normal_functor;
118   typename GeomTraits::Construct_translated_point_3    translated_point_functor;
119   typename GeomTraits::Construct_centroid_3            centroid_functor;
120   typename GeomTraits::Collinear_3                     collinear_functor;
121 
122   Tree tree;
123 
124   double max_diagonal;
125   bool   use_diagonal;
126 public:
127   /**
128    * Construct AABB tree with a mesh. Ignores degenerated faces.
129    * @param mesh `CGAL Polyhedron` on which AABB tree constructed
130    * @param build_kd_tree requirement on internal kd-tree (it is only required if find_closest_with_AABB_distance is planned to use)
131    * @param use_diagonal if true: calculates diagonal of AABB tree and cast segments instead of rays using diagonal length
132    * @param traits trait object
133    */
134   SDF_calculation(const Polyhedron& mesh,
135                   VertexPointPmap vertex_point_map,
136                   bool build_kd_tree = false,
137                   bool use_diagonal = true, GeomTraits traits = GeomTraits())
138     :
traits(traits)139     traits(traits),
140     mesh(mesh),
141     vertex_point_map(vertex_point_map),
142     angle_functor(traits.angle_3_object()),
143     scale_functor(traits.construct_scaled_vector_3_object()),
144     sum_functor(traits.construct_sum_of_vectors_3_object()),
145     normal_functor(traits.construct_normal_3_object()),
146     translated_point_functor(traits.construct_translated_point_3_object()),
147     centroid_functor(traits.construct_centroid_3_object()),
148     collinear_functor(traits.collinear_3_object()),
149     tree(create_traits(mesh, vertex_point_map)),
150     use_diagonal(use_diagonal)
151   {
152     typedef typename boost::property_traits<VertexPointPmap>::reference Point_ref;
153     face_iterator it, end;
154     for(it = faces(mesh).begin(), end = faces(mesh).end(); it!=end; it++)
155     {
156         halfedge_handle h = halfedge(*it, mesh);
157         Point_ref a(get(vertex_point_map, target(h, mesh)));
158         h = next(h, mesh);
159         Point_ref b(get(vertex_point_map,target(h, mesh)));
160         h = next(h, mesh);
161         Point_ref c(get(vertex_point_map, target(h, mesh)));
162         bool test = collinear_functor(a,b,c);
163         if(!test)
164           tree.insert(Primitive(it, mesh, vertex_point_map));
165     }
166     if(!build_kd_tree) {
167       tree.do_not_accelerate_distance_queries();
168     }
169     tree.build();
170 
171     if(use_diagonal) {
172       CGAL::Bbox_3 bbox = tree.bbox();
173       max_diagonal =
174         std::sqrt(
175           CGAL::squared_distanceC3( bbox.xmin(), bbox.ymin(), bbox.zmin(), bbox.xmax(),
176                                     bbox.ymax(), bbox.zmax() )
177         );
178     }
179   }
180 
181   /**
182    * Calculates SDF values for each facet in a range, and stores them in @a sdf_values. Note that sdf values are neither smoothed nor normalized.
183    * @tparam FacetValueMap `WritablePropertyMap` with `boost::graph_traits<Polyhedron>::face_handle` as key and `double` as value type
184    * @tparam InputIterator Iterator over polyhedrons. Its value type is `pointer to polyhedron`.
185    * @param facet_begin range begin
186    * @param facet_end range past-the-end
187    * @param cone_angle opening angle for cone, expressed in radians
188    * @param number_of_rays number of rays picked from cone for each facet
189    * @param[out] sdf_values
190    */
191   template <class FacetValueMap, class InputIterator, class DiskSampling>
calculate_sdf_values(InputIterator facet_begin,InputIterator facet_end,double cone_angle,std::size_t number_of_rays,FacetValueMap sdf_values,DiskSampling disk_sampler)192   void calculate_sdf_values(
193     InputIterator facet_begin,
194     InputIterator facet_end,
195     double cone_angle,
196     std::size_t number_of_rays,
197     FacetValueMap sdf_values,
198     DiskSampling disk_sampler) const {
199     Disk_samples_list disk_samples;
200     disk_sampler(number_of_rays, std::back_inserter(disk_samples));
201 
202     for( ; facet_begin != facet_end; ++facet_begin) {
203       boost::optional<double> sdf_value = calculate_sdf_value_of_facet(*facet_begin,
204                                           cone_angle, true, disk_samples);
205 
206       if(sdf_value) {
207         put(sdf_values, *facet_begin, *sdf_value);
208       } else          {
209         put(sdf_values, *facet_begin, -1.0);
210       }
211     }
212   }
213 
214   /**
215    * Overload for default sampling parameter
216    */
217   template <class FacetValueMap, class InputIterator>
calculate_sdf_values(InputIterator facet_begin,InputIterator facet_end,double cone_angle,std::size_t number_of_rays,FacetValueMap sdf_values)218   void calculate_sdf_values(
219     InputIterator facet_begin,
220     InputIterator facet_end,
221     double cone_angle,
222     std::size_t number_of_rays,
223     FacetValueMap sdf_values) const {
224     calculate_sdf_values(facet_begin, facet_end, cone_angle, number_of_rays,
225                          sdf_values, Default_sampler());
226   }
227 
228   /**
229    * Cast rays inside code located around normal with apex at center.
230    * Any encountered primitives are tested with `skip`, and accepted if `skip` returns false.
231    * For each ray, closest encountered primitive send to `visitor`.
232    *
233    * \note: normal should have unit length
234    */
235   template<class SkipPrimitiveFunctor, class FirstIntersectionVisitor>
calculate_sdf_value_of_point(Point center,Vector normal,SkipPrimitiveFunctor skip,FirstIntersectionVisitor visitor,double cone_angle,std::size_t number_of_rays,bool accept_if_acute)236   boost::optional<double> calculate_sdf_value_of_point(
237     Point center,
238     Vector normal,
239     SkipPrimitiveFunctor skip,
240     FirstIntersectionVisitor visitor,
241     double cone_angle,
242     std::size_t number_of_rays,
243     bool accept_if_acute) const {
244     return calculate_sdf_value_of_point(center, normal, skip, visitor, cone_angle,
245                                         number_of_rays, accept_if_acute,
246                                         Default_sampler() );
247   }
248 
249   /**
250    * Overload for taking DiskSampling as template parameter
251    */
252   template<class SkipPrimitiveFunctor, class FirstIntersectionVisitor, class DiskSampling>
calculate_sdf_value_of_point(Point center,Vector normal,SkipPrimitiveFunctor skip,FirstIntersectionVisitor visitor,double cone_angle,std::size_t number_of_rays,bool accept_if_acute,DiskSampling disk_sampler)253   boost::optional<double> calculate_sdf_value_of_point(
254     Point center,
255     Vector normal,
256     SkipPrimitiveFunctor skip,
257     FirstIntersectionVisitor visitor,
258     double cone_angle,
259     std::size_t number_of_rays,
260     bool accept_if_acute,
261     DiskSampling disk_sampler) const {
262     Disk_samples_list disk_samples;
263     disk_sampler(number_of_rays, std::back_inserter(disk_samples));
264 
265     return calculate_sdf_value_of_point(center, normal, skip, visitor, cone_angle,
266                                         accept_if_acute, disk_samples);
267   }
268 
269   /**
270    * Overload for directly taking sampled points from disk as parameter
271    */
272   template<class SkipPrimitiveFunctor, class FirstIntersectionVisitor>
calculate_sdf_value_of_point(const Point & center,const Vector & normal,SkipPrimitiveFunctor skip,FirstIntersectionVisitor visitor,double cone_angle,bool accept_if_acute,const Disk_samples_list & disk_samples)273   boost::optional<double> calculate_sdf_value_of_point(
274     const Point& center,
275     const Vector& normal,
276     SkipPrimitiveFunctor skip,
277     FirstIntersectionVisitor visitor,
278     double cone_angle,
279     bool accept_if_acute,
280     const Disk_samples_list& disk_samples) const {
281     if(cone_angle < 0.0 || cone_angle > CGAL_PI) {
282       CGAL_warning_msg(false, "Cone angle is clamped between [0, CGAL_PI].");
283       cone_angle = (std::min)(CGAL_PI, (std::max)(0.0, cone_angle));
284     }
285 
286     Plane plane(center, normal);
287     Vector v1 = plane.base1(), v2 = plane.base2();
288     v1 = scale_functor(v1, FT(1.0 / CGAL::sqrt( to_double(v1.squared_length()) )));
289     v2 = scale_functor(v2, FT(1.0 / CGAL::sqrt( to_double(v2.squared_length()) )));
290 
291     std::vector<std::pair<double, double> > ray_distances;
292     ray_distances.reserve(disk_samples.size());
293 
294     const FT normal_multiplier( cos(cone_angle / 2.0) );
295     const FT disk_multiplier  ( sin(cone_angle / 2.0) );
296 
297     const Vector& scaled_normal = scale_functor(normal, normal_multiplier);
298 
299     for(Disk_samples_list::const_iterator sample_it = disk_samples.begin();
300         sample_it != disk_samples.end(); ++sample_it) {
301       bool is_intersected, intersection_is_acute;
302       double min_distance;
303       Primitive_id closest_id;
304 
305       Vector disk_vector = sum_functor(
306                              scale_functor(v1, FT(disk_multiplier * sample_it->get<0>())),
307                              scale_functor(v2, FT(disk_multiplier * sample_it->get<1>())) );
308       Vector ray_direction = sum_functor(scaled_normal, disk_vector);
309 
310       if(use_diagonal) {
311         FT max_distance( max_diagonal / std::sqrt(to_double(
312                            ray_direction.squared_length())));
313         const Vector scaled_direction = scale_functor(ray_direction, max_distance);
314         const Vector target_vector = sum_functor( Vector(Point(ORIGIN), center),
315                                      scaled_direction);
316         const Point  target_point = translated_point_functor(Point(ORIGIN),
317                                     target_vector);
318         Segment segment(center, target_point);
319 
320         if(traits.is_degenerate_3_object()(segment)) {
321           CGAL_warning_msg(false,
322                        "A degenerate segment is constructed. Most probable reason is using CGAL_PI as cone_angle parameter and also picking center of disk as a sample.");
323         }
324 
325         boost::tie(is_intersected, intersection_is_acute, min_distance, closest_id)
326           = cast_and_return_minimum(segment, skip, accept_if_acute);
327       } else {
328         Ray ray(center, ray_direction);
329 
330         if(traits.is_degenerate_3_object()(ray)) {
331           CGAL_warning_msg(false,
332                        "A degenerate ray is constructed. Most probable reason is using CGAL_PI as cone_angle parameter and also picking center of disk as a sample.");
333         }
334 
335         boost::tie(is_intersected, intersection_is_acute, min_distance, closest_id)
336           = ray_casting(ray, skip, accept_if_acute);
337       }
338 
339       if(!intersection_is_acute) {
340         continue;
341       }
342 
343       visitor(closest_id, min_distance);
344 
345       ray_distances.push_back(std::make_pair(min_distance, sample_it->get<2>()));
346     }
347 
348     if(ray_distances.empty()) {
349       return boost::none;
350     }
351 
352     return boost::optional<double>(remove_outliers_and_calculate_sdf_value(
353                                      ray_distances));
354   }
355 
356   /**
357    * Finds closest distance to center using `closest_point` query on AABB.
358    * @return squared distance
359    */
find_closest_with_AABB_distance(Point center)360   double find_closest_with_AABB_distance(Point center) const {
361     Point closest = tree.closest_point(center);
362     return (closest - center).squared_length();
363   }
364 private:
365   /**
366    * Calculates SDF value for parameter @a facet.
367    * @param facet
368    * @param tree AABB tree which is built from polyhedron
369    * @param samples sampled points from a unit-disk which are corresponds to rays picked from cone
370    * @return calculated SDF value
371    */
calculate_sdf_value_of_facet(face_handle facet,double cone_angle,bool accept_if_acute,const Disk_samples_list & disk_samples)372   boost::optional<double> calculate_sdf_value_of_facet(
373     face_handle facet,
374     double cone_angle,
375     bool accept_if_acute,
376     const Disk_samples_list& disk_samples) const {
377 
378     const Point p1 = get(vertex_point_map,target(halfedge(facet,mesh),mesh));
379     const Point p2 = get(vertex_point_map,target(next(halfedge(facet,mesh),mesh),mesh));
380     const Point p3 = get(vertex_point_map,target(prev(halfedge(facet,mesh),mesh),mesh));
381     const Point center  = centroid_functor(p1, p2, p3);
382     if (collinear_functor(p1, p2, p3)) return boost::none;
383     Vector normal = normal_functor(p2, p1, p3);
384     normal=scale_functor(normal,
385                          FT(1.0/std::sqrt(to_double(normal.squared_length()))));
386     if (normal!=normal) return boost::none;
387     CGAL::internal::SkipPrimitiveFunctor<face_handle>
388     skip(facet);
389     CGAL::internal::FirstIntersectionVisitor<face_handle>
390     visitor;
391 
392     return calculate_sdf_value_of_point(center, normal, skip, visitor, cone_angle,
393                                         accept_if_acute, disk_samples);
394   }
395 
396   /**
397    * Finds closest intersection for parameter @a query.
398    * @param query `Segment` or `Ray` type query.
399    * It is now only used with `Segment` as the function `first_intersection()` is faster if rays are used
400    * @param tree AABB tree which includes polyhedron
401    * @param facet parent facet of @a query
402    * (since numerical limitations on both center calculation and intersection test, query might intersect with related facet, should be skipped in such case)
403    * @return tuple of:
404    *   - get<0> bool   : true if any intersection is found
405    *   - get<1> bool   : true if intersection is found and is acceptable (i.e. accute angle with surface normal)
406    *   - get<2> double : distance between ray/segment origin and intersection point (0.0 if get<0> is false)
407    *   - get<3> Primitive_id : closest intersected primitive if get<0> is true, else Primitive_id()
408    */
409   template <class Query, class SkipPrimitiveFunctor>
cast_and_return_minimum(const Query & query,SkipPrimitiveFunctor skip,bool accept_if_acute)410   boost::tuple<bool, bool, double, Primitive_id> cast_and_return_minimum(
411     const Query& query, SkipPrimitiveFunctor skip, bool accept_if_acute) const {
412     boost::tuple<bool, bool, double, Primitive_id>
413     min_distance(false, false, 0.0, Primitive_id());
414 
415     typedef typename Tree:: template Intersection_and_primitive_id<Query>::Type Intersection_and_primitive_id;
416     std::list<Intersection_and_primitive_id> intersections;
417 
418     //SL: the difference with all_intersections is that in the traversal traits, we do do_intersect before calling intersection.
419     typedef  std::back_insert_iterator< std::list<Intersection_and_primitive_id> >
420     Output_iterator;
421     Listing_intersection_traits_ray_or_segment_triangle<typename Tree::AABB_traits,Query,Output_iterator>
422     traversal_traits(std::back_inserter(intersections), tree.traits());
423     tree.traversal(query,traversal_traits);
424 
425     Vector min_i_ray(NULL_VECTOR);
426     Primitive_id min_id;
427     for(typename std::list<Intersection_and_primitive_id>::iterator op_it =
428           intersections.begin();
429         op_it != intersections.end() ; ++op_it) {
430       const typename Intersection_and_primitive_id::first_type& object = op_it->first;
431       Primitive_id id = op_it->second;
432       if( skip(id) ) {
433         continue;
434       }
435 
436       const Point* i_point;
437       if(!(i_point = boost::get<Point>(&object))) {
438         continue;  // continue in case of segment.
439       }
440 
441       Vector i_ray(*i_point, query.source());
442       double new_distance = to_double( i_ray.squared_length() );
443       if(!min_distance.template get<0>()
444           || new_distance < min_distance.template get<2>()) {
445         min_distance.template get<3>() = id;
446         min_distance.template get<2>() = new_distance;
447         min_distance.template get<0>() = true;
448         min_id = id;
449         min_i_ray = i_ray;
450       }
451     }
452     if(!min_distance.template get<0>()) {
453       return min_distance;
454     }
455 
456     if(accept_if_acute) {
457       // check whether the ray makes acute angle with intersected facet
458       const Point& min_v1 = get(vertex_point_map,target(halfedge(min_id,mesh),mesh));
459       const Point& min_v2 = get(vertex_point_map,target(next(halfedge(min_id,mesh),mesh),mesh));
460       const Point& min_v3 = get(vertex_point_map,target(prev(halfedge(min_id,mesh),mesh),mesh));
461       Vector min_normal = scale_functor(normal_functor(min_v1, min_v2, min_v3), -1.0);
462 
463       if(angle_functor(translated_point_functor(Point(ORIGIN), min_i_ray),
464                        Point(ORIGIN),
465                        translated_point_functor(Point(ORIGIN), min_normal)) != ACUTE) {
466         return min_distance;
467       }
468     }
469 
470     min_distance.template get<1>() = true; // founded intersection is acceptable.
471     min_distance.template get<2>() = std::sqrt(min_distance.template get<2>());
472     return min_distance;
473   }
474 
475   // function similar to `cast_and_return_minimum()` but using the function
476   // first_intersection with a Ray to get the closest intersected primitive
477   template<typename SkipFunctor>
ray_casting(const Ray & query,SkipFunctor s,bool accept_if_acute)478   boost::tuple<bool, bool, double, Primitive_id> ray_casting(
479     const Ray& query, SkipFunctor s, bool accept_if_acute) const {
480 
481     const boost::optional< typename Tree::template Intersection_and_primitive_id<Ray>::Type >
482       min_intersection = tree.first_intersection(query, s);
483     if(!min_intersection)
484       return boost::make_tuple(false, false, 0.0, Primitive_id());
485 
486     const Point* i_point = boost::get<Point>( &min_intersection->first );
487     if (!i_point) //segment case ignored
488       return boost::make_tuple(false, false, 0.0, Primitive_id());
489 
490     Vector min_i_ray(*i_point, query.source());
491 
492     boost::tuple<bool, bool, double, Primitive_id>
493     min_distance(true, false, to_double(min_i_ray.squared_length()), min_intersection->second);
494 
495     const Primitive_id& min_id = min_distance.template get<3>();
496 
497     if(accept_if_acute) {
498       // check whether the ray makes acute angle with intersected facet
499       const Point& min_v1 = get(vertex_point_map,target(halfedge(min_id,mesh),mesh));
500       const Point& min_v2 = get(vertex_point_map,target(next(halfedge(min_id,mesh),mesh),mesh));
501       const Point& min_v3 = get(vertex_point_map,target(prev(halfedge(min_id,mesh),mesh),mesh));
502       Vector min_normal = scale_functor(normal_functor(min_v1, min_v2, min_v3), -1.0);
503 
504       if(angle_functor(translated_point_functor(Point(ORIGIN), min_i_ray),
505                        Point(ORIGIN),
506                        translated_point_functor(Point(ORIGIN), min_normal)) != ACUTE) {
507         return min_distance;
508       }
509     }
510 
511     min_distance.template get<1>() = true; // founded intersection is acceptable.
512     min_distance.template get<2>() = std::sqrt(min_distance.template get<2>());
513 
514     return min_distance;
515   }
516 
517   /**
518    * Uses Median Absolute Deviation and removes rays which don't fall into `CGAL_NUMBER_OF_MAD` * MAD.
519    * Also takes weighted average of accepted rays and calculate final sdf value.
520    * @param ray_distances contains distance & weight pairs for each ray
521    * @return outlier removed and averaged sdf value
522    */
remove_outliers_and_calculate_sdf_value(std::vector<std::pair<double,double>> & ray_distances)523   double remove_outliers_and_calculate_sdf_value(
524     std::vector<std::pair<double, double> >& ray_distances) const {
525     // pair first -> distance, second -> weight
526 
527     const std::size_t accepted_ray_count = ray_distances.size();
528     if(accepted_ray_count == 0)      {
529       return 0.0;
530     } else if(accepted_ray_count == 1) {
531       return ray_distances[0].first;
532     }
533 
534     /* Calculate median sdf */
535     const std::size_t half_ray_count = accepted_ray_count / 2;
536     std::nth_element(ray_distances.begin(), ray_distances.begin() + half_ray_count,
537                      ray_distances.end());
538     double median_sdf = ray_distances[half_ray_count].first;
539     if(accepted_ray_count % 2 == 0) {
540       median_sdf += std::max_element(ray_distances.begin(),
541                                      ray_distances.begin() + half_ray_count)->first;
542       median_sdf /= 2.0;
543     }
544 
545     /* Calculate median absolute deviation */
546     std::vector<double> absolute_deviation;
547     absolute_deviation.reserve(accepted_ray_count);
548     for(std::vector<std::pair<double, double> >::iterator it =
549           ray_distances.begin(); it != ray_distances.end(); ++it) {
550       absolute_deviation.push_back(std::abs(it->first - median_sdf));
551     }
552 
553     std::nth_element(absolute_deviation.begin(),
554                      absolute_deviation.begin() + half_ray_count, absolute_deviation.end());
555     double median_deviation = absolute_deviation[half_ray_count];
556     if(accepted_ray_count % 2 == 0) {
557       median_deviation += *std::max_element(absolute_deviation.begin(),
558                                             absolute_deviation.begin() + half_ray_count);
559       median_deviation /= 2.0;
560     }
561 
562     /* Calculate sdf, accept rays if ray_dist - median < (median_deviation * Factor) */
563     double total_weights = 0.0, total_distance = 0.0;
564     for(std::vector<std::pair<double, double> >::iterator it =
565           ray_distances.begin(); it != ray_distances.end(); ++it) {
566       if(std::abs(it->first - median_sdf) > (median_deviation * CGAL_NUMBER_OF_MAD)) {
567         continue;
568       }
569       total_distance += it->first * it->second;
570       total_weights += it->second;
571     }
572 
573     if(total_distance == 0.0) {
574       return median_sdf;  // no ray is accepted, return median.
575     } else                      {
576       return total_distance / total_weights;
577     }
578   }
579 };
580 }//namespace internal
581 /// @endcond
582 }//namespace CGAL
583 #undef CGAL_NUMBER_OF_MAD
584 
585 #endif //CGAL_SURFACE_MESH_SEGMENTATION_SDF_CALCULATION_H
586