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/curvature_flow_impl.h $
7 // $Id: curvature_flow_impl.h 7e12992 2020-06-15T17:12:04+02:00 Laurent Rineau
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_CURVATURE_FLOW_IMPL_H
15 #define CGAL_POLYGON_MESH_PROCESSING_INTERNAL_CURVATURE_FLOW_IMPL_H
16 
17 #include <CGAL/license/Polygon_mesh_processing/meshing_hole_filling.h>
18 
19 #include <CGAL/Polygon_mesh_processing/measure.h>
20 #include <CGAL/Polygon_mesh_processing/Weights.h>
21 #include <CGAL/Polygon_mesh_processing/internal/named_function_params.h>
22 #include <CGAL/Polygon_mesh_processing/internal/named_params_helper.h>
23 
24 #include <CGAL/Dynamic_property_map.h>
25 #include <CGAL/utility.h>
26 
27 #if defined(CGAL_EIGEN3_ENABLED)
28 #include <CGAL/Eigen_solver_traits.h>
29 #endif
30 
31 #include <boost/graph/graph_traits.hpp>
32 #include <boost/property_map/property_map.hpp>
33 
34 #include <algorithm>
35 #include <iostream>
36 #include <utility>
37 #include <vector>
38 #include <unordered_map>
39 
40 namespace CGAL {
41 namespace Polygon_mesh_processing {
42 namespace internal {
43 
44 // Empirically, _Meyer seems to produce the best results from the various weights available in Weights.h
45 template<typename TriangleMesh,
46          typename VertexPointMap,
47          typename CotangentValue = CGAL::internal::Cotangent_value_Meyer<TriangleMesh, VertexPointMap> >
48 struct Edge_cotangent_weight
49   : public CotangentValue
50 {
51   typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor   halfedge_descriptor;
52   typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor     vertex_descriptor;
53 
Edge_cotangent_weightEdge_cotangent_weight54   Edge_cotangent_weight(TriangleMesh& pmesh_, VertexPointMap vpmap_) : CotangentValue(pmesh_, vpmap_) {}
55 
pmeshEdge_cotangent_weight56   TriangleMesh& pmesh() { return CotangentValue::pmesh(); }
57 
operatorEdge_cotangent_weight58   double operator()(halfedge_descriptor he)
59   {
60     if(is_border_edge(he, pmesh()))
61     {
62       halfedge_descriptor h1 = next(he, pmesh());
63       vertex_descriptor vs = source(he, pmesh());
64       vertex_descriptor vt = target(he, pmesh());
65       vertex_descriptor v1 = target(h1, pmesh());
66 
67       return CotangentValue::operator()(vs, v1, vt);
68     }
69     else
70     {
71       halfedge_descriptor h1 = next(he, pmesh());
72       halfedge_descriptor h2 = prev(opposite(he, pmesh()), pmesh());
73       vertex_descriptor vs = source(he, pmesh());
74       vertex_descriptor vt = target(he, pmesh());
75       vertex_descriptor v1 = target(h1, pmesh());
76       vertex_descriptor v2 = source(h2, pmesh());
77 
78       return CotangentValue::operator()(vs, v1, vt) + CotangentValue::operator()(vs, v2, vt);
79     }
80   }
81 };
82 
83 template<typename TriangleMesh,
84          typename VertexPointMap,
85          typename VertexConstraintMap,
86          typename SparseLinearSolver,
87          typename GeomTraits>
88 class Shape_smoother
89 {
90   typedef typename GeomTraits::FT                                                 FT;
91   typedef typename GeomTraits::Point_3                                            Point;
92   typedef typename GeomTraits::Vector_3                                           Vector;
93   typedef typename boost::property_traits<VertexPointMap>::reference              Point_ref;
94 
95   typedef CGAL::Triple<std::size_t, std::size_t, double>                          Triplet;
96 
97   typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor           vertex_descriptor;
98   typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor         halfedge_descriptor;
99   typedef typename boost::graph_traits<TriangleMesh>::edge_descriptor             edge_descriptor;
100   typedef typename boost::graph_traits<TriangleMesh>::face_descriptor             face_descriptor;
101 
102   typedef CGAL::dynamic_vertex_property_t<std::size_t>                            Vertex_local_index;
103   typedef typename boost::property_map<TriangleMesh, Vertex_local_index>::type    IndexMap;
104 
105   // linear system
106   typedef typename SparseLinearSolver::Matrix                                     Eigen_matrix;
107   typedef typename SparseLinearSolver::Vector                                     Eigen_vector;
108 
109 public:
Shape_smoother(TriangleMesh & mesh,VertexPointMap & vpmap,VertexConstraintMap & vcmap,const GeomTraits & traits)110   Shape_smoother(TriangleMesh& mesh,
111                  VertexPointMap& vpmap,
112                  VertexConstraintMap& vcmap,
113                  const GeomTraits& traits)
114     :
115       mesh_(mesh),
116       vpmap_(vpmap),
117       vcmap_(vcmap),
118       vimap_(get(Vertex_local_index(), mesh_)),
119       scale_volume_after_smoothing(true),
120       traits_(traits),
121       weight_calculator_(mesh, vpmap)
122   { }
123 
124   template<typename FaceRange>
init_smoothing(const FaceRange & face_range)125   void init_smoothing(const FaceRange& face_range)
126   {
127     set_face_range(face_range);
128 
129     std::size_t id = 0;
130     for(vertex_descriptor v : vertices(mesh_))
131       put(vimap_, v, id++);
132 
133     // vertices that are not in the range or are constrained still need value '1' in D because the RHS is D * X^n
134     diagonal_.assign(vertices(mesh_).size(), 1.);
135     constrained_flags_.assign(vertices(mesh_).size(), false);
136 
137     for(vertex_descriptor v : vertices(mesh_))
138     {
139       if(is_constrained(v))
140       {
141         constrained_flags_[get(vimap_, v)] = true;
142 
143         // scaling things cannot preserve the position of more than a single constrained point
144         if(anchor_point == boost::none)
145           anchor_point = get(vpmap_, v);
146         else
147           scale_volume_after_smoothing = false;
148       }
149     }
150 
151     if(!CGAL::is_closed(mesh_))
152       scale_volume_after_smoothing = false;
153   }
154 
setup_system(Eigen_matrix & A,Eigen_vector & bx,Eigen_vector & by,Eigen_vector & bz,std::vector<Triplet> & stiffness_elements,const double & time)155   void setup_system(Eigen_matrix& A,
156                     Eigen_vector& bx, Eigen_vector& by, Eigen_vector& bz,
157                     std::vector<Triplet>& stiffness_elements,
158                     const double& time)
159   {
160     compute_coefficient_matrix(A, stiffness_elements, time);
161     compute_rhs(bx, by, bz);
162   }
163 
solve_system(const Eigen_matrix & A,Eigen_vector & Xx,Eigen_vector & Xy,Eigen_vector & Xz,const Eigen_vector & bx,const Eigen_vector & by,const Eigen_vector & bz,SparseLinearSolver & solver)164   bool solve_system(const Eigen_matrix& A,
165                     Eigen_vector& Xx, Eigen_vector& Xy, Eigen_vector& Xz,
166                     const Eigen_vector& bx, const Eigen_vector& by, const Eigen_vector& bz,
167                     SparseLinearSolver& solver)
168   {
169     FT D;
170 
171     // calls compute once to factorize with the preconditioner
172     if(!solver.factor(A, D))
173     {
174 #ifdef CGAL_PMP_SMOOTHING_DEBUG
175       std::cerr << "Could not factorize linear system with preconditioner." << std::endl;
176 #endif
177       return false;
178     }
179 
180     if(!solver.linear_solver(bx, Xx) ||
181        !solver.linear_solver(by, Xy) ||
182        !solver.linear_solver(bz, Xz))
183     {
184 #ifdef CGAL_PMP_SMOOTHING_DEBUG
185       std::cerr << "Could not solve linear system." << std::endl;
186 #endif
187       return false;
188     }
189 
190     return true;
191   }
192 
calculate_stiffness_matrix_elements(std::vector<Triplet> & stiffness_elements)193   void calculate_stiffness_matrix_elements(std::vector<Triplet>& stiffness_elements)
194   {
195     CGAL_assertion(stiffness_elements.empty());
196     stiffness_elements.reserve(8 * vrange_.size());
197 
198     std::unordered_map<std::size_t, double> diag_coeff;
199     for(face_descriptor f : frange_)
200     {
201       for(halfedge_descriptor hi : halfedges_around_face(halfedge(f, mesh_), mesh_))
202       {
203         // Get a single canonical non-border halfedge per edge
204         if(is_border(hi, mesh_))
205           continue;
206 
207         const halfedge_descriptor hi_opp = opposite(hi, mesh_);
208         if(!is_border(hi_opp, mesh_) && hi < hi_opp)
209           continue;
210 
211         const vertex_descriptor v_source = source(hi, mesh_);
212         const vertex_descriptor v_target = target(hi, mesh_);
213 
214         const bool is_source_constrained = is_constrained(v_source);
215         const bool is_target_constrained = is_constrained(v_target);
216 
217         if(is_source_constrained && is_target_constrained)
218           continue;
219 
220         const FT Lij = weight_calculator_(hi);
221 
222         const std::size_t i_source = get(vimap_, v_source);
223         const std::size_t i_target = get(vimap_, v_target);
224 
225         // note that these constraints create asymmetry in the matrix
226         if(!is_source_constrained)
227         {
228           stiffness_elements.push_back(Triplet(i_source, i_target, Lij));
229           diag_coeff.insert(std::make_pair(i_source, 0)).first->second -= Lij;
230         }
231 
232         if(!is_target_constrained)
233         {
234           stiffness_elements.push_back(Triplet(i_target, i_source, Lij));
235           diag_coeff.insert(std::make_pair(i_target, 0)).first->second -= Lij;
236         }
237       }
238     }
239 
240     typename std::unordered_map<std::size_t, double>::iterator it = diag_coeff.begin(),
241                                                                end = diag_coeff.end();
242     for(; it!=end; ++it)
243       stiffness_elements.push_back(Triplet(it->first, it->first, it->second));
244   }
245 
update_mesh_no_scaling(const Eigen_vector & Xx,const Eigen_vector & Xy,const Eigen_vector & Xz)246   void update_mesh_no_scaling(const Eigen_vector& Xx, const Eigen_vector& Xy, const Eigen_vector& Xz)
247   {
248     for(vertex_descriptor v : vrange_)
249     {
250       std::size_t index = get(vimap_, v);
251       const FT x_new = Xx[index];
252       const FT y_new = Xy[index];
253       const FT z_new = Xz[index];
254 
255       Point new_pos(x_new, y_new, z_new);
256       put(vpmap_, v, new_pos);
257     }
258   }
259 
update_mesh(const Eigen_vector & Xx,const Eigen_vector & Xy,const Eigen_vector & Xz)260   void update_mesh(const Eigen_vector& Xx, const Eigen_vector& Xy, const Eigen_vector& Xz)
261   {
262     namespace PMP = CGAL::Polygon_mesh_processing;
263 
264     if(!scale_volume_after_smoothing)
265       return update_mesh_no_scaling(Xx, Xy, Xz);
266 
267     const FT old_vol = volume(mesh_, parameters::vertex_point_map(vpmap_).geom_traits(traits_));
268 
269     // If no vertex is constrained, then the smoothed mesh will simply share the same centroid as the input mesh
270     Point pre_smooth_anchor_point;
271     if(anchor_point != boost::none)
272       pre_smooth_anchor_point = *anchor_point;
273     else
274       pre_smooth_anchor_point = PMP::centroid(mesh_, parameters::vertex_point_map(vpmap_).geom_traits(traits_));
275 
276     for(vertex_descriptor v : vrange_)
277     {
278       std::size_t index = get(vimap_, v);
279       const FT x_new = Xx[index];
280       const FT y_new = Xy[index];
281       const FT z_new = Xz[index];
282 
283       Point new_pos(x_new, y_new, z_new);
284       put(vpmap_, v, new_pos);
285     }
286 
287     Point post_smooth_anchor_point;
288     if(anchor_point != boost::none)
289       post_smooth_anchor_point = *anchor_point;
290     else
291       post_smooth_anchor_point = PMP::centroid(mesh_, parameters::vertex_point_map(vpmap_).geom_traits(traits_));
292 
293     const FT new_vol = volume(mesh_, parameters::vertex_point_map(vpmap_));
294     CGAL_assertion(new_vol != 0);
295     const FT inflating_factor = std::cbrt(CGAL::abs(old_vol / new_vol));
296 
297     for(vertex_descriptor v : vertices(mesh_))
298     {
299       Vector d = traits_.construct_vector_3_object()(post_smooth_anchor_point, get(vpmap_, v));
300       Point new_pos = traits_.construct_translated_point_3_object()(pre_smooth_anchor_point,
301                         traits_.construct_scaled_vector_3_object()(d, inflating_factor));
302 
303       put(vpmap_, v, new_pos);
304     }
305   }
306 
307 private:
is_constrained(const vertex_descriptor & v)308   bool is_constrained(const vertex_descriptor& v)
309   {
310     return get(vcmap_, v);
311   }
312 
313   template<typename FaceRange>
set_face_range(const FaceRange & face_range)314   void set_face_range(const FaceRange& face_range)
315   {
316     frange_.assign(face_range.begin(), face_range.end());
317     vrange_.reserve(3 * face_range.size());
318     for(face_descriptor f : face_range)
319     {
320       for(vertex_descriptor v : vertices_around_face(halfedge(f, mesh_), mesh_))
321         vrange_.push_back(v);
322     }
323 
324     // get rid of duplicate vertices
325     std::sort(vrange_.begin(), vrange_.end());
326     vrange_.erase(std::unique(vrange_.begin(), vrange_.end()), vrange_.end());
327   }
328 
compute_coefficient_matrix(Eigen_matrix & A,std::vector<Triplet> & stiffness_elements,const double & time)329   void compute_coefficient_matrix(Eigen_matrix& A,
330                                   std::vector<Triplet>& stiffness_elements,
331                                   const double& time)
332   {
333     fill_mass_matrix();
334 
335     // fill A = Mass - time * Laplacian
336     for(const Triplet& t : stiffness_elements)
337     {
338       std::size_t i = t.get<0>(), j = t.get<1>();
339 
340       if(i != j)
341         A.set_coef(i, j, - time * t.get<2>(), true);
342       else if(!constrained_flags_[i]) // && i==j
343         A.set_coef(i, i, diagonal_[t.get<0>()] - time * t.get<2>(), true);
344     }
345 
346     for(vertex_descriptor v : vrange_)
347     {
348       std::size_t index = get(vimap_, v);
349       if(constrained_flags_[index])
350         A.set_coef(index, index, 1., true);
351     }
352 
353     // we do not call A.assemble_matrix here
354     // Eigen's compute during factorization does the building correctly,
355     // and without assemble_matrix the reference A can be used in the next iterations.
356   }
357 
fill_mass_matrix()358   void fill_mass_matrix()
359   {
360     for(vertex_descriptor v : vrange_)
361     {
362       std::size_t index = get(vimap_, v);
363       if(!is_constrained(v))
364         diagonal_[index] = 0.;
365     }
366 
367     for(face_descriptor f : frange_)
368     {
369       const double area = face_area(f, mesh_, parameters::vertex_point_map(vpmap_).geom_traits(traits_));
370 
371       for(vertex_descriptor v : vertices_around_face(halfedge(f, mesh_), mesh_))
372       {
373         if(!is_constrained(v))
374           diagonal_[get(vimap_, v)] += area / 6.;
375       }
376     }
377   }
378 
compute_rhs(Eigen_vector & bx,Eigen_vector & by,Eigen_vector & bz)379   void compute_rhs(Eigen_vector& bx, Eigen_vector& by, Eigen_vector& bz)
380   {
381     for(vertex_descriptor vi : vertices(mesh_))
382     {
383       std::size_t index = get(vimap_, vi);
384       Point_ref p = get(vpmap_, vi);
385       bx.set(index, diagonal_[index] * p.x());
386       by.set(index, diagonal_[index] * p.y());
387       bz.set(index, diagonal_[index] * p.z());
388     }
389   }
390 
391 private:
392   std::vector<vertex_descriptor> vrange_;
393   std::vector<face_descriptor> frange_;
394   TriangleMesh& mesh_;
395 
396   VertexPointMap vpmap_;
397   VertexConstraintMap vcmap_;
398   IndexMap vimap_;
399 
400   // Smoothing has a tendency to reduce volumes, so we can scale things back up based on the change
401   // of volume. We need an anchor point to scale up, either a constrained point or the centroid
402   // of the initial mesh if no vertex is constrained. If there is more than a constrained vertex,
403   // then no scaling can be done without violating the constraint.
404   bool scale_volume_after_smoothing;
405   boost::optional<Point> anchor_point;
406 
407   // linear system data
408   std::vector<double> diagonal_; // index of vector -> index of vimap_
409   std::vector<bool> constrained_flags_;
410 
411   const GeomTraits& traits_;
412   Edge_cotangent_weight<TriangleMesh, VertexPointMap> weight_calculator_;
413 };
414 
415 } // internal
416 } // PMP
417 } // CGAL
418 
419 #endif // CGAL_POLYGON_MESH_PROCESSING_INTERNAL_CURVATURE_FLOW_NEW_IMPL_H
420