GCC Code Coverage Report


Directory: ./
File: lib/geogram/mesh/mesh_frame_field.cpp
Date: 2026-09-07 02:28:19
Exec Total Coverage
Lines: 0 316 0.0%
Functions: 0 11 0.0%
Branches: 0 302 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2022 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/fr/bruno-levy
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #include <geogram/mesh/mesh_frame_field.h>
41 #include <geogram/mesh/mesh.h>
42 #include <geogram/mesh/mesh_geometry.h>
43 #include <geogram/numerics/matrix_util.h>
44 #include <geogram/basic/logger.h>
45 #include <geogram/basic/line_stream.h>
46 #include <geogram/basic/progress.h>
47 #include <geogram/bibliography/bibliography.h>
48 #include <geogram/NL/nl.h>
49
50 // Some member functions of NormalCycle are not used here.
51 // note: NormalCycle will be exported sometime, so for now
52 // we deactivate the warning.
53
54 #ifdef __clang__
55 #pragma GCC diagnostic ignored "-Wunused-member-function"
56 #endif
57
58 #ifdef __ICC
59 #pragma warning disable 177
60 #endif
61
62 namespace {
63 using namespace GEO;
64
65 /**
66 * \brief We manipulate 4-symmetry direction fields [Ray et.al]
67 */
68 const double symd = 4.0;
69
70 /**
71 * \brief Represents a local orthonormal basis
72 * of a mesh facet.
73 */
74 class MeshFacetBasis {
75 public:
76
77 /**
78 * \brief Constructs a new MeshFacetBasis.
79 * \param[in] M a const reference to the mesh
80 * \param[in] f the index of the facet in \p M
81 */
82 MeshFacetBasis(
83 const Mesh& M, index_t f
84 ) {
85 X = normalize(
86 Geom::mesh_corner_vector(M, M.facets.corners_begin(f))
87 );
88 N = normalize(Geom::mesh_facet_normal(M,f));
89 Y = cross(N,X);
90 }
91
92 /**
93 * \brief Transforms a 3d vector into the local
94 * 2d basis.
95 * \param[in] v the input 3d vector
96 * \return the representation of \p v in the local
97 * 2d basis.
98 */
99 vec2 project(const vec3& v) const {
100 return vec2(dot(v,X),dot(v,Y));
101 }
102
103 /**
104 * \brief Transforms a local 3d vector into the
105 * global 3d basis.
106 * \param[in] v the input 2d vector in the local basis
107 * \return the representation of \p v in the global
108 * 3d basis.
109 */
110 vec3 unproject(const vec2& v) const {
111 return v.x*X + v.y*Y;
112 }
113
114 /**
115 * \brief Computes the angles between a 3d vector and
116 * the first axis of the local basis.
117 * \param[in] v the input vector in the global 3d basis
118 * \return the angle between \p v and the first axis of
119 * the local basis
120 */
121 double angle(const vec3& v) const {
122 vec2 v2=project(v);
123 return atan2(v2.y,v2.x);
124 }
125
126 /**
127 * \brief Computes the rotation angle between the two reference vectors
128 * of two facets that share an edge.
129 * \param[in] M a reference to the mesh
130 * \param[in] c a corner index in \p M
131 * \return the angle between the reference frames of the
132 * two facets sharing the edge originating at \p c1
133 */
134 static double reference_rotation_accross_edge(
135 const Mesh& M, index_t c
136 ) {
137 geo_debug_assert(M.facets.are_simplices());
138 index_t f1 = c/3;
139 index_t f2 = M.facet_corners.adjacent_facet(c);
140 geo_debug_assert(f2 != NO_FACET);
141 vec3 ref = Geom::mesh_corner_vector(M,c);
142 MeshFacetBasis B1(M,f1);
143 MeshFacetBasis B2(M,f2);
144 return B2.angle(ref) - B1.angle(ref);
145 }
146
147 private:
148 vec3 X;
149 vec3 Y;
150 vec3 N;
151 };
152
153
154 /**
155 * \brief Solves for Periodic Global Parameterization variables.
156 * \details This function computes for each facet of the mesh \p M
157 * two variables, that correspond to the cosine and sine of an angle
158 * interpolated over the mesh. This angle is relative to the first
159 * edge of the facet, as defined in the MeshFacetBasis class.
160 * \param[in] M a const reference to the surface mesh
161 * \param[in,out] sincos_alpha a vector of 2*M.facets.nb() variables,
162 * that correspond to the interpolated variables. The initial value
163 * is taken into account in the fitting term if \p fitting is non-zero
164 * \param[in] locked a vector of M.facets.nb() booleans, indicating
165 * whether each facet is locked. The variables that correspond to
166 * a locked facet are unchanged.
167 * \param[in] global_fitting importance of the fitting term with respect to
168 * the initial value of \p sincos_alpha. If zero, no fitting term is
169 * installed
170 * \param[in] local_fitting an optional vector of M.facets.nb() doubles
171 * that specifies for each facet an individual factor that scales
172 * global_fitting
173 */
174 void solve_PGP(
175 const Mesh& M,
176 vector<double>& sincos_alpha,
177 const vector<bool>& locked,
178 double global_fitting,
179 const vector<double>& local_fitting = vector<double>()
180 ) {
181 // Step 0: normalize variables
182 for(index_t f: M.facets) {
183 double c = sincos_alpha[2*f];
184 double s = sincos_alpha[2*f+1];
185 double scale = sqrt(s*s+c*c);
186 if(scale > 1e-30) {
187 sincos_alpha[2*f] = c/scale;
188 sincos_alpha[2*f+1] = s/scale;
189 }
190 }
191
192 // Step 1: Setup the OpenNL solver
193 nlNewContext();
194 nlSolverParameteri(NL_NB_VARIABLES, NLint(2*M.facets.nb()));
195 nlSolverParameteri(NL_LEAST_SQUARES, NL_TRUE);
196 #ifdef GEO_DEBUG
197 nlEnable(NL_VERBOSE);
198 #endif
199 nlEnable(NL_NORMALIZE_ROWS);
200
201 // Step 2: setup the variables
202 nlBegin(NL_SYSTEM);
203 for(index_t f: M.facets) {
204 nlSetVariable(2*f, sincos_alpha[2*f]);
205 nlSetVariable(2*f+1, sincos_alpha[2*f+1]);
206 if(locked.size() != 0 && locked[f]) {
207 nlLockVariable(2*f);
208 nlLockVariable(2*f+1);
209 }
210 }
211
212 nlBegin(NL_MATRIX);
213
214 // Step 3: setup the PGP smoothness term
215 for(index_t f1: M.facets) {
216 for(index_t c1: M.facets.corners(f1)) {
217 index_t f2 = M.facet_corners.adjacent_facet(c1);
218 if(f2 == NO_FACET || f1 < f2) {
219 continue;
220 }
221
222 double angle = -symd*
223 MeshFacetBasis::reference_rotation_accross_edge(
224 M,c1
225 );
226
227 double c = cos(angle);
228 double s = sin(angle);
229
230 nlBegin(NL_ROW);
231 nlCoefficient(2*f1,c);
232 nlCoefficient(2*f1+1,s);
233 nlCoefficient(2*f2,-1.0);
234 nlEnd(NL_ROW);
235
236 nlBegin(NL_ROW);
237 nlCoefficient(2*f1,-s);
238 nlCoefficient(2*f1+1,c);
239 nlCoefficient(2*f2+1,-1.0);
240 nlEnd(NL_ROW);
241 }
242 }
243
244 // Step 4: setup the data fitting term
245 if(global_fitting != 0) {
246 for(index_t f: M.facets) {
247
248 double fitting = global_fitting;
249 if(local_fitting.size() != 0) {
250 fitting *= local_fitting[f];
251 }
252
253 if(fitting == 0.0) {
254 continue;
255 }
256
257 nlRowScaling(fitting);
258 nlBegin(NL_ROW);
259 nlCoefficient(2*f,1.0);
260 nlRightHandSide(sincos_alpha[2*f]);
261 nlEnd(NL_ROW);
262
263 nlRowScaling(fitting);
264 nlBegin(NL_ROW);
265 nlCoefficient(2*f+1,1.0);
266 nlRightHandSide(sincos_alpha[2*f+1]);
267 nlEnd(NL_ROW);
268 }
269 }
270
271 nlEnd(NL_MATRIX);
272 nlEnd(NL_SYSTEM);
273
274 // Step 5: solve the linear system
275 nlSolve() ;
276
277 // Step 6: read the new values of the variables
278 for(index_t f: M.facets) {
279 sincos_alpha[2*f] = nlGetVariable(2*f);
280 sincos_alpha[2*f+1] = nlGetVariable(2*f+1);
281 }
282
283 // Step 7: cleanup memory allocated by OpenNL
284 nlDeleteContext(nlGetCurrent());
285 }
286
287
288 /**
289 * \brief Estimates the curvature tensor using a set
290 * of samples. Each sample is a vector and a dihedral angle.
291 * \details The algorithm is detailed in the following reference:
292 * Restricted Delaunay Triangulation and Normal Cycle,
293 * D. Cohen-Steiner and J.M. Morvan,
294 * SOCG 2003
295 */
296 class NormalCycle {
297 public:
298 /**
299 * \brief Constructs a new NormalCycle.
300 */
301 NormalCycle() {
302 clear();
303 }
304
305 /**
306 * \brief Clears the currently accumulated matrix.
307 */
308 void clear() {
309 for(index_t i=0; i<6; ++i) {
310 M_[i] = 0.0;
311 }
312 }
313
314 /**
315 * \brief Computes the eigenvalues
316 * and eigenvectors of the accumulated tensor.
317 */
318 void compute() {
319 double trace = M_[0] + M_[2] + M_[5] ;
320 double s = 1e-6 * trace ;
321 if (trace==0.0) {
322 s = 1e-6;
323 }
324 M_[0] += s ;
325 M_[2] += s ;
326 M_[5] += s ;
327
328
329 double eigen_vectors[9] ;
330 MatrixUtil::semi_definite_symmetric_eigen(
331 M_, 3, eigen_vectors, eigen_value_
332 ) ;
333
334 axis_[0] = vec3(
335 eigen_vectors[0], eigen_vectors[1], eigen_vectors[2]
336 );
337
338 axis_[1] = vec3(
339 eigen_vectors[3], eigen_vectors[4], eigen_vectors[5]
340 );
341
342 axis_[2] = vec3(
343 eigen_vectors[6], eigen_vectors[7], eigen_vectors[8]
344 );
345
346 // Normalize the eigen vectors
347
348 for(index_t i=0; i<3; ++i) {
349 axis_[i] = normalize(axis_[i]) ;
350 }
351
352 // Sort the eigen vectors
353 i_[0] = 0 ;
354 i_[1] = 1 ;
355 i_[2] = 2 ;
356
357 double l0 = ::fabs(eigen_value_[0]) ;
358 double l1 = ::fabs(eigen_value_[1]) ;
359 double l2 = ::fabs(eigen_value_[2]) ;
360
361 if(l1 > l0) {
362 std::swap(l0 , l1 ) ;
363 std::swap(i_[0], i_[1]) ;
364 }
365
366 if(l2 > l1) {
367 std::swap(l1 , l2 ) ;
368 std::swap(i_[1], i_[2]) ;
369 }
370 if(l1 > l0) {
371 std::swap(l0 , l1 ) ;
372 std::swap(i_[0],i_[1]) ;
373 }
374 }
375
376 /**
377 * \brief Accumulates a dihedral angle to the current
378 * tensor.
379 * \details This function needs to be called between
380 * a begin() \ end() pair. If a geometric clipping
381 * neighborhood is used, the specified edge vector
382 * needs to be clipped by it.
383 * \param[in] edge the supporting edge of the dihedron
384 * \param[in] angle the angle of the dihedron
385 * \param[in] neigh_area the area of the clipped
386 * neighborhood
387 * to store the accumulated tensor.
388 */
389 void accumulate_dihedral_angle(
390 const vec3& edge, double angle, double neigh_area=1.0
391 ) {
392 vec3 e = normalize(edge) ;
393 double s = length(edge) * angle * neigh_area ;
394 M_[0] += s * e.x * e.x;
395 M_[1] += s * e.x * e.y;
396 M_[2] += s * e.y * e.y;
397 M_[3] += s * e.x * e.z;
398 M_[4] += s * e.y * e.z;
399 M_[5] += s * e.z * e.z;
400 }
401
402 /**
403 * \brief Gets an eigenvector by index
404 * \param[in] i the index of the eigenvector (0,1 or 2).
405 * The eigenvectors are sorted by increasing eigenvalue
406 * magnitude.
407 * \return the \p i%-th eigenvector
408 */
409 const vec3& eigen_vector(int i) const {
410 return axis_[i_[i]];
411 }
412
413 /**
414 * \brief Gets an eigenvalue by index
415 * \param[in] i the index of the eigenvalue (0,1 or 2).
416 * The eigenvalues are sorted by increasing eigenvalue
417 * magnitude.
418 * \return the \p i%-th eigenvalue
419 */
420 double eigen_value(int i) const {
421 return eigen_value_[i_[i]];
422 }
423
424 /**
425 * \brief Gets the estimated normal vector.
426 * \return the estimated normal vector
427 */
428 const vec3& N() const {
429 return eigen_vector(2);
430 }
431
432 /**
433 * \brief Gets the estimated direction of maximum curvature.
434 * \return the estimated direction of maximum curvature
435 */
436 const vec3& Kmax() const {
437 return eigen_vector(0);
438 }
439
440 /**
441 * \brief Gets the estimated direction of minimum curvature.
442 * \return the estimated direction of minimum curvature
443 */
444 const vec3& Kmin() const {
445 return eigen_vector(1);
446 }
447
448 /**
449 * \brief Gets the estimated maximum curvature.
450 * \return the estimated maximum curvature
451 */
452 double kmax() const {
453 return eigen_value(0);
454 }
455
456 /**
457 * \brief Gets the estimated minimum curvature.
458 * \return the estimated minimum curvature
459 */
460 double kmin() const {
461 return eigen_value(1);
462 }
463
464 /**
465 * \brief Adds the currently accumulated tensor to a
466 * matrix.
467 * \param[out] M an array of 6 doubles that represents
468 * the tensor to which the current tensor will be added
469 */
470 void add_to_matrix(double* M) const {
471 for(index_t i=0; i<6; ++i) {
472 M[i] += M_[i];
473 }
474 }
475
476 /**
477 * \brief Adds a matrix to the currently accumulated tensor.
478 * \param[in] M an array of 6 doubles that represents the
479 * matrix that should be added to the currently accumulated
480 * tensor.
481 */
482 void add_matrix(const double* M) {
483 for(index_t i=0; i<6; ++i) {
484 M_[i] += M[i];
485 }
486 }
487
488 private:
489 vec3 axis_[3] ;
490 double eigen_value_[3] ;
491 double M_[6] ;
492 int i_[3] ;
493 };
494
495 /**
496 * \brief Estimates the direction of the maximum principal curvature.
497 * \details The direction of maximum principal curvature is encoded
498 * as the cosine and sine of the angle it makes relative to the first
499 * edge of teach triangle, as defined in the MeshFacetBasis class.
500 * \param[in] M a const reference to the surface mesh
501 * \param[in,out] sincos_alpha a vector of 2*M.facets.nb() doubles,
502 * that contains the cosines and sines of the angle between the estimated
503 * directions and the first edge of each facet.
504 * \param[in] locked a vector of M.facets.nb() booleans, that indicates
505 * for each facet whether it is locked. Directions of locked facets are
506 * kept unchanged
507 * \param[out] magnitude a vector of M.facets.nb() doubles that indicates
508 * for each facet the magnitude of the principal direction of curvature
509 */
510 void estimate_max_curvature_direction(
511 const Mesh& M, vector<double>& sincos_alpha, const vector<bool>& locked,
512 vector<double>& magnitude
513 ) {
514 NormalCycle NC;
515 vector<double> matrices(M.vertices.nb()*6,0.0);
516
517 // Compute tensors of vertex neighborhoods
518 for(index_t f1: M.facets) {
519 for(index_t c: M.facets.corners(f1)) {
520 index_t f2 = M.facet_corners.adjacent_facet(c);
521 if(f2 == NO_FACET || f2 < f1) {
522 continue;
523 }
524
525 index_t v1 = M.facet_corners.vertex(c);
526 index_t v2 = M.facet_corners.vertex(c+1);
527
528 vec3 e = Geom::mesh_corner_vector(M,c);
529 double alpha = Geom::mesh_normal_angle(M,c);
530
531 NC.clear();
532 NC.accumulate_dihedral_angle(e,alpha);
533 NC.add_to_matrix(&matrices[6*v1]);
534 NC.add_to_matrix(&matrices[6*v2]);
535 }
536 }
537
538
539 // For each facet, accumulate the tensors of all its
540 // vertices.
541 for(index_t f: M.facets) {
542 if(locked.size() != 0 && locked[f]) {
543 continue;
544 }
545 NC.clear();
546 for(index_t c: M.facets.corners(f)) {
547 index_t v=M.facet_corners.vertex(c);
548 NC.add_matrix(&matrices[6*v]);
549 }
550 NC.compute();
551
552 // Compute eigenvector and encode it as PGP
553 // representation (cosine and sine of the
554 // angle relative to the first edge of the triangle).
555 vec2 K = MeshFacetBasis(M,f).project(
556 NC.Kmax()
557 );
558 double angle = atan2(K.y,K.x)*symd;
559 sincos_alpha[2*f] = cos(angle);
560 sincos_alpha[2*f+1] = sin(angle);
561
562 magnitude[f] = fabs(NC.kmax());
563 }
564 }
565
566
567 }
568
569 namespace GEO {
570
571 bool FrameField::load(
572 const Mesh& M, bool volumetric, const std::string& filename
573 ) {
574 Logger::out("Frames") << "Loading frames from "
575 << filename << std::endl;
576 frames_.clear();
577 frames_.reserve(M.cells.nb() * 9);
578 centers_.clear();
579 bool result = true;
580 bool with_centers = false;
581 try {
582 LineInput in(filename);
583 if(!in.OK()) {
584 return false;
585 }
586 bool first_line = true;
587 while(!in.eof() && in.get_line()) {
588 in.get_fields();
589 if(first_line) {
590 if(in.nb_fields() == 12) {
591 with_centers = true;
592 }
593 first_line=false;
594 }
595 if(!with_centers && in.nb_fields() != 3) {
596 Logger::err("I/O")
597 << "Line " << in.line_number()
598 << ": invalid number of fields (expected 3, got"
599 << in.nb_fields()
600 << ")"
601 << std::endl;
602 result = false;
603 break;
604 }
605 if(with_centers && in.nb_fields() != 12) {
606 Logger::err("I/O")
607 << "Line " << in.line_number()
608 << ": invalid number of fields (expected 12, got"
609 << in.nb_fields()
610 << ")"
611 << std::endl;
612 result = false;
613 break;
614 }
615 if(with_centers) {
616 for(index_t i=0; i<3; ++i) {
617 centers_.push_back(in.field_as_double(i));
618 }
619 for(index_t i=3; i<12; ++i) {
620 frames_.push_back(in.field_as_double(i));
621 }
622 } else {
623 frames_.push_back(in.field_as_double(0));
624 frames_.push_back(in.field_as_double(1));
625 frames_.push_back(in.field_as_double(2));
626 }
627 }
628 }
629 catch(const std::exception& ex) {
630 Logger::err("I/O") << ex.what() << std::endl;
631 result = false;
632 }
633 if(!result) {
634 Logger::err("I/O")
635 << "Could not load file: " << filename
636 << std::endl;
637 return false;
638 }
639 if(!with_centers) {
640 if(volumetric) {
641 if(frames_.size() != M.cells.nb() * 9) {
642 Logger::err("I/O")
643 << "Invalid number of elements in frame for: "
644 << filename << std::endl;
645 return false;
646 }
647 centers_.resize(M.cells.nb() * 3);
648 for(index_t t : M.cells) {
649 vec3 g = Geom::mesh_tet_center(M, t);
650 centers_[3 * t] = g.x;
651 centers_[3 * t + 1] = g.y;
652 centers_[3 * t + 2] = g.z;
653 }
654 } else {
655 if(frames_.size() != M.facets.nb() * 9) {
656 Logger::err("I/O")
657 << "Invalid number of elements in frame for: "
658 << filename << std::endl;
659 return false;
660 }
661 centers_.resize(M.facets.nb() * 3);
662 for(index_t f: M.facets) {
663 vec3 g = Geom::mesh_facet_center(M, f);
664 centers_[3 * f] = g.x;
665 centers_[3 * f + 1] = g.y;
666 centers_[3 * f + 2] = g.z;
667 }
668 }
669 }
670 geo_assert((centers_.size()/3)*3 == centers_.size());
671 geo_assert((frames_.size()/9)*9 == frames_.size());
672
673 index_t nb_vectors = frames_.size()/3;
674 for(index_t i=0; i<nb_vectors; ++i) {
675 double s = 0.0;
676 for(index_t c=0; c<3; ++c) {
677 s += frames_[3*i+c]*frames_[3*i+c];
678 }
679 s = ::sqrt(s);
680 if(s == 0.0) {
681 Logger::warn("Frames")
682 << "Zero-length vector in frame" << std::endl;
683 } else {
684 for(index_t c=0; c<3; ++c) {
685 frames_[3*i+c] /= s;
686 }
687 }
688 }
689
690
691 Logger::out("Frames") << "Loaded " << centers_.size()/3
692 << " frames" << std::endl;
693 Logger::out("Frames") << "Creating NN search" << std::endl;
694 NN_ = NearestNeighborSearch::create(3, "default");
695 NN_->set_points(centers_.size()/3, centers_.data());
696 return true;
697 }
698
699 void FrameField::create_from_surface_mesh(
700 const Mesh& M, bool volumetric, double sharp_angle_threshold
701 ) {
702
703 geo_cite("DBLP:journals/tog/RayVLL08");
704 geo_cite("DBLP:journals/tog/RayVAL09");
705
706 sharp_angle_threshold *= M_PI/180.0 ;
707
708 vector<double> alpha_sincos(2*M.facets.nb(),0.0);
709 vector<bool> locked(M.facets.nb());
710
711 // Step 1: setup the fixed variables
712 index_t nb_constrained = 0;
713
714 for(index_t f1: M.facets) {
715 for(index_t c1: M.facets.corners(f1)) {
716 index_t f2 = M.facet_corners.adjacent_facet(c1);
717 if(
718 f2 == NO_FACET || (
719 ::fabs(Geom::mesh_normal_angle(M,c1)) >
720 sharp_angle_threshold
721 )
722 ) {
723 vec2 v = MeshFacetBasis(M,f1).project(
724 Geom::mesh_corner_vector(M,c1)
725 );
726 double angle = atan2(v.y,v.x)*symd;
727
728 locked[f1]=true;
729 alpha_sincos[2*f1] = cos(angle);
730 alpha_sincos[2*f1+1] = sin(angle);
731
732 ++nb_constrained;
733 }
734 }
735 }
736
737 Logger::out("Frames")
738 << nb_constrained << " constrained edges" << std::endl;
739
740 vector<double> certainty(M.facets.nb());
741 estimate_max_curvature_direction(M,alpha_sincos,locked,certainty);
742 double max_certainty = 0.0;
743 for(index_t f: M.facets) {
744 max_certainty = std::max(max_certainty,certainty[f]);
745 }
746 for(index_t f: M.facets) {
747 certainty[f] /= max_certainty;
748 if(Numeric::is_nan(certainty[f])) {
749 certainty[f] = 0.0;
750 }
751 }
752
753 // Step 2: solve for sines and cosines
754 // (Periodic Global Parameterization)
755 try {
756 ProgressTask progress("Frames Smth.",4);
757 for(index_t k=0; k<5; ++k) {
758 solve_PGP(M,alpha_sincos,locked,1.0,certainty);
759 progress.progress(k);
760 }
761 } catch(const TaskCanceled&) {
762 }
763
764
765 // Step 3: deduce the frame field from
766 // the solution of the linear system
767 frames_.resize(M.facets.nb()*9);
768 centers_.resize(M.facets.nb()*3);
769 for(index_t f: M.facets) {
770 double angle = atan2(
771 alpha_sincos[2*f+1],
772 alpha_sincos[2*f]
773 ) / symd;
774 vec3 U = MeshFacetBasis(M,f).unproject(vec2(cos(angle),sin(angle)));
775 vec3 W = normalize(Geom::mesh_facet_normal(M,f));
776 vec3 V = cross(W,U);
777
778 frames_[9*f+0] = U.x;
779 frames_[9*f+1] = U.y;
780 frames_[9*f+2] = U.z;
781 frames_[9*f+3] = V.x;
782 frames_[9*f+4] = V.y;
783 frames_[9*f+5] = V.z;
784 frames_[9*f+6] = W.x;
785 frames_[9*f+7] = W.y;
786 frames_[9*f+8] = W.z;
787
788 vec3 g = Geom::mesh_facet_center(M,f);
789 centers_[3*f+0] = g.x;
790 centers_[3*f+1] = g.y;
791 centers_[3*f+2] = g.z;
792 }
793
794 if(use_NN_ || volumetric) {
795 NN_ = NearestNeighborSearch::create(3, "default");
796 NN_->set_points(centers_.size()/3, centers_.data());
797 }
798
799 // Step 4: In volumetric mode, for each tet we find the nearest
800 // facet and lookup the frame field from it.
801 if(volumetric) {
802 vector<double> new_frames(9*M.cells.nb());
803 vector<double> new_centers(3*M.cells.nb());
804 for(index_t t: M.cells) {
805 vec3 g = Geom::mesh_tet_center(M,t);
806 get_nearest_frame(g.data(), &new_frames[9*t]);
807 new_centers[3*t+0] = g.x;
808 new_centers[3*t+1] = g.y;
809 new_centers[3*t+2] = g.z;
810 }
811 frames_.swap(new_frames);
812 centers_.swap(new_centers);
813 NN_->set_points(centers_.size()/3, centers_.data());
814 }
815 }
816
817
818
819
820 void FrameField::scale_frame_vector(
821 double* frame, const vec3& N, double s
822 ) {
823 index_t max_index = 0;
824 double max_prod = -1e30;
825 for(index_t i = 0; i < 3; ++i) {
826 double cur_prod =
827 ::fabs(
828 N.x * frame[3 * i] +
829 N.y * frame[3 * i + 1] +
830 N.z * frame[3 * i + 2]
831 );
832 if(cur_prod > max_prod) {
833 max_prod = cur_prod;
834 max_index = i;
835 }
836 }
837 vec3 W = s*normalize(vec3(&frame[3*max_index]));
838 frame[3*max_index ]=W.x;
839 frame[3*max_index+1]=W.y;
840 frame[3*max_index+2]=W.z;
841 }
842
843 void FrameField::fix_frame(double* frame, const vec3& N) {
844 index_t w_index=0;
845 double max_prod = -1e30;
846 for(index_t i=0; i<3; ++i) {
847 double cur_prod =
848 ::fabs(N.x*frame[3*i]+N.y*frame[3*i+1]+N.z*frame[3*i+2]);
849 if(cur_prod > max_prod) {
850 max_prod = cur_prod;
851 w_index = i;
852 }
853 }
854 index_t u_index = (w_index + 1)%3;
855 index_t v_index = (u_index + 1)%3;
856 vec3 U = normalize(vec3(frame+3*u_index));
857 vec3 V = normalize(vec3(frame+3*v_index));
858 vec3 W = normalize(N);
859
860 if(dot(cross(U,V),W) < 0.0) {
861 U = -U;
862 }
863
864 U -= dot(U,W)*W;
865 V -= dot(V,W)*W;
866
867 U = normalize(U);
868 V = normalize(V);
869
870
871 vec3 X = normalize(U+V);
872 vec3 Y = normalize(U-V);
873 U = normalize(X+Y);
874 V = normalize(X-Y);
875
876 frame[0] = U.x;
877 frame[1] = U.y;
878 frame[2] = U.z;
879 frame[3] = V.x;
880 frame[4] = V.y;
881 frame[5] = V.z;
882 frame[6] = W.x;
883 frame[7] = W.y;
884 frame[8] = W.z;
885 }
886
887
888 }
889