GCC Code Coverage Report


Directory: ./
File: lib/geogram/parameterization/mesh_PGP_2d.cpp
Date: 2026-09-07 02:28:19
Exec Total Coverage
Lines: 0 301 0.0%
Functions: 0 4 0.0%
Branches: 0 344 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