1 // Copyright (c) 2018 GeometryFactory (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/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h $
7 // $Id: mesh_smoothing_impl.h 29ddd67 2020-02-06T17:14:16+01:00 Mael Rouxel-Labbé
8 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
9 //
10 //
11 // Author(s)     : Mael Rouxel-Labbé
12 //                 Konstantinos Katrioplas (konst.katrioplas@gmail.com)
13 
14 #ifndef CGAL_POLYGON_MESH_PROCESSING_INTERNAL_MESH_SMOOTHING_IMPL_H
15 #define CGAL_POLYGON_MESH_PROCESSING_INTERNAL_MESH_SMOOTHING_IMPL_H
16 
17 #include <CGAL/license/Polygon_mesh_processing/meshing_hole_filling.h>
18 
19 #ifdef CGAL_PMP_USE_CERES_SOLVER
20 #include <CGAL/Polygon_mesh_processing/internal/Smoothing/ceres_support.h>
21 #endif
22 
23 #include <CGAL/Polygon_mesh_processing/compute_normal.h>
24 #include <CGAL/Polygon_mesh_processing/shape_predicates.h>
25 
26 #include <CGAL/AABB_tree.h>
27 #include <CGAL/AABB_traits.h>
28 #include <CGAL/AABB_triangle_primitive.h>
29 #include <CGAL/boost/graph/Euler_operations.h>
30 #include <CGAL/Dynamic_property_map.h>
31 #include <CGAL/Kernel/global_functions_3.h>
32 #include <CGAL/iterator.h>
33 #include <CGAL/number_type_config.h>
34 #include <CGAL/Origin.h>
35 #include <CGAL/property_map.h>
36 #include <CGAL/use.h>
37 #include <CGAL/utils.h>
38 
39 #include <boost/graph/graph_traits.hpp>
40 
41 #include <algorithm>
42 #include <cmath>
43 #include <iterator>
44 #include <map>
45 #include <utility>
46 #include <vector>
47 
48 namespace CGAL {
49 namespace Polygon_mesh_processing {
50 namespace internal {
51 
52 template <typename V, typename GT>
get_radian_angle(const V & v1,const V & v2,const GT & gt)53 typename GT::FT get_radian_angle(const V& v1, const V& v2, const GT& gt)
54 {
55   typedef typename GT::FT FT;
56 
57   return gt.compute_approximate_angle_3_object()(v1, v2) * CGAL_PI / FT(180);
58 }
59 
60 // super naive for now. Not sure it even makes sense to do something like that for surfaces
61 template<typename TriangleMesh,
62          typename VertexPointMap,
63          typename ECMap,
64          typename GeomTraits>
65 class Delaunay_edge_flipper
66 {
67   typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor    vertex_descriptor;
68   typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor  halfedge_descriptor;
69   typedef typename boost::graph_traits<TriangleMesh>::edge_descriptor      edge_descriptor;
70   typedef typename boost::graph_traits<TriangleMesh>::face_descriptor      face_descriptor;
71 
72   typedef typename boost::property_traits<VertexPointMap>::reference       Point_ref;
73   typedef typename GeomTraits::FT                                          FT;
74   typedef typename GeomTraits::Vector_3                                    Vector;
75 
76 public:
Delaunay_edge_flipper(TriangleMesh & mesh,const VertexPointMap vpmap,const ECMap ecmap,const GeomTraits & traits)77   Delaunay_edge_flipper(TriangleMesh& mesh,
78                         const VertexPointMap vpmap,
79                         const ECMap ecmap,
80                         const GeomTraits& traits)
81     : mesh_(mesh), vpmap_(vpmap), ecmap_(ecmap), traits_(traits)
82   { }
83 
should_be_flipped(const edge_descriptor e)84   bool should_be_flipped(const edge_descriptor e) const
85   {
86     if(is_border(e, mesh_) || get(ecmap_, e))
87       return false;
88 
89     const halfedge_descriptor h = halfedge(e, mesh_);
90     const halfedge_descriptor opp_h = opposite(h, mesh_);
91 
92     const vertex_descriptor v0 = source(h, mesh_);
93     const vertex_descriptor v1 = target(h, mesh_);
94     const vertex_descriptor v2 = target(next(h, mesh_), mesh_);
95     const vertex_descriptor v3 = target(next(opp_h, mesh_), mesh_);
96 
97     std::set<vertex_descriptor> unique_vs { v0, v1, v2, v3 };
98     if(unique_vs.size() != 4)
99       return false;
100 
101     // Don't want to flip if the other diagonal already exists
102     // @todo remeshing can be used to still flip those
103     std::pair<edge_descriptor, bool> other_hd_already_exists = edge(v2, v3, mesh_);
104     if(other_hd_already_exists.second)
105       return false;
106 
107     // not local Delaunay := sum of the opposite angles is greater than pi
108     const Point_ref p0 = get(vpmap_, v0);
109     const Point_ref p1 = get(vpmap_, v1);
110     const Point_ref p2 = get(vpmap_, v2);
111     const Point_ref p3 = get(vpmap_, v3);
112 
113     FT alpha = get_radian_angle(Vector(p0 - p2), Vector(p1 - p2), traits_);
114     FT beta = get_radian_angle(Vector(p1 - p3), Vector(p0 - p3), traits_);
115 
116     return (alpha + beta > CGAL_PI);
117   }
118 
119   template <typename Marked_edges_map, typename EdgeRange>
add_to_stack_if_unmarked(const edge_descriptor e,const Marked_edges_map marks,EdgeRange & edge_range)120   void add_to_stack_if_unmarked(const edge_descriptor e,
121                                 const Marked_edges_map marks,
122                                 EdgeRange& edge_range)
123   {
124     if(!get(marks, e))
125     {
126       put(marks, e, true);
127       edge_range.push_back(e);
128     }
129   }
130 
131 public:
132   template <typename FaceRange>
operator()133   void operator()(const FaceRange& face_range)
134   {
135 #ifdef CGAL_PMP_SMOOTHING_DEBUG
136     std::cout << "Flipping edges" << std::endl;
137 #endif
138 
139     // edges to consider
140     std::vector<edge_descriptor> edge_range;
141     edge_range.reserve(3 * face_range.size());
142     for(face_descriptor f : face_range)
143     {
144       for(halfedge_descriptor h : halfedges_around_face(halfedge(f, mesh_), mesh_))
145         edge_range.push_back(edge(h, mesh_));
146     }
147 
148     // keep unique elements
149     std::sort(edge_range.begin(), edge_range.end());
150     edge_range.erase(std::unique(edge_range.begin(), edge_range.end()), edge_range.end());
151 
152     // Mark edges that are in the stack
153     typedef CGAL::dynamic_edge_property_t<bool>                         Edge_property_tag;
154     typedef typename boost::property_map<TriangleMesh,
155                                          Edge_property_tag>::type       Marked_edges_map;
156 
157     Marked_edges_map marks = get(Edge_property_tag(), mesh_);
158 
159     // dynamic pmaps do not have default values...
160     for(edge_descriptor e : edges(mesh_))
161       put(marks, e, false);
162     for(edge_descriptor e : edge_range)
163       put(marks, e, true);
164 
165     int flipped_n = 0;
166     while(!edge_range.empty())
167     {
168       edge_descriptor e = edge_range.back();
169 
170       edge_range.pop_back();
171       put(marks, e, false);
172 
173       if(should_be_flipped(e))
174       {
175         ++flipped_n;
176 
177         halfedge_descriptor h = halfedge(e, mesh_);
178 
179 #ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
180         std::cout << "Flipping " << edge(h, mesh_) << std::endl;
181 #endif
182         Euler::flip_edge(h, mesh_);
183 
184         add_to_stack_if_unmarked(edge(next(h, mesh_), mesh_), marks, edge_range);
185         add_to_stack_if_unmarked(edge(prev(h, mesh_), mesh_), marks, edge_range);
186         add_to_stack_if_unmarked(edge(next(opposite(h, mesh_), mesh_), mesh_), marks, edge_range);
187         add_to_stack_if_unmarked(edge(prev(opposite(h, mesh_), mesh_), mesh_), marks, edge_range);
188       }
189     }
190 
191 #ifdef CGAL_PMP_SMOOTHING_DEBUG
192     std::cout << flipped_n << " flips" << std::endl;
193 #endif
194   }
195 
196 private:
197   TriangleMesh& mesh_;
198   const VertexPointMap vpmap_;
199   const ECMap ecmap_;
200   const GeomTraits& traits_;
201 };
202 
203 template<typename TriangleMesh, typename VertexPointMap, typename GeomTraits>
204 class Angle_smoother
205 {
206   typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor    vertex_descriptor;
207   typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor  halfedge_descriptor;
208 
209   typedef typename boost::property_traits<VertexPointMap>::reference       Point_ref;
210   typedef typename GeomTraits::FT                                          FT;
211   typedef typename GeomTraits::Vector_3                                    Vector;
212 
213   typedef std::pair<halfedge_descriptor, halfedge_descriptor>              He_pair;
214 
215 public:
Angle_smoother(const TriangleMesh & mesh,const VertexPointMap vpmap,const GeomTraits & traits)216   Angle_smoother(const TriangleMesh& mesh,
217                  const VertexPointMap vpmap,
218                  const GeomTraits& traits)
219     : mesh_(mesh), vpmap_(vpmap), traits_(traits)
220   { }
221 
222 private:
rotate_edge(const halfedge_descriptor main_he,const He_pair & incident_pair)223   Vector rotate_edge(const halfedge_descriptor main_he,
224                      const He_pair& incident_pair) const
225   {
226     // get common vertex around which the edge is rotated
227     Point_ref pt = get(vpmap_, target(main_he, mesh_));
228 
229     Point_ref left_pt = get(vpmap_, source(incident_pair.first, mesh_));
230     Point_ref right_pt = get(vpmap_, target(incident_pair.second, mesh_));
231     CGAL_assertion(target(incident_pair.first, mesh_) == source(incident_pair.second, mesh_));
232 
233     Vector edge1(pt, left_pt);
234     Vector edge2(pt, right_pt);
235 
236     // find bisector
237     internal::normalize(edge1, traits_);
238     internal::normalize(edge2, traits_);
239 
240     Vector bisector = traits_.construct_sum_of_vectors_3_object()(edge1, edge2);
241     internal::normalize(bisector, traits_);
242 
243     return bisector;
244   }
245 
246 public:
247   // If it's ever allowed to move vertices on the border, the min angle computations will be missing
248   // some values (angles incident to the border)
operator()249   Vector operator()(const vertex_descriptor v) const
250   {
251     Vector move = CGAL::NULL_VECTOR;
252     FT weights_sum = FT(0);
253 
254     for(halfedge_descriptor main_he : halfedges_around_source(v, mesh_))
255     {
256       He_pair incident_pair = std::make_pair(prev(opposite(main_he, mesh_), mesh_),
257                                              next(main_he, mesh_));
258 
259       // avoid zero angles
260       Point_ref ps = get(vpmap_, source(main_he, mesh_));
261       Point_ref pt = get(vpmap_, target(main_he, mesh_));
262       Point_ref left_pt = get(vpmap_, source(incident_pair.first, mesh_));
263       Point_ref right_pt = get(vpmap_, target(incident_pair.second, mesh_));
264       CGAL_assertion(target(incident_pair.first, mesh_) == source(incident_pair.second, mesh_));
265 
266       Vector left_v(pt, left_pt);
267       Vector right_v(pt, right_pt);
268 
269       // rotate
270       Vector bisector = rotate_edge(main_he, incident_pair);
271       FT scaling_factor = CGAL::approximate_sqrt(
272                             traits_.compute_squared_distance_3_object()(get(vpmap_, source(main_he, mesh_)),
273                                                                         get(vpmap_, target(main_he, mesh_))));
274       bisector = traits_.construct_scaled_vector_3_object()(bisector, scaling_factor);
275       Vector ps_psi(ps, traits_.construct_translated_point_3_object()(pt, bisector));
276 
277       FT angle = get_radian_angle(right_v, left_v, traits_);
278       if(angle == FT(0))
279       {
280         // no degenerate faces is a precondition, angle can be 0 but it should be a numerical error
281         CGAL_warning(!is_degenerate_triangle_face(face(main_he, mesh_), mesh_));
282 
283         return ps_psi; // since a small angle gives more weight, a null angle give priority (?)
284       }
285 
286       // small angles carry more weight
287       FT weight = 1. / CGAL::square(angle);
288       weights_sum += weight;
289 
290       move += weight * ps_psi;
291     }
292 
293     if(weights_sum != FT(0))
294      move /= weights_sum;
295 
296     return move;
297   }
298 
299 private:
300   const TriangleMesh& mesh_;
301   const VertexPointMap vpmap_;
302   const GeomTraits& traits_;
303 };
304 
305 template<typename TriangleMesh, typename VertexPointMap, typename GeomTraits>
306 class Area_smoother
307 {
308   typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor    vertex_descriptor;
309   typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor  halfedge_descriptor;
310 
311   typedef typename boost::property_traits<VertexPointMap>::value_type      Point;
312   typedef typename boost::property_traits<VertexPointMap>::reference       Point_ref;
313   typedef typename GeomTraits::FT                                          FT;
314   typedef typename GeomTraits::Vector_3                                    Vector;
315 
316 public:
Area_smoother(const TriangleMesh & mesh,const VertexPointMap vpmap,const GeomTraits & traits)317   Area_smoother(const TriangleMesh& mesh,
318                 const VertexPointMap vpmap,
319                 const GeomTraits& traits)
320     : mesh_(mesh), vpmap_(vpmap), traits_(traits)
321   { }
322 
323 private:
element_area(const vertex_descriptor v1,const vertex_descriptor v2,const vertex_descriptor v3)324   FT element_area(const vertex_descriptor v1,
325                   const vertex_descriptor v2,
326                   const vertex_descriptor v3) const
327   {
328     return CGAL::approximate_sqrt(traits_.compute_squared_area_3_object()(get(vpmap_, v1),
329                                                                           get(vpmap_, v2),
330                                                                           get(vpmap_, v3)));
331   }
332 
element_area(const Point & P,const vertex_descriptor v2,const vertex_descriptor v3)333   FT element_area(const Point& P,
334                   const vertex_descriptor v2,
335                   const vertex_descriptor v3) const
336   {
337     return CGAL::approximate_sqrt(traits_.compute_squared_area_3_object()(P,
338                                                                           get(vpmap_, v2),
339                                                                           get(vpmap_, v3)));
340   }
341 
compute_average_area_around(const vertex_descriptor v)342   FT compute_average_area_around(const vertex_descriptor v) const
343   {
344     FT sum_areas = 0;
345     unsigned int number_of_edges = 0;
346 
347     for(halfedge_descriptor h : halfedges_around_source(v, mesh_))
348     {
349       // opposite vertices
350       vertex_descriptor vi = source(next(h, mesh_), mesh_);
351       vertex_descriptor vj = target(next(h, mesh_), mesh_);
352 
353       FT S = element_area(v, vi, vj);
354       sum_areas += S;
355       ++number_of_edges;
356     }
357 
358     return sum_areas / number_of_edges;
359   }
360 
361   struct Face_energy
362   {
Face_energyFace_energy363     Face_energy(const Point& pi, const Point& pj, const FT s_av)
364       :
365         qx(pi.x()), qy(pi.y()), qz(pi.z()),
366         rx(pj.x()), ry(pj.y()), rz(pj.z()),
367         s_av(s_av)
368     { }
369 
370     // next two functions are just for convencience, the only thing ceres cares about is the operator()
371     template <typename T>
areaFace_energy372     FT area(const T x, const T y, const T z) const
373     {
374       return CGAL::approximate_sqrt(CGAL::squared_area(Point(x, y, z),
375                                                        Point(qx, qy, qz),
376                                                        Point(rx, ry, rz)));
377     }
378 
379     template <typename T>
evaluateFace_energy380     FT evaluate(const T x, const T y, const T z) const { return area(x, y, z) - s_av; }
381 
382     template <typename T>
operatorFace_energy383     bool operator()(const T* const x, const T* const y, const T* const z,
384                     T* residual) const
385     {
386       // Defining this because I haven't found much difference empirically (auto-diff being maybe
387       // a couple % faster), but numeric differenciation should be stronger in the face
388       // of difficult cases. Leaving the auto-differenciation formulation in case somebody really
389       // cares about the extra speed.
390 #define CGAL_CERES_USE_NUMERIC_DIFFERENCIATION
391 
392 #ifdef CGAL_CERES_USE_NUMERIC_DIFFERENCIATION
393       residual[0] = evaluate(x[0], y[0], z[0]);
394 #else
395       // Computations must be explicit so that automatic differenciation can be used
396       T dqx = qx - x[0];
397       T dqy = qy - y[0];
398       T dqz = qz - z[0];
399       T drx = rx - x[0];
400       T dry = ry - y[0];
401       T drz = rz - z[0];
402 
403       T vx = dqy*drz - dqz*dry;
404       T vy = dqz*drx - dqx*drz;
405       T vz = dqx*dry - dqy*drx;
406 
407       T squared_area = 0.25 * (vx*vx + vy*vy + vz*vz);
408       T area = sqrt(squared_area);
409 
410       residual[0] = area - s_av;
411 #endif
412       return true;
413     }
414 
415   private:
416     const FT qx, qy, qz;
417     const FT rx, ry, rz;
418     const FT s_av;
419   };
420 
421 public:
operator()422   Vector operator()(const vertex_descriptor v) const
423   {
424 #ifdef CGAL_PMP_USE_CERES_SOLVER
425     const Point_ref vp = get(vpmap_, v);
426 
427     const FT S_av = compute_average_area_around(v);
428 
429     const FT initial_x = vp.x();
430     const FT initial_y = vp.y();
431     const FT initial_z = vp.z();
432     FT x = initial_x, y = initial_y, z = initial_z;
433 
434     ceres::Problem problem;
435 
436     problem.AddParameterBlock(&x, 1);
437     problem.AddParameterBlock(&y, 1);
438     problem.AddParameterBlock(&z, 1);
439 
440     for(halfedge_descriptor h : halfedges_around_source(v, mesh_))
441     {
442       CGAL_assertion(!is_border(h, mesh_));
443 
444       vertex_descriptor vi = source(next(h, mesh_), mesh_);
445       vertex_descriptor vj = target(next(h, mesh_), mesh_);
446       const Point_ref vip = get(vpmap_, vi);
447       const Point_ref vjp = get(vpmap_, vj);
448 
449 #ifdef CGAL_CERES_USE_NUMERIC_DIFFERENCIATION
450       ceres::CostFunction* cost_function =
451         new ceres::NumericDiffCostFunction<Face_energy, ceres::CENTRAL, 1, 1, 1, 1>(new Face_energy(vip, vjp, S_av));
452 #else
453       ceres::CostFunction* cost_function =
454         new ceres::AutoDiffCostFunction<Face_energy, 1, 1, 1, 1>(new Face_energy(vip, vjp, S_av));
455 #endif
456       problem.AddResidualBlock(cost_function, NULL, &x, &y, &z);
457     }
458 
459     ceres::Solver::Options options;
460     options.logging_type = ceres::SILENT;
461 //    options.minimizer_progress_to_stdout = true;
462     ceres::Solver::Summary summary;
463     ceres::Solve(options, &problem, &summary);
464 //    std::cout << summary.BriefReport() << "\n";
465 //    std::cout << "x : " << initial_x << " -> " << x << "\n";
466 //    std::cout << "y : " << initial_y << " -> " << y << "\n";
467 //    std::cout << "z : " << initial_z << " -> " << z << "\n";
468 
469     return Vector(x - initial_x, y - initial_y, z - initial_z);
470 #else
471     CGAL_USE(v);
472     return CGAL::NULL_VECTOR;
473 #endif
474   }
475 
476 private:
477   const TriangleMesh& mesh_;
478   const VertexPointMap vpmap_;
479   const GeomTraits& traits_;
480 };
481 
482 template<typename Optimizer, typename TriangleMesh,
483          typename VertexPointMap, typename VertexConstraintMap,
484          typename GeomTraits>
485 class Mesh_smoother
486 {
487   typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor    vertex_descriptor;
488   typedef typename boost::graph_traits<TriangleMesh>::edge_descriptor      edge_descriptor;
489   typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor  halfedge_descriptor;
490   typedef typename boost::graph_traits<TriangleMesh>::face_descriptor      face_descriptor;
491 
492   typedef typename boost::property_traits<VertexPointMap>::value_type      Point;
493   typedef typename boost::property_traits<VertexPointMap>::reference       Point_ref;
494   typedef typename GeomTraits::FT                                          FT;
495   typedef typename GeomTraits::Vector_3                                    Vector;
496 
497 public:
Mesh_smoother(TriangleMesh & pmesh,VertexPointMap & vpmap,VertexConstraintMap & vcmap,const GeomTraits & traits)498   Mesh_smoother(TriangleMesh& pmesh,
499                 VertexPointMap& vpmap,
500                 VertexConstraintMap& vcmap,
501                 const GeomTraits& traits)
502     :
503       mesh_(pmesh), vpmap_(vpmap), vcmap_(vcmap), traits_(traits)
504   {}
505 
506 public:
507   template<typename FaceRange>
set_vertex_range(const FaceRange & face_range)508   void set_vertex_range(const FaceRange& face_range)
509   {
510     vrange_.reserve(3 * face_range.size());
511     for(face_descriptor f : face_range)
512     {
513      for(vertex_descriptor v : vertices_around_face(halfedge(f, mesh_), mesh_))
514       vrange_.push_back(v);
515     }
516 
517     // get rid of duplicate vertices
518     std::sort(vrange_.begin(), vrange_.end());
519     vrange_.erase(std::unique(vrange_.begin(), vrange_.end()), vrange_.end());
520   }
521 
522   template<typename FaceRange>
init_smoothing(const FaceRange & face_range)523   void init_smoothing(const FaceRange& face_range)
524   {
525     CGAL_precondition(CGAL::is_triangle_mesh(mesh_));
526     CGAL_precondition_code(std::set<typename boost::graph_traits<TriangleMesh>::face_descriptor> degen_faces;)
527     CGAL_precondition_code(CGAL::Polygon_mesh_processing::degenerate_faces(
528                             mesh_, std::inserter(degen_faces, degen_faces.begin()),
529                             CGAL::parameters::vertex_point_map(vpmap_).geom_traits(traits_));)
530     CGAL_precondition(degen_faces.empty());
531 
532     set_vertex_range(face_range);
533   }
534 
535   // generic optimizer, the move is computed by 'Optimizer'
536   std::size_t optimize(const bool use_sanity_checks = true,
537                        const bool apply_moves_in_single_batch = false,
538                        const bool enforce_no_min_angle_regression = false)
539   {
540     typedef CGAL::dynamic_vertex_property_t<Point>                      Vertex_property_tag;
541     typedef typename boost::property_map<TriangleMesh,
542                                          Vertex_property_tag>::type     Position_map;
543 
544     Position_map new_positions = get(Vertex_property_tag(), mesh_);
545 
546     Optimizer compute_move(mesh_, vpmap_, traits_);
547 
548 #ifdef CGAL_PMP_SMOOTHING_DEBUG
549     FT total_displacement = 0;
550     std::cout << "apply_moves_in_single_batch: " << apply_moves_in_single_batch << std::endl;
551 #endif
552 
553     std::size_t moved_points = 0;
554     for(vertex_descriptor v : vrange_)
555     {
556       if(is_border(v, mesh_) || is_constrained(v))
557         continue;
558 
559 #ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
560       std::cout << "Considering " << v << " pos: " << get(vpmap_, v) << std::endl;
561 #endif
562 
563       // compute normal to v
564       Vector vn = compute_vertex_normal(v, mesh_, CGAL::parameters::vertex_point_map(vpmap_)
565                                                                    .geom_traits(traits_));
566 
567       // calculate movement
568       const Point_ref pos = get(vpmap_, v);
569       Vector move = compute_move(v);
570 
571       // Gram Schmidt so that the new location is on the tangent plane of v (i.e. do mv -= (mv*n)*n)
572       const FT sp = traits_.compute_scalar_product_3_object()(vn, move);
573       move = traits_.construct_sum_of_vectors_3_object()(
574                move, traits_.construct_scaled_vector_3_object()(vn, - sp));
575 
576       const Point new_pos = pos + move;
577       if(move != CGAL::NULL_VECTOR &&
578          !does_move_create_degenerate_faces(v, new_pos) &&
579          (!use_sanity_checks || !does_move_create_bad_faces(v, new_pos)) &&
580          (!enforce_no_min_angle_regression || does_improve_min_angle_in_star(v, new_pos)))
581       {
582 #ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
583         std::cout << "moving " << get(vpmap_, v) << " to " << new_pos << std::endl;
584 #endif
585 
586         if(apply_moves_in_single_batch)
587           put(new_positions, v, new_pos);
588         else
589           put(vpmap_, v, new_pos);
590 
591 #ifdef CGAL_PMP_SMOOTHING_DEBUG
592         total_displacement += CGAL::approximate_sqrt(traits_.compute_squared_length_3_object()(move));
593 #endif
594 
595         ++moved_points;
596       }
597       else // some sanity check failed
598       {
599 #ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
600         std::cout << "move is rejected!" << std::endl;
601 #endif
602         if(apply_moves_in_single_batch)
603           put(new_positions, v, pos);
604       }
605     }
606 
607     // update locations
608     if(apply_moves_in_single_batch)
609     {
610       for(vertex_descriptor v : vrange_)
611       {
612         if(is_border(v, mesh_) || is_constrained(v))
613           continue;
614 
615         put(vpmap_, v, get(new_positions, v));
616       }
617     }
618 
619 #ifdef CGAL_PMP_SMOOTHING_DEBUG
620     std::cout << "moved: " << moved_points << " points based on angle." << std::endl;
621     std::cout << "total displacement: " << total_displacement << std::endl;
622     std::cout << "not improved min angle: " << vrange_.size() - moved_points << " times." << std::endl;
623 #endif
624 
625     return moved_points;
626   }
627 
628   template <typename AABBTree>
project_to_surface(const AABBTree & tree)629   void project_to_surface(const AABBTree& tree)
630   {
631 #ifdef CGAL_PMP_SMOOTHING_DEBUG
632     std::cout << "Projecting back to the surface" << std::endl;
633 #endif
634 
635     for(vertex_descriptor v : vrange_)
636     {
637       if(is_border(v, mesh_) || is_constrained(v))
638         continue;
639 
640       Point_ref p_query = get(vpmap_, v);
641       const Point projected = tree.closest_point(p_query);
642 #ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
643       std::cout << p_query << " to " << projected << std::endl;
644 #endif
645 
646       put(vpmap_, v, projected);
647     }
648   }
649 
650 private:
is_constrained(const vertex_descriptor v)651   bool is_constrained(const vertex_descriptor v)
652   {
653     return get(vcmap_, v);
654   }
655 
656   // Null faces are bad because they make normal computation difficult
does_move_create_degenerate_faces(const vertex_descriptor v,const Point & new_pos)657   bool does_move_create_degenerate_faces(const vertex_descriptor v,
658                                          const Point& new_pos) const
659   {
660     for(halfedge_descriptor main_he : halfedges_around_source(v, mesh_))
661     {
662       const halfedge_descriptor prev_he = prev(main_he, mesh_);
663       const Point_ref lpt = get(vpmap_, target(main_he, mesh_));
664       const Point_ref rpt = get(vpmap_, source(prev_he, mesh_));
665 
666       if(traits_.collinear_3_object()(lpt, rpt, new_pos))
667         return true;
668     }
669 
670     return false;
671   }
672 
673   // check for degenerate or inversed faces
does_move_create_bad_faces(const vertex_descriptor v,const Point & new_pos)674   bool does_move_create_bad_faces(const vertex_descriptor v,
675                                   const Point& new_pos) const
676   {
677     // check for face inversions
678     for(halfedge_descriptor main_he : halfedges_around_source(v, mesh_))
679     {
680       const halfedge_descriptor prev_he = prev(main_he, mesh_);
681       const Point_ref lpt = get(vpmap_, target(main_he, mesh_));
682       const Point_ref rpt = get(vpmap_, source(prev_he, mesh_));
683 
684       CGAL_assertion(!traits_.collinear_3_object()(lpt, rpt, new_pos)); // checked above
685 
686       const Point_ref old_pos = get(vpmap_, v);
687       Vector ov_1 = traits_.construct_vector_3_object()(old_pos, lpt);
688       Vector ov_2 = traits_.construct_vector_3_object()(old_pos, rpt);
689       Vector old_n = traits_.construct_cross_product_vector_3_object()(ov_1, ov_2);
690       Vector nv_1 = traits_.construct_vector_3_object()(new_pos, lpt);
691       Vector nv_2 = traits_.construct_vector_3_object()(new_pos, rpt);
692       Vector new_n = traits_.construct_cross_product_vector_3_object()(nv_1, nv_2);
693 
694       if(!is_positive(traits_.compute_scalar_product_3_object()(old_n, new_n)))
695       {
696 #ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
697       std::cout << "Moving vertex would result in the inversion of a face normal!" << std::endl;
698 #endif
699         return true;
700       }
701     }
702 
703     return false;
704   }
705 
does_improve_min_angle_in_star(const vertex_descriptor v,const Point & new_pos)706   bool does_improve_min_angle_in_star(const vertex_descriptor v,
707                                       const Point& new_pos) const
708   {
709     // check if the minimum angle of the star has not deteriorated
710     FT old_min_angle = CGAL_PI;
711     for(halfedge_descriptor main_he : halfedges_around_source(v, mesh_))
712     {
713       const Point_ref old_pos = get(vpmap_, v);
714 
715       const halfedge_descriptor prev_he = prev(main_he, mesh_);
716       const Point_ref lpt = get(vpmap_, target(main_he, mesh_));
717       const Point_ref rpt = get(vpmap_, source(prev_he, mesh_));
718 
719       old_min_angle = (std::min)(old_min_angle,
720                                  (std::min)(get_radian_angle(Vector(old_pos, lpt),
721                                                              Vector(old_pos, rpt), traits_),
722                                             (std::min)(get_radian_angle(Vector(lpt, rpt),
723                                                                         Vector(lpt, old_pos), traits_),
724                                                        get_radian_angle(Vector(rpt, old_pos),
725                                                                         Vector(rpt, lpt), traits_))));
726     }
727 
728     for(halfedge_descriptor main_he : halfedges_around_source(v, mesh_))
729     {
730       const halfedge_descriptor prev_he = prev(main_he, mesh_);
731       const Point_ref lpt = get(vpmap_, target(main_he, mesh_));
732       const Point_ref rpt = get(vpmap_, source(prev_he, mesh_));
733 
734       if(get_radian_angle(Vector(new_pos, lpt), Vector(new_pos, rpt), traits_) < old_min_angle ||
735          get_radian_angle(Vector(lpt, rpt), Vector(lpt, new_pos), traits_) < old_min_angle ||
736          get_radian_angle(Vector(rpt, new_pos), Vector(rpt, lpt), traits_) < old_min_angle)
737       {
738 #ifdef CGAL_PMP_SMOOTHING_DEBUG_PP
739         const Point_ref old_pos = get(vpmap_, v);
740 
741         std::cout << "deterioration of min angle in the star!" << std::endl;
742         std::cout << "old/new positions: " << old_pos << " " << new_pos << std::endl;;
743         std::cout << "old min angle: " << old_min_angle << std::endl;
744         std::cout << "new angles: " << std::endl;
745         std::cout << get_radian_angle(Vector(new_pos, lpt), Vector(new_pos, rpt), traits_) << " ";
746         std::cout << get_radian_angle(Vector(lpt, rpt), Vector(lpt, new_pos), traits_) << " ";
747         std::cout << get_radian_angle(Vector(rpt, new_pos), Vector(rpt, lpt), traits_) << std::endl;
748 #endif
749         return false;
750       }
751     }
752 
753     return true;
754   }
755 
756 private:
757   TriangleMesh& mesh_;
758   VertexPointMap& vpmap_;
759   VertexConstraintMap vcmap_;
760   GeomTraits traits_;
761 
762   std::vector<vertex_descriptor> vrange_;
763 };
764 
765 } // namespace internal
766 } // namespace Polygon_mesh_processing
767 } // namespace CGAL
768 
769 #endif // CGAL_POLYGON_MESH_PROCESSING_INTERNAL_MESH_SMOOTHING_IMPL_H
770