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