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