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