GCC Code Coverage Report


Directory: ./
File: parameterization/mesh_PGP_2d.cpp
Date: 2026-09-27 03:22:43
Exec Total Coverage
Lines: 0 394 0.0%
Functions: 0 7 0.0%
Branches: 0 712 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/parameterization/mesh_PGP_2d.h>
41 #include <geogram/mesh/mesh.h>
42 #include <geogram/mesh/mesh_geometry.h>
43 #include <geogram/NL/nl.h>
44 #include <geogram/bibliography/bibliography.h>
45
46 #include <stack>
47
48 namespace {
49 using namespace GEO;
50
51
52 /**
53 * \brief computes gradients in a triangle in 2D.
54 */
55 class ParamTrglGradient {
56 public:
57 ✗ ParamTrglGradient(
58 const vec2& p1, const vec2& p2, const vec2& p3
59 ✗ ) {
60 ✗ vertex_[0] = p1 ;
61 ✗ vertex_[1] = p2 ;
62 ✗ vertex_[2] = p3 ;
63
64 ✗ double x1 = p1.x ;
65 ✗ double y1 = p1.y ;
66 ✗ double x2 = p2.x ;
67 ✗ double y2 = p2.y ;
68 ✗ double x3 = p3.x ;
69 ✗ double y3 = p3.y ;
70
71 ✗ double d = x2*y3 - y2*x3 + x3*y1 - y3*x1 + x1*y2 - y1*x2 ;
72
73 ✗ if(fabs(d) < 1e-10) {
74 ✗ d = 1.0 ;
75 ✗ is_flat_ = true ;
76 } else {
77 ✗ is_flat_ = false ;
78 }
79
80 ✗ TX_[0] = (y2 - y3)/d ;
81 ✗ TX_[1] = (y3 - y1)/d ;
82 ✗ TX_[2] = (y1 - y2)/d ;
83
84 ✗ TY_[0] = -(x2 - x3)/d ;
85 ✗ TY_[1] = -(x3 - x1)/d ;
86 ✗ TY_[2] = -(x1 - x2)/d ;
87 ✗ }
88
89 ✗ double TX(int i) const {
90 ✗ geo_debug_assert(i<3);
91 ✗ return TX_[i];
92 }
93
94 ✗ double TY(int i) const {
95 ✗ geo_debug_assert(i<3);
96 ✗ return TY_[i];
97 }
98
99 ✗ bool is_flat() const {
100 ✗ return is_flat_ ;
101 }
102
103 private:
104 double TX_[3] ;
105 double TY_[3] ;
106 vec2 vertex_[3] ;
107 bool is_flat_ ;
108 } ;
109
110
111 /**
112 * \brief Retrieves a coordinate from an angle computed by PGP.
113 * \param[in] alpha the input variable.
114 * \param[in] ref the reference variable.
115 * \return a number congruent to \p alpha modulo 2 pi in the inverval
116 * [ \p ref - M_PI, \p ref + M_PI]
117 */
118 ✗ double normalize_periodic_variable(
119 double alpha, double ref
120 ) {
121 ✗ int count = 0;
122 ✗ if(Numeric::is_nan(alpha)) {
123 ✗ return 0.0 ;
124 }
125 ✗ if(Numeric::is_nan(ref)) {
126 ✗ return 0.0 ;
127 }
128 ✗ double result = alpha ;
129 ✗ count = 0 ;
130 ✗ while(ref - result > M_PI) {
131 ✗ result += 2.0 * M_PI ;
132 ✗ count ++ ;
133 ✗ if(count > 100) {
134 ✗ return 0.0 ;
135 }
136 }
137 ✗ count = 0 ;
138 ✗ while(result - ref > M_PI) {
139 ✗ result -= 2.0 * M_PI ;
140 ✗ count ++ ;
141 ✗ if(count > 100) {
142 ✗ return 0.0 ;
143 }
144 }
145 ✗ return result ;
146 }
147 }
148
149 namespace GEO {
150
151 namespace GlobalParam2d {
152
153 ✗ void PGP(
154 Mesh* mesh,
155 Attribute<vec3>& B, Attribute<vec2>& U,
156 double scaling, bool constrain_hard_edges,
157 bool use_direct_solver,
158 double maximum_scaling_correction
159 ) {
160 ✗ geo_cite("DBLP:journals/tog/RayLLSA06");
161
162 ✗ bool do_brush = true;
163
164 // We use f = c/3
165 // (could be fixed in the future, using a c2f array...).
166 ✗ geo_assert(mesh->facets.are_simplices());
167
168 // Step 0: Preparation
169
170 ✗ scaling *= 2.0;
171 ✗ scaling *= surface_average_edge_length(*mesh);
172
173 // Step 0.1: Preparation / Brushing
174 ✗ if(do_brush) {
175 ✗ Internal::brush(mesh,B);
176 }
177
178 // Step 0.2: Preparation / Compute relative rotation of B between
179 // pairs of adjacent facets
180 ✗ Attribute<index_t> R_ff(mesh->facet_corners.attributes(),"R");
181 ✗ Internal::compute_R_ff(mesh,B,R_ff);
182 ✗ Attribute<index_t> R_fv(mesh->facet_corners.attributes(),"R_fv");
183 ✗ Internal::compute_R_fv(mesh,R_ff,R_fv);
184
185 ✗ Attribute<double> CC;
186 ✗ if(maximum_scaling_correction != 1.0) {
187 ✗ Logger::out("PGP") << "Computing scaling correction"
188 ✗ << std::endl;
189 ✗ CC.bind(mesh->vertices.attributes(), "CC");
190 ✗ Attribute<vec3> Bv(mesh->vertices.attributes(), "B");
191 ✗ Internal::transfer_B_to_vertices(mesh, B, Bv, R_fv);
192 ✗ curl_correction(
193 mesh, Bv, R_fv, CC,
194 use_direct_solver, maximum_scaling_correction
195 );
196 ✗ }
197
198
199 ✗ Logger::out("PGP") << "Solving for PGP" << std::endl;
200
201
202 // Step 1: Determine the structure of the problem:
203 // - There are four variables per vertex (cu, su, cv, sv)
204 // - Each vertex has a reference corner (v2c[v])
205 // - Each corner c knows the number of rotations Rc[c] required
206 // to make the B of its triangle match the B of the triangle
207 // of the reference corner attached to its vertex (clear
208 // enough ?)
209
210 // Rot[ R_ff[c] ][i][j] corresponds to the transform to
211 // be applied to the variables associated with a
212 // vertex to express their coordinates in the frame
213 // of corner c (it corresponds to the same Rot[][][]
214 // matrices as QuadCover, with the exception that each
215 // coefficient 1 is replaced with the 2x2 identity matrix
216 // and each coefficient -1 with Mat2x2(1,0,0,-1) (there is
217 // only one "-1" coefficient because cos(-x) = cos(x) !!).
218 // Note: in the PGP paper, the wrong coefficient is negated.
219
220 static const double Rot[4][4][4] = {
221 {{ 1, 0, 0, 0},
222 { 0, 1, 0, 0},
223 { 0, 0, 1, 0},
224 { 0, 0, 0, 1}},
225
226 {{ 0, 0, 1, 0},
227 { 0, 0, 0, 1},
228 { 1, 0, 0, 0},
229 { 0,-1, 0, 0}},
230
231 {{ 1, 0, 0, 0},
232 { 0,-1, 0, 0},
233 { 0, 0, 1, 0},
234 { 0, 0, 0,-1}},
235
236 {{ 0, 0, 1, 0},
237 { 0, 0, 0,-1},
238 { 1, 0, 0, 0},
239 { 0, 1, 0, 0}}
240 };
241
242 static double Rotc2[4][4];
243
244 // Step 2: setup and solve linear system
245 {
246
247 ✗ nlNewContext();
248
249 ✗ nlSolverParameteri(NL_LEAST_SQUARES, NL_TRUE);
250 ✗ nlSolverParameteri(
251 ✗ NL_NB_VARIABLES, NLint(mesh->vertices.nb()*4)
252 );
253
254 ✗ if(use_direct_solver) {
255 ✗ if(nlInitExtension("CHOLMOD")) {
256 ✗ nlSolverParameteri(NL_SOLVER, NL_CHOLMOD_EXT);
257 ✗ } else if(nlInitExtension("SUPERLU")) {
258 ✗ nlSolverParameteri(NL_SOLVER, NL_PERM_SUPERLU_EXT);
259 } else {
260 ✗ Logger::warn("PGP")
261 ✗ << "Could not initialize direct sovlver"
262 ✗ << std::endl;
263 ✗ Logger::warn("PGP")
264 ✗ << "Falling back to Jacobi pre-CG"
265 ✗ << std::endl;
266 ✗ use_direct_solver = false;
267 }
268 }
269
270 ✗ if(!use_direct_solver) {
271 // With the iterative solver, a very small threshold is
272 // needed, because of the very high scaling on the
273 // solution due to the varying modulus of the complex
274 // numbers far away from the constrained point.
275 ✗ nlSolverParameterd(NL_THRESHOLD, 1e-20);
276 }
277
278 ✗ nlBegin(NL_SYSTEM);
279
280 ✗ if(constrain_hard_edges) {
281 ✗ for(index_t c: mesh->facet_corners) {
282 index_t cnstr =
283 ✗ Internal::get_edge_constraints(mesh, c, B);
284 ✗ index_t v = mesh->facet_corners.vertex(c);
285 // Inverse R, because when the axis turns
286 // clockwise, coordinates turn anticlockwise.
287 ✗ index_t Rcc = Internal::inverse_R(R_fv[c]);
288 ✗ if(cnstr & Internal::CNSTR_U) {
289 ✗ if(Rcc == 0 || Rcc == 2) {
290 ✗ nlLockVariable(4*v);
291 ✗ nlSetVariable(4*v,1e4);
292 ✗ nlLockVariable(4*v+1);
293 ✗ nlSetVariable(4*v+1,0.0);
294 } else {
295 ✗ nlLockVariable(4*v+2);
296 ✗ nlSetVariable(4*v+2,1e4);
297 ✗ nlLockVariable(4*v+3);
298 ✗ nlSetVariable(4*v+3,0.0);
299 }
300 }
301 ✗ if(cnstr & Internal::CNSTR_V) {
302 ✗ if(Rcc == 0 || Rcc == 2) {
303 ✗ nlLockVariable(4*v+2);
304 ✗ nlSetVariable(4*v+2,1e4);
305 ✗ nlLockVariable(4*v+3);
306 ✗ nlSetVariable(4*v+3,0.0);
307 } else {
308 ✗ nlLockVariable(4*v);
309 ✗ nlSetVariable(4*v,1e4);
310 ✗ nlLockVariable(4*v+1);
311 ✗ nlSetVariable(4*v+1,0.0);
312 }
313 }
314 }
315 }
316
317 // Lock at least one variable per connected component.
318 {
319 ✗ std::vector<bool> f_visited(mesh->facets.nb(),false);
320 ✗ for(index_t f: mesh->facets) {
321 ✗ if(!f_visited[f]) {
322 ✗ index_t nb_locked=0;
323 ✗ index_t first_v = mesh->facets.vertex(f,0);
324 ✗ std::stack<index_t> S;
325 ✗ f_visited[f] = true;
326 ✗ S.push(f);
327 ✗ while(!S.empty()) {
328 ✗ index_t f_top = S.top();
329 ✗ S.pop();
330 ✗ FOR(le,mesh->facets.nb_vertices(f_top)) {
331 ✗ index_t v = mesh->facets.vertex(f_top,le);
332 ✗ if(
333 ✗ nlVariableIsLocked(4*v) ||
334 ✗ nlVariableIsLocked(4*v+2)) {
335 ✗ ++nb_locked;
336 }
337 index_t f_neigh =
338 ✗ mesh->facets.adjacent(f_top,le);
339
340 ✗ if(f_neigh != NO_FACET &&
341 ✗ !f_visited[f_neigh]
342 ) {
343 ✗ f_visited[f_neigh] = true;
344 ✗ S.push(f_neigh);
345 }
346 }
347 }
348
349 // Lock one of the points in each
350 // connected component to make sure that
351 // the minimum is well defined.
352 ✗ if(nb_locked == 0) {
353 ✗ nlLockVariable(4*first_v);
354 ✗ nlSetVariable(4*first_v,1e4);
355
356 ✗ nlLockVariable(4*first_v+1);
357 ✗ nlSetVariable(4*first_v+1,0.0);
358
359 ✗ nlLockVariable(4*first_v+2);
360 ✗ nlSetVariable(4*first_v+2,1e4);
361
362 ✗ nlLockVariable(4*first_v+3);
363 ✗ nlSetVariable(4*first_v+3,0.0);
364 }
365 ✗ }
366 }
367 ✗ }
368
369 ✗ nlBegin(NL_MATRIX);
370
371 // This one will be replaced in-place
372 // with the rotation that encodes the
373 // delta u and delta v along each edge.
374
375 double RotDelta[4][4];
376 ✗ FOR(i,4) {
377 ✗ FOR(j,4) {
378 ✗ RotDelta[i][j] = ((i==j) ? 1.0 : 0.0);
379 }
380 }
381
382 ✗ for(index_t f: mesh->facets) {
383
384 ✗ vec3 Bf, BTf;
385
386 ✗ for(index_t c1=mesh->facets.corners_begin(f);
387 ✗ c1 < mesh->facets.corners_end(f); ++c1) {
388
389 ✗ Internal::get_B_on_edge(mesh, B, R_ff, f, c1, Bf, BTf);
390
391 index_t c2 =
392 ✗ mesh->facets.next_corner_around_facet(f,c1);
393 ✗ index_t v1 = mesh->facet_corners.vertex(c1);
394 ✗ index_t v2 = mesh->facet_corners.vertex(c2);
395
396 vec3 E =
397 ✗ mesh->vertices.point(v2) -
398 ✗ mesh->vertices.point(v1) ;
399 ✗ double delta_u = 2.0 * M_PI * dot(E,Bf)/scaling;
400 ✗ double delta_v = 2.0 * M_PI * dot(E,BTf)/scaling;
401
402 ✗ if(CC.is_bound()) {
403 ✗ double s = (0.5*(CC[v1] + CC[v2]));
404 ✗ delta_u /= s;
405 ✗ delta_v /= s;
406 }
407
408 ✗ double sdu = sin(delta_u);
409 ✗ double cdu = cos(delta_u);
410 ✗ double sdv = sin(delta_v);
411 ✗ double cdv = cos(delta_v);
412
413 ✗ RotDelta[0][0] = cdu;
414 ✗ RotDelta[0][1] = -sdu;
415 ✗ RotDelta[1][0] = sdu;
416 ✗ RotDelta[1][1] = cdu;
417
418 ✗ RotDelta[2][2] = cdv;
419 ✗ RotDelta[2][3] = -sdv;
420 ✗ RotDelta[3][2] = sdv;
421 ✗ RotDelta[3][3] = cdv;
422
423 // Inverse R, because when the axis turns
424 // clockwise, coordinates turn anticlockwise.
425 ✗ index_t Rc1 = Internal::inverse_R(R_fv[c1]);
426 ✗ index_t Rc2 = Internal::inverse_R(R_fv[c2]);
427
428 // Compute the product of the "delta u, delta v"
429 // rotation with the Rc2 "90 degrees rotation" matrix,
430 // exactly like in the PGP article.
431
432 ✗ for(index_t i=0; i<4; ++i) {
433 ✗ for(index_t j=0; j<4; ++j) {
434 ✗ Rotc2[i][j] = 0.0;
435 ✗ for(index_t k=0; k<4; ++k) {
436 ✗ Rotc2[i][j] +=
437 ✗ RotDelta[i][k] * Rot[Rc2][k][j];
438 }
439 }
440 }
441
442 ✗ for(index_t i=0; i<4; ++i) {
443 ✗ nlBegin(NL_ROW);
444 ✗ for(index_t j=0; j<4; ++j) {
445 ✗ double a1 = Rot[Rc1][i][j];
446 ✗ if(a1 != 0.0) {
447 ✗ nlCoefficient(v1*4+j, a1);
448 }
449 }
450 ✗ for(index_t j=0; j<4; ++j) {
451 ✗ double a2 = Rotc2[i][j];
452 ✗ if(a2 != 0.0) {
453 ✗ nlCoefficient(v2*4+j, -a2);
454 }
455 }
456 ✗ nlEnd(NL_ROW);
457 }
458 }
459 }
460
461 ✗ nlEnd(NL_MATRIX);
462 ✗ nlEnd(NL_SYSTEM);
463
464 ✗ nlSolve();
465
466 ✗ Attribute<double> PGP;
467 ✗ PGP.bind_if_is_defined(mesh->facet_corners.attributes(),"PGP");
468 ✗ if(!PGP.is_bound()) {
469 ✗ PGP.create_vector_attribute(
470 ✗ mesh->facet_corners.attributes(), "PGP", 4
471 );
472 }
473
474 ✗ for(index_t c: mesh->facet_corners) {
475 ✗ index_t v = mesh->facet_corners.vertex(c);
476 // Inverse R, because when the axis turns
477 // clockwise, coordinates turn anticlockwise.
478 ✗ index_t Rcc = Internal::inverse_R(R_fv[c]);
479 double vars[4];
480 ✗ for(index_t i=0; i<4; ++i) {
481 ✗ vars[i] = 0.0;
482 ✗ for(index_t j=0; j<4; ++j) {
483 ✗ vars[i] += Rot[Rcc][i][j] * nlGetVariable(4*v+j);
484 }
485 }
486 ✗ double s = sqrt(vars[0]*vars[0]+vars[1]*vars[1]);
487 ✗ vars[0] /= s;
488 ✗ vars[1] /= s;
489
490 ✗ s = sqrt(vars[2]*vars[2]+vars[3]*vars[3]);
491 ✗ vars[2] /= s;
492 ✗ vars[3] /= s;
493
494 ✗ U[c].x = atan2(vars[1], vars[0]);
495 ✗ U[c].y = atan2(vars[3], vars[2]);
496
497 ✗ FOR(i,4) {
498 ✗ PGP[4*c+i] = vars[i];
499 }
500 }
501
502 Attribute<index_t> singular(
503 ✗ mesh->facets.attributes(),"is_singular"
504 ✗ );
505
506 ✗ for(index_t f: mesh->facets) {
507 ✗ singular[f] = false;
508 ✗ for(index_t c1=mesh->facets.corners_begin(f);
509 ✗ c1<mesh->facets.corners_end(f); ++c1) {
510 index_t c2 =
511 ✗ mesh->facets.next_corner_around_facet(f,c1);
512
513 ✗ index_t v1 = mesh->facet_corners.vertex(c1);
514 ✗ index_t v2 = mesh->facet_corners.vertex(c2);
515 vec3 E =
516 ✗ mesh->vertices.point(v2) -
517 ✗ mesh->vertices.point(v1);
518
519 ✗ vec3 Bf, BTf;
520 ✗ Internal::get_B_on_edge(mesh, B, R_ff, f, c1, Bf, BTf);
521
522 ✗ double delta_u = 2.0 * M_PI * dot(E,Bf)/scaling;
523 ✗ double delta_v = 2.0 * M_PI * dot(E,BTf)/scaling;
524
525 ✗ if(CC.is_bound()) {
526 ✗ double s = (0.5*(CC[v1] + CC[v2]));
527 ✗ delta_u /= s;
528 ✗ delta_v /= s;
529 }
530
531 ✗ double expected_u = U[c1].x-delta_u;
532 ✗ double expected_v = U[c1].y-delta_v;
533 vec2 uv(
534 ✗ normalize_periodic_variable(U[c2].x,expected_u),
535 ✗ normalize_periodic_variable(U[c2].y,expected_v)
536 ✗ );
537 ✗ if(c2 == mesh->facets.corners_begin(f)) {
538 ✗ singular[f] =
539 ✗ (length(uv - U[c2]) > 1e-20) ? 1 : 0;
540 } else {
541 ✗ U[c2] = uv;
542 }
543
544 }
545 }
546
547 ✗ for(index_t c: mesh->facet_corners) {
548 ✗ U[c].x /= M_PI;
549 ✗ U[c].y /= M_PI;
550 ✗ Internal::snap_tex_coord(U[c].x);
551 ✗ Internal::snap_tex_coord(U[c].y);
552 }
553
554 ✗ nlDeleteContext(nlGetCurrent());
555 ✗ }
556
557
558 ✗ }
559
560
561 ✗ void curl_correction(
562 Mesh* mesh, Attribute<vec3>& Bv,
563 Attribute<index_t>& R_fv, Attribute<double>& CC,
564 bool use_direct_solver, double max_scaling_correction
565 ) {
566 ✗ geo_assert(Bv.manager() == &mesh->vertices.attributes());
567 ✗ nlNewContext();
568
569 ✗ nlSolverParameteri(NL_LEAST_SQUARES, NL_TRUE);
570 ✗ nlSolverParameteri(
571 ✗ NL_NB_VARIABLES, NLint(mesh->vertices.nb()*4)
572 );
573
574 ✗ if(use_direct_solver) {
575 ✗ if(nlInitExtension("CHOLMOD")) {
576 ✗ nlSolverParameteri(NL_SOLVER, NL_CHOLMOD_EXT);
577 ✗ } else if(nlInitExtension("SUPERLU")) {
578 ✗ nlSolverParameteri(NL_SOLVER, NL_PERM_SUPERLU_EXT);
579 } else {
580 ✗ Logger::warn("PGP")
581 ✗ << "Could not initialize direct solver"
582 ✗ << std::endl;
583 ✗ Logger::warn("PGP")
584 ✗ << "Falling back to Jacobi pre-CG"
585 ✗ << std::endl;
586 ✗ use_direct_solver = false;
587 }
588 }
589
590 ✗ if(!use_direct_solver) {
591 // With the iterative solver, a very small threshold is
592 // needed, because of the very high scaling on the
593 // solution due to the varying modulus of the complex
594 // numbers far away from the constrained point.
595 ✗ nlSolverParameterd(NL_THRESHOLD, 1e-20);
596 }
597
598 ✗ const double locked_value = 1.0;
599 ✗ const double solver_scale = 1e3;
600
601 ✗ nlBegin(NL_SYSTEM);
602 ✗ nlLockVariable(0u);
603 ✗ nlSetVariable(0u,locked_value);
604 ✗ nlBegin(NL_MATRIX);
605 ✗ for(index_t f: mesh->facets) {
606
607 ✗ index_t va = mesh->facets.vertex(f,0);
608 ✗ index_t vb = mesh->facets.vertex(f,1);
609 ✗ index_t vc = mesh->facets.vertex(f,2);
610
611 ✗ vec3 N = normalize(Geom::mesh_facet_normal(*mesh,f));
612
613 ✗ vec3 fieldA3d = normalize(Bv[va]);
614 ✗ vec3 fieldB3d = normalize(Bv[vb]);
615 ✗ vec3 fieldC3d = normalize(Bv[vc]);
616
617 ✗ FOR(i, Internal::inverse_R(R_fv[f*3])) {
618 ✗ fieldA3d = cross(N,fieldA3d);
619 }
620
621 ✗ FOR(i, Internal::inverse_R(R_fv[f*3+1])) {
622 ✗ fieldB3d = cross(N,fieldB3d);
623 }
624
625 ✗ FOR(i, Internal::inverse_R(R_fv[f*3+2])) {
626 ✗ fieldC3d = cross(N,fieldC3d);
627 }
628
629 ✗ vec3 U = normalize(cross(cross(N, fieldA3d),N));
630 ✗ vec3 V = normalize(cross(N, U));
631
632
633 // fieldx is the 2d field at point x. The fields are
634 // rotated in a coherent way to take the modulus into
635 // account notice that it will be better if the field
636 // is rotated to the facet instead of being projected
637 ✗ vec2 fieldA(1.0,0.0);
638 ✗ vec2 fieldB(dot(fieldB3d, U), dot(fieldB3d, V)) ;
639 ✗ fieldB = normalize(fieldB);
640
641 ✗ vec2 fieldC (dot(fieldC3d, U), dot(fieldC3d, V)) ;
642 ✗ fieldC = normalize(fieldC);
643
644 ✗ vec3 A3d = mesh->vertices.point(va);
645 ✗ vec3 B3d = mesh->vertices.point(vb);
646 ✗ vec3 C3d = mesh->vertices.point(vc);
647
648 ✗ vec3 AB3d = B3d-A3d;
649 ✗ vec3 AC3d = C3d-A3d;
650
651 ✗ vec2 A(0.0,0.0);
652 ✗ vec2 B(dot(AB3d,U), dot(AB3d,V));
653 ✗ vec2 C(dot(AC3d,U), dot(AC3d,V));
654
655 // hummm... flat triangles ?
656 ✗ double a=0;
657 ✗ double b=0;
658 ✗ ParamTrglGradient trg(A,B,C);
659 ✗ if (trg.is_flat() ||
660 ✗ length(cross(AB3d,AC3d))<1e-10 ||
661 ✗ ::fabs(fieldB.x) < 1e-20 ||
662 ✗ ::fabs(fieldC.x) < 1e-20
663 ) {
664 ✗ std::cerr<<"bad triangle ..." << std::endl ;
665
666 ✗ nlBegin(NL_ROW);
667 ✗ nlCoefficient(va,1e-2) ;
668 ✗ nlCoefficient(vb,-1e-2) ;
669 ✗ nlEnd(NL_ROW);
670
671 ✗ nlBegin(NL_ROW);
672 ✗ nlCoefficient(va,1e-2) ;
673 ✗ nlCoefficient(vc,-1e-2) ;
674 ✗ nlEnd(NL_ROW);
675
676 ✗ nlBegin(NL_ROW);
677 ✗ nlCoefficient(vc,1e-2) ;
678 ✗ nlCoefficient(vb,-1e-2) ;
679 ✗ nlEnd(NL_ROW);
680 ✗ continue ;
681 } else {
682
683 // the direction field is represented by angles
684 // double alpha_A = 0;
685 // double alpha_B = atan(fieldB.y/fieldB.x);
686 // double alpha_C = atan(fieldC.y/fieldC.x);
687
688 // Order 1 Taylor Expansion ....
689 ✗ double alpha_A = 0;
690 ✗ double alpha_B = fieldB.y/fieldB.x;
691 ✗ double alpha_C = fieldC.y/fieldC.x;
692
693 vec2 grad_alpha (
694 ✗ trg.TX(0)*alpha_A +
695 ✗ trg.TX(1)*alpha_B +
696 ✗ trg.TX(2)*alpha_C ,
697 ✗ trg.TY(0)*alpha_A +
698 ✗ trg.TY(1)*alpha_B +
699 ✗ trg.TY(2)*alpha_C
700 ✗ );
701 ✗ a = grad_alpha.x;
702 ✗ b = grad_alpha.y;
703 }
704
705 ✗ double sqrt_area = ::sqrt(Geom::mesh_facet_area(*mesh,f)) ;
706
707 ✗ if(
708 ✗ Numeric::is_nan(sqrt_area) ||
709 ✗ Numeric::is_nan(trg.TX(0)) ||
710 ✗ Numeric::is_nan(trg.TX(1)) ||
711 ✗ Numeric::is_nan(trg.TX(2)) ||
712 ✗ Numeric::is_nan(trg.TY(0)) ||
713 ✗ Numeric::is_nan(trg.TY(1)) ||
714 ✗ Numeric::is_nan(trg.TY(2)) ||
715 ✗ Numeric::is_nan(a) ||
716 ✗ Numeric::is_nan(b)
717 ) {
718 ✗ std::cerr << "Found NAN !!" << std::endl ;
719 }
720
721
722 ✗ nlRowScaling(sqrt_area) ;
723 ✗ nlBegin(NL_ROW);
724 ✗ nlCoefficient(va, trg.TX(0)) ;
725 ✗ nlCoefficient(vb, trg.TX(1)) ;
726 ✗ nlCoefficient(vc, trg.TX(2)) ;
727 ✗ nlRightHandSide(solver_scale*b);
728 ✗ nlEnd(NL_ROW);
729
730 ✗ nlRowScaling(sqrt_area) ;
731 ✗ nlBegin(NL_ROW);
732 ✗ nlCoefficient(va, trg.TY(0)) ;
733 ✗ nlCoefficient(vb, trg.TY(1)) ;
734 ✗ nlCoefficient(vc, trg.TY(2)) ;
735 ✗ nlRightHandSide(solver_scale*-a);
736 ✗ nlEnd(NL_ROW);
737 }
738 ✗ nlEnd(NL_MATRIX);
739 ✗ nlEnd(NL_SYSTEM);
740
741 ✗ nlSolve();
742 ✗ double min_val = Numeric::max_float64();
743 ✗ double max_val = Numeric::min_float64();
744 ✗ for(index_t v: mesh->vertices) {
745 ✗ CC[v] = ::exp(
746 ✗ (nlGetVariable(v)-locked_value)/solver_scale)
747 ;
748 ✗ min_val = std::min(min_val,CC[v]);
749 ✗ max_val = std::max(max_val,CC[v]);
750 }
751 ✗ double avg_val = 0.5 * (min_val + max_val);
752 ✗ double min_limit = 1.0 / ::sqrt(max_scaling_correction);
753 ✗ double max_limit = ::sqrt(max_scaling_correction);
754 ✗ for(index_t v: mesh->vertices) {
755 ✗ CC[v] = CC[v] / avg_val;
756 ✗ geo_clamp(CC[v], min_limit, max_limit);
757 }
758 ✗ nlDeleteContext(nlGetCurrent());
759 ✗ }
760
761 } // namespace GlobalParam2d
762 } // namespace OGF
763