1 // Gmsh - Copyright (C) 1997-2020 C. Geuzaine, J.-F. Remacle
2 //
3 // See the LICENSE.txt file for license information. Please report all
4 // issues on https://gitlab.onelab.info/gmsh/gmsh/issues.
5 //
6 // Author: Maxence Reberol
7 
8 #include "qmtMeshGeometryOptimization.h"
9 
10 /* System includes */
11 #include <queue>
12 
13 /* Gmsh includes */
14 #include "GmshMessage.h"
15 #include "OS.h"
16 #include "GVertex.h"
17 #include "GEdge.h"
18 #include "GFace.h"
19 #include "GModel.h"
20 #include "MVertex.h"
21 #include "MLine.h"
22 #include "MElement.h"
23 #include "MTriangle.h"
24 #include "MQuadrangle.h"
25 #include "robin_hood.h"
26 #include "meshOctreeLibOL.h"
27 #include "Context.h"
28 #include "gmsh.h" // api
29 
30 /* QuadMeshingTools includes */
31 #include "cppUtils.h"
32 #include "qmtMeshUtils.h"
33 #include "arrayGeometry.h"
34 #include "geolog.h"
35 
36 /* WinslowUntangler includes */
37 #if defined(HAVE_WINSLOWUNTANGLER)
38 #include "winslowUntangler.h"
39 #include "meshSurfaceUntangling.h"
40 #endif
41 
42 #if defined(HAVE_EIGEN)
43 #include <Eigen/SparseLU>
44 #include <Eigen/IterativeLinearSolvers>
45 #endif
46 
47 constexpr bool DBG_VIZU_K = false;
48 constexpr bool DBG_VIZU_G = false;
49 constexpr bool DBG_VIZU_U = false;
50 
51 using namespace CppUtils;
52 using namespace ArrayGeometry;
53 
54 using std::vector;
55 using vec4 = std::array<double, 4>;
56 using vec5 = std::array<double, 5>;
57 
58 template <typename Key, typename T, typename Hash = robin_hood::hash<Key>,
59           typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
60 using unordered_map =
61   robin_hood::detail::Table<true, MaxLoadFactor100, Key, T, Hash, KeyEqual>;
62 
63 template <typename Key, typename Hash = robin_hood::hash<Key>,
64           typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
65 using unordered_set =
66   robin_hood::detail::Table<true, MaxLoadFactor100, Key, void, Hash, KeyEqual>;
67 
68 namespace QMT {
69   constexpr size_t NO_SIZE_T = (size_t)-1;
70   constexpr uint32_t NO_U32 = (uint32_t)-1;
71 
72   bool SHOW_QUALITY = false; /* Debug only */
73 
kernelWinslowSpecialStencil(const vec3 ptsStencil[8],vec3 & newPos)74   inline bool kernelWinslowSpecialStencil(const vec3 ptsStencil[8],
75                                           vec3 &newPos)
76   {
77     /* Stencil:
78      *   6---1---4
79      *   |   |   |
80      *   2--- ---0
81      *   |   |   |
82      *   7---3---5
83      */
84     /* warning: 2D stencil but 3D coordinates */
85     const double hx = 1.;
86     const double hy = 1.;
87 
88     /* 1. Compute the winslow coefficients (alpha_i, beta_i in the Karman paper)
89      */
90     /*    a. Compute first order derivatives of the position */
91     vec3 r_i[2];
92     r_i[0] = 1. / hx * (ptsStencil[0] - ptsStencil[2]);
93     r_i[1] = 1. / hy * (ptsStencil[1] - ptsStencil[3]);
94     /*    b. Compute the alpha_i coefficients */
95     const double alpha_0 = dot(r_i[1], r_i[1]);
96     const double alpha_1 = dot(r_i[0], r_i[0]);
97     /*    c. Compute the beta coefficient */
98     const double beta = dot(r_i[0], r_i[1]);
99 
100     /* cross derivative */
101     const vec3 u_xy =
102       1. / (4. * hx * hy) *
103       (ptsStencil[4] + ptsStencil[7] - ptsStencil[6] - ptsStencil[5]);
104 
105     /* 2. Compute the "winslow new position" */
106     const double denom = 2. * alpha_0 / (hx * hx) + 2. * alpha_1 / (hy * hy);
107     if(std::abs(denom) < 1.e-18) return false;
108     newPos = 1. / denom *
109              (alpha_0 / (hx * hx) * (ptsStencil[0] + ptsStencil[2]) +
110               alpha_1 / (hy * hy) * (ptsStencil[1] + ptsStencil[3]) -
111               2. * beta * u_xy);
112     return true;
113   }
114 
kernelWinslow(const std::array<vec3,8> & stencil,vec3 & newPos)115   inline bool kernelWinslow(const std::array<vec3, 8> &stencil, vec3 &newPos)
116   {
117     /* Continuous ordering in input stencil */
118     const std::array<uint32_t, 8> o2n = {0, 2, 4, 6, 1, 7, 3, 5};
119     const std::array<vec3, 8> winStencil = {
120       stencil[o2n[0]], stencil[o2n[1]], stencil[o2n[2]], stencil[o2n[3]],
121       stencil[o2n[4]], stencil[o2n[5]], stencil[o2n[6]], stencil[o2n[7]]};
122     return kernelWinslowSpecialStencil(winStencil.data(), newPos);
123   }
124 
kernelLaplacian(const std::vector<vec3> & points,vec3 & newPos)125   bool kernelLaplacian(const std::vector<vec3> &points, vec3 &newPos)
126   {
127     const size_t N = points.size();
128     if(N == 0) return false;
129     newPos = {0., 0., 0.};
130     for(size_t i = 0; i < N; ++i) { newPos = newPos + points[i]; }
131     newPos = 1. / double(N) * newPos;
132     return true;
133   }
134 
kernelAngleBased(const vec3 & center,const std::vector<vec3> & points,vec3 & newPos)135   bool kernelAngleBased(const vec3 &center, const std::vector<vec3> &points,
136                         vec3 &newPos)
137   {
138     const size_t N = points.size();
139     std::vector<vec3> rotated(N);
140     std::vector<double> angles(N);
141     double sum_angle = 0.;
142     for(size_t i = 0; i < N; ++i) {
143       const vec3 &prev = points[(N + i - 1) % N];
144       const vec3 &cur = points[i];
145       const vec3 &next = points[(i + 1) % N];
146       vec3 oldDir = (center - cur);
147       double len = length(oldDir);
148       if(len == 0.) return false;
149       vec3 d1 = prev - cur;
150       if(length2(d1) == 0.) return false;
151       vec3 d2 = next - cur;
152       if(length2(d2) == 0.) return false;
153       normalize(d1);
154       normalize(d2);
155       vec3 newDir = d1 + d2;
156       if(length2(newDir) == 0.) return false;
157       normalize(newDir);
158       if(dot(newDir, oldDir) < 0.) { newDir = -1. * newDir; }
159       rotated[i] = cur + len * newDir;
160       normalize(oldDir);
161       double agl = angleVectorsAlreadyNormalized(newDir, oldDir);
162       angles[i] = agl;
163       sum_angle += agl;
164     }
165     if(sum_angle == 0.) return false;
166     newPos.data()[0] = 0.;
167     newPos.data()[1] = 0.;
168     newPos.data()[2] = 0.;
169     for(size_t i = 0; i < N; ++i) {
170       double w = angles[i] / sum_angle;
171       // double w = 1./double(N);
172       newPos = newPos + w * rotated[i];
173     }
174     return true;
175   }
176 
177   std::array<vec3, 8>
fillStencilRegular(size_t v,const std::vector<vec3> & points,const std::vector<size_t> & one_ring_first,const std::vector<uint32_t> & one_ring_values)178   fillStencilRegular(size_t v, const std::vector<vec3> &points,
179                      const std::vector<size_t> &one_ring_first,
180                      const std::vector<uint32_t> &one_ring_values)
181   {
182     return std::array<vec3, 8>{points[one_ring_values[one_ring_first[v] + 0]],
183                                points[one_ring_values[one_ring_first[v] + 1]],
184                                points[one_ring_values[one_ring_first[v] + 2]],
185                                points[one_ring_values[one_ring_first[v] + 3]],
186                                points[one_ring_values[one_ring_first[v] + 4]],
187                                points[one_ring_values[one_ring_first[v] + 5]],
188                                points[one_ring_values[one_ring_first[v] + 6]],
189                                points[one_ring_values[one_ring_first[v] + 7]]};
190   }
191 
fillStencilIrregular(size_t v,const std::vector<vec3> & points,const std::vector<size_t> & one_ring_first,const std::vector<uint32_t> & one_ring_values,std::vector<vec3> & stencil,bool oneOverTwo=false)192   void fillStencilIrregular(size_t v, const std::vector<vec3> &points,
193                             const std::vector<size_t> &one_ring_first,
194                             const std::vector<uint32_t> &one_ring_values,
195                             std::vector<vec3> &stencil, bool oneOverTwo = false)
196   {
197     if(oneOverTwo) {
198       size_t n = (one_ring_first[v + 1] - one_ring_first[v]) / 2;
199       stencil.resize(n);
200       for(size_t i = 0; i < stencil.size(); ++i) {
201         stencil[i] = points[one_ring_values[one_ring_first[v] + 2 * i]];
202       }
203     }
204     else {
205       stencil.resize(one_ring_first[v + 1] - one_ring_first[v]);
206       for(size_t i = 0; i < stencil.size(); ++i) {
207         stencil[i] = points[one_ring_values[one_ring_first[v] + i]];
208       }
209     }
210   }
211 
stencilAverageLength(const std::array<vec3,8> & stencil)212   double stencilAverageLength(const std::array<vec3, 8> &stencil)
213   {
214     double avg = 0.;
215     const uint32_t N = stencil.size();
216     for(uint32_t i = 0; i < N; ++i) {
217       const vec3 &p0 = stencil[i];
218       const vec3 &p1 = stencil[(i + 1) % N];
219       avg += length(p1 - p0);
220     }
221     avg /= double(N);
222     return avg;
223   }
224 
stencilAverageLength(const std::vector<vec3> & stencil)225   double stencilAverageLength(const std::vector<vec3> &stencil)
226   {
227     double avg = 0.;
228     const uint32_t N = stencil.size();
229     for(uint32_t i = 0; i < N; ++i) {
230       const vec3 &p0 = stencil[i];
231       const vec3 &p1 = stencil[(i + 1) % N];
232       avg += length(p1 - p0);
233     }
234     avg /= double(N);
235     return avg;
236   }
237 
238   /* p0, p1, p2, p3: the four (ordered and oriented) corners
239    * Quad normal reference computed from the corners. Element should not
240    * be inverted or too tangled */
quad_shape_ln(const vec3 & p0,const vec3 & p1,const vec3 & p2,const vec3 & p3)241   inline double quad_shape_ln(const vec3 &p0, const vec3 &p1, const vec3 &p2,
242                               const vec3 &p3)
243   {
244     /* Based on Sandia Verdict document */
245     constexpr double EPS = 1.e-16;
246     constexpr double EPS2 = EPS * EPS;
247     const vec3 L0 = p1 - p0;
248     const vec3 L1 = p2 - p1;
249     const vec3 L2 = p3 - p2;
250     const vec3 L3 = p0 - p3;
251     const vec3 X1 = L0 - L2;
252     const vec3 X2 = L1 - L3;
253     vec3 Nc = cross(X1, X2);
254     const double lenNc_sq = length2(Nc);
255     if(lenNc_sq < EPS2) return -1.;
256     normalize(Nc);
257     const double len0_sq = length2(L0);
258     const double len1_sq = length2(L1);
259     const double len2_sq = length2(L2);
260     const double len3_sq = length2(L3);
261     if(len0_sq < EPS2 || len1_sq < EPS2 || len2_sq < EPS2 || len3_sq < EPS2)
262       return 0.;
263     const vec3 N0 = cross(L3, L0);
264     const vec3 N1 = cross(L0, L1);
265     const vec3 N2 = cross(L1, L2);
266     const vec3 N3 = cross(L2, L3);
267     const double a0 = dot(Nc, N0); /* bad if non planar quad ? */
268     const double a1 = dot(Nc, N1);
269     const double a2 = dot(Nc, N2);
270     const double a3 = dot(Nc, N3);
271     const double q0 = a0 / (len3_sq + len0_sq);
272     const double q1 = a1 / (len0_sq + len1_sq);
273     const double q2 = a2 / (len1_sq + len2_sq);
274     const double q3 = a3 / (len2_sq + len3_sq);
275     if(SHOW_QUALITY && (q0 < 0 || q1 < 0 || q2 < 0 || q3 < 0)) {
276       vec3 mid = (p0 + p1 + p2 + p3) * 0.25;
277       DBG(q0, q1, q2, q3);
278       GeoLog::add(mid, Nc, "Nc");
279       GeoLog::add(p0, N0, "Ni");
280       GeoLog::add(p1, N1, "Ni");
281       GeoLog::add(p2, N2, "Ni");
282       GeoLog::add(p3, N3, "Ni");
283       GeoLog::add({p0, p1, p2, p3}, 0., "quad");
284       GeoLog::flush();
285       gmsh::fltk::run();
286       abort();
287     }
288     return 2. * std::min(std::min(q0, q1), std::min(q2, q3));
289   }
290 
stencilQualitySICNmin(const vec3 & center,const std::array<vec3,8> & stencil,double breakIfBelowThreshold=-DBL_MAX)291   inline double stencilQualitySICNmin(const vec3 &center,
292                                       const std::array<vec3, 8> &stencil,
293                                       double breakIfBelowThreshold = -DBL_MAX)
294   {
295     double qmin = DBL_MAX;
296     constexpr uint32_t N = 4;
297     for(uint32_t i = 0; i < N; ++i) {
298       const vec3 &p0 = stencil[2 * i + 0];
299       const vec3 &p1 = stencil[2 * i + 1];
300       const size_t i2 = (2 * i + 2) % (2 * N);
301       const vec3 &p2 = stencil[i2];
302       const double q = quad_shape_ln(p0, p1, p2, center);
303       qmin = std::min(q, qmin);
304       if(qmin < breakIfBelowThreshold) return qmin;
305     }
306     return qmin;
307   }
308 
stencilQualitySICNmin(const vec3 & center,const std::vector<vec3> & stencil,double breakIfBelowThreshold=-DBL_MAX)309   inline double stencilQualitySICNmin(const vec3 &center,
310                                       const std::vector<vec3> &stencil,
311                                       double breakIfBelowThreshold = -DBL_MAX)
312   {
313     if(stencil.size() % 2 != 0) return -DBL_MAX;
314     double qmin = DBL_MAX;
315     const uint32_t N = stencil.size() / 2;
316     for(uint32_t i = 0; i < N; ++i) {
317       const vec3 &p0 = stencil[2 * i + 0];
318       const vec3 &p1 = stencil[2 * i + 1];
319       const size_t i2 = (2 * i + 2) % (2 * N);
320       const vec3 &p2 = stencil[i2];
321       const double q = quad_shape_ln(p0, p1, p2, center);
322       qmin = std::min(q, qmin);
323       if(qmin < breakIfBelowThreshold) return qmin;
324     }
325     return qmin;
326   }
327 
328   inline double
stencilQualitySICNminGmsh(const vec3 & center,const std::array<vec3,8> & stencil,double breakIfBelowThreshold=-DBL_MAX)329   stencilQualitySICNminGmsh(const vec3 &center,
330                             const std::array<vec3, 8> &stencil,
331                             double breakIfBelowThreshold = -DBL_MAX)
332   {
333     double qmin = DBL_MAX;
334     constexpr uint32_t N = 4;
335     for(uint32_t i = 0; i < N; ++i) {
336       const vec3 &p0 = stencil[2 * i + 0];
337       const vec3 &p1 = stencil[2 * i + 1];
338       const size_t i2 = (2 * i + 2) % (2 * N);
339       const vec3 &p2 = stencil[i2];
340       MVertex a(p0[0], p0[1], p0[2]);
341       MVertex b(p1[0], p1[1], p1[2]);
342       MVertex c(p2[0], p2[1], p2[2]);
343       MVertex d(center[0], center[1], center[2]);
344       MQuadrangle quad(&a, &b, &c, &d);
345       double q = quad.minSICNShapeMeasure();
346       if(std::isnan(q)) q = -1.;
347       qmin = std::min(q, qmin);
348       if(qmin < breakIfBelowThreshold) return qmin;
349     }
350     return qmin;
351   }
352 
353   inline double
stencilQualitySICNminGmsh(const vec3 & center,const std::vector<vec3> & stencil,double breakIfBelowThreshold=-DBL_MAX)354   stencilQualitySICNminGmsh(const vec3 &center,
355                             const std::vector<vec3> &stencil,
356                             double breakIfBelowThreshold = -DBL_MAX)
357   {
358     if(stencil.size() % 2 != 0) return -DBL_MAX;
359     double qmin = DBL_MAX;
360     const uint32_t N = stencil.size() / 2;
361     for(uint32_t i = 0; i < N; ++i) {
362       const vec3 &p0 = stencil[2 * i + 0];
363       const vec3 &p1 = stencil[2 * i + 1];
364       const size_t i2 = (2 * i + 2) % (2 * N);
365       const vec3 &p2 = stencil[i2];
366       MVertex a(p0[0], p0[1], p0[2]);
367       MVertex b(p1[0], p1[1], p1[2]);
368       MVertex c(p2[0], p2[1], p2[2]);
369       MVertex d(center[0], center[1], center[2]);
370       MQuadrangle quad(&a, &b, &c, &d);
371       double q = quad.minSICNShapeMeasure();
372       qmin = std::min(q, qmin);
373       if(qmin < breakIfBelowThreshold) return qmin;
374     }
375     return qmin;
376   }
377 
378   struct OneRing {
379     vec5 *p_uv = NULL;
380     uint32_t n = 0;
381     vec2 jump = {{0., 0.}};
382     vec3 range = {{0., 0., 0.}};
383   };
384 
buildCondensedStructure(const std::vector<MVertex * > & freeVertices,const std::vector<MElement * > & elements,std::unordered_map<MVertex *,size_t> & old2new,std::vector<vector<size_t>> & v2v)385   bool buildCondensedStructure(const std::vector<MVertex *> &freeVertices,
386                                const std::vector<MElement *> &elements,
387                                std::unordered_map<MVertex *, size_t> &old2new,
388                                std::vector<vector<size_t> > &v2v)
389   {
390     /* Build the old2new mapping */
391     size_t vcount = 0;
392     for(MVertex *v : freeVertices) {
393       old2new[v] = vcount;
394       vcount += 1;
395     }
396     size_t nInterior = vcount;
397     if(nInterior == 0) return true; /* nothing to smooth */
398     v2v.resize(nInterior);
399     for(MElement *f : elements) {
400       for(size_t le = 0; le < 4; ++le) {
401         MVertex *vs[2] = {f->getVertex(le), f->getVertex((le + 1) % 4)};
402         size_t nvs[2];
403         for(size_t lv = 0; lv < 2; ++lv) {
404           MVertex *v = vs[lv];
405           size_t nv = NO_SIZE_T;
406           auto it = old2new.find(v);
407           if(it == old2new.end()) {
408             old2new[v] = vcount;
409             nv = vcount;
410             vcount += 1;
411           }
412           else {
413             nv = it->second;
414           }
415           nvs[lv] = nv;
416         }
417         if(nvs[0] < nInterior) v2v[old2new[vs[0]]].push_back(old2new[vs[1]]);
418         if(nvs[1] < nInterior) v2v[old2new[vs[1]]].push_back(old2new[vs[0]]);
419       }
420       constexpr bool addDiags = false;
421       if(addDiags) {
422         for(size_t d = 0; d < 2; ++d) {
423           MVertex *vs[2] = {f->getVertex(d), f->getVertex((d + 2) % 4)};
424           size_t nvs[2] = {old2new[vs[0]], old2new[vs[1]]};
425           if(nvs[0] < nInterior) v2v[old2new[vs[0]]].push_back(old2new[vs[1]]);
426           if(nvs[1] < nInterior) v2v[old2new[vs[1]]].push_back(old2new[vs[0]]);
427         }
428       }
429     }
430     return true;
431   }
432 
buildCondensedStructure(const std::vector<MElement * > & elements,const std::vector<MVertex * > & freeVertices,std::unordered_map<MVertex *,uint32_t> & old2new,std::vector<MVertex * > & new2old,std::vector<std::array<uint32_t,4>> & quads,std::vector<std::vector<uint32_t>> & v2q,std::vector<std::vector<uint32_t>> & oneRings,std::vector<std::array<double,3>> & points)433   bool buildCondensedStructure(const std::vector<MElement *> &elements,
434                                const std::vector<MVertex *> &freeVertices,
435                                std::unordered_map<MVertex *, uint32_t> &old2new,
436                                std::vector<MVertex *> &new2old,
437                                std::vector<std::array<uint32_t, 4> > &quads,
438                                std::vector<std::vector<uint32_t> > &v2q,
439                                std::vector<std::vector<uint32_t> > &oneRings,
440                                std::vector<std::array<double, 3> > &points)
441   {
442     new2old.reserve(2 * freeVertices.size());
443     v2q.reserve(2 * freeVertices.size());
444     points.reserve(2 * freeVertices.size());
445 
446     size_t vcount = 0;
447     for(MVertex *v : freeVertices) {
448       old2new[v] = vcount;
449       vec3 pt = SVector3(v->point());
450       points.push_back(pt);
451       new2old.push_back(v);
452       vcount += 1;
453     }
454 
455     v2q.resize(vcount);
456     quads.clear();
457     quads.resize(elements.size(), {NO_U32, NO_U32, NO_U32, NO_U32});
458     for(size_t f = 0; f < elements.size(); ++f) {
459       MElement *q = elements[f];
460       if(q->getNumVertices() > 4) {
461         Msg::Error(
462           "buildCondensedStructure: element with %li vertices not supported",
463           q->getNumVertices());
464         return false;
465       }
466       for(size_t lv = 0; lv < q->getNumVertices(); ++lv) {
467         MVertex *v = q->getVertex(lv);
468         auto it = old2new.find(v);
469         size_t nv;
470         if(it == old2new.end()) {
471           old2new[v] = vcount;
472           new2old.push_back(v);
473           nv = vcount;
474           vcount += 1;
475           vec3 pt = SVector3(v->point());
476           points.push_back(pt);
477           if(nv >= v2q.size()) { v2q.resize(nv + 1); }
478         }
479         else {
480           nv = it->second;
481         }
482         quads[f][lv] = nv;
483         v2q[nv].push_back(f);
484       }
485     }
486     points.shrink_to_fit();
487     quads.shrink_to_fit();
488     new2old.shrink_to_fit();
489     v2q.shrink_to_fit();
490 
491     /* Build the one rings for the free vertices */
492     oneRings.resize(freeVertices.size());
493     vector<MElement *> adjElts;
494     for(size_t v = 0; v < freeVertices.size(); ++v) {
495       adjElts.resize(v2q[v].size());
496       for(size_t lq = 0; lq < v2q[v].size(); ++lq) {
497         adjElts[lq] = elements[v2q[v][lq]];
498       }
499       std::vector<MVertex *> bnd;
500       bool okb = buildBoundary(adjElts, bnd);
501       if(!okb) {
502         Msg::Warning(
503           "buildCondensedStructure: failed to build boundary for stencil");
504         return false;
505       }
506       if(bnd.back() == bnd.front()) bnd.pop_back();
507 
508       /* Be sure the first vertex on the boundary is edge-connected to v */
509       /* Start of the stencil */
510       MVertex *vp = freeVertices[v];
511       MVertex *v0 = NULL;
512       for(MElement *e : adjElts) {
513         size_t N = e->getNumVertices();
514         for(size_t j = 0; j < N; ++j) {
515           MVertex *a = e->getVertex(j);
516           MVertex *b = e->getVertex((j + 1) % N);
517           if(a == vp) {
518             v0 = b;
519             break;
520           }
521           else if(b == vp) {
522             v0 = a;
523             break;
524           }
525         }
526         if(v0 != NULL) break;
527       }
528       if(v0 == NULL) {
529         Msg::Warning("buildCondensedStructure: failed to found v0");
530         return false;
531       }
532       for(size_t j = 0; j < bnd.size(); ++j) {
533         if(bnd[j] == v0 && j > 0) {
534           std::rotate(bnd.begin(), bnd.begin() + j, bnd.end());
535           break;
536         }
537       }
538       if(bnd.front() != v0) {
539         Msg::Warning("buildCondensedStructure: wrong start (bnd size: %li)",
540                      bnd.size());
541         return false;
542       }
543 
544       if(bnd.size() < 3) {
545         Msg::Warning("buildCondensedStructure: wrong boundary, size %li",
546                      bnd.size());
547         return false;
548       }
549 
550       oneRings[v].resize(bnd.size());
551       for(size_t j = 0; j < bnd.size(); ++j) {
552         auto it = old2new.find(bnd[j]);
553         if(it != old2new.end()) { oneRings[v][j] = it->second; }
554         else {
555           Msg::Error("buildCondensedStructure: vertex not found in old2new");
556           return false;
557         }
558       }
559     }
560 
561     return true;
562   }
563 
buildUVSmoothingDataStructures(GFace * gf,const std::vector<MElement * > & elements,const std::vector<MVertex * > & freeVertices,std::vector<vec5> & point_uv,std::vector<size_t> & one_ring_first,std::vector<uint32_t> & one_ring_values,std::vector<MVertex * > & new2old)564   bool buildUVSmoothingDataStructures(
565     GFace *gf, const std::vector<MElement *> &elements,
566     const std::vector<MVertex *> &freeVertices, std::vector<vec5> &point_uv,
567     std::vector<size_t> &one_ring_first, std::vector<uint32_t> &one_ring_values,
568     std::vector<MVertex *> &new2old)
569   {
570     std::unordered_map<MVertex *, uint32_t> old2new;
571     std::vector<std::array<uint32_t, 4> > quads;
572     std::vector<std::vector<uint32_t> > v2q;
573     std::vector<std::vector<uint32_t> > oneRings;
574     std::vector<std::array<double, 3> > points;
575     bool okc = buildCondensedStructure(elements, freeVertices, old2new, new2old,
576                                        quads, v2q, oneRings, points);
577     if(!okc) {
578       Msg::Warning(
579         "buildCondensedStructure: failed to build condensed representation");
580       return false;
581     }
582 
583     /* Get associated uv in GFace */
584     std::vector<std::array<double, 2> > uvs(old2new.size(), {DBL_MAX, DBL_MAX});
585     for(size_t i = 0; i < elements.size(); ++i) {
586       std::vector<SPoint2> quad_uvs = paramOnElement(gf, elements[i]);
587       for(size_t lv = 0; lv < 4; ++lv) {
588         size_t v = quads[i][lv];
589         if(v == NO_U32) continue;
590         if(uvs[v][0] == DBL_MAX) {
591           uvs[v][0] = quad_uvs[lv][0];
592           uvs[v][1] = quad_uvs[lv][1];
593         }
594       }
595     }
596 
597     /* Compact 3D + uv */
598     point_uv.resize(points.size());
599     for(size_t i = 0; i < points.size(); ++i) {
600       point_uv[i][0] = points[i][0];
601       point_uv[i][1] = points[i][1];
602       point_uv[i][2] = points[i][2];
603       point_uv[i][3] = uvs[i][0];
604       point_uv[i][4] = uvs[i][1];
605     }
606 
607     /* One ring adjacencies in contiguous memory */
608     compress(oneRings, one_ring_first, one_ring_values);
609 
610     return true;
611   }
612 
getContinuousUVOnLoop(GFace * gf,const std::vector<MVertex * > & bndOrdered,std::vector<SPoint2> & bndUvs)613   bool getContinuousUVOnLoop(GFace *gf,
614                              const std::vector<MVertex *> &bndOrdered,
615                              std::vector<SPoint2> &bndUvs)
616   {
617     if(bndOrdered.size() < 3) return false;
618 
619     /* If periodic parametrization, get periods */
620     double Ts[2] = {0., 0.};
621     if(gf->periodic(0)) Ts[0] = gf->period(0);
622     if(gf->periodic(1)) Ts[1] = gf->period(1);
623 
624     /* Get start point on the loop, inside GFace if possible */
625     size_t i0 = 0;
626     for(size_t i = 0; i < bndOrdered.size(); ++i) {
627       MVertex *v = bndOrdered[i];
628       if(v->onWhat() == gf) {
629         i0 = i;
630         break;
631       }
632     }
633 
634     /* Get all the parameters on the loop */
635     bndUvs.resize(bndOrdered.size());
636     reparamMeshVertexOnFace(bndOrdered[i0], gf, bndUvs[i0], true);
637     SPoint2 prevUV = bndUvs[i0];
638 
639     double gapMax[2] = {0., 0.};
640     const size_t N = bndOrdered.size();
641     for(size_t k = 1; k < N; ++k) {
642       size_t i = (i0 + k) % N;
643       MVertex *v = bndOrdered[i];
644       bool okr = reparamMeshVertexOnFaceWithRef(gf, v, prevUV, bndUvs[i]);
645       if(!okr) return false;
646 
647       gapMax[0] =
648         std::max(gapMax[0], std::abs(bndUvs[i].data()[0] - prevUV.data()[0]));
649       gapMax[1] =
650         std::max(gapMax[1], std::abs(bndUvs[i].data()[1] - prevUV.data()[1]));
651       prevUV = bndUvs[i];
652     }
653     gapMax[0] = std::max(
654       gapMax[0], std::abs(bndUvs.front().data()[0] - bndUvs.back().data()[0]));
655     gapMax[1] = std::max(
656       gapMax[1], std::abs(bndUvs.front().data()[1] - bndUvs.back().data()[1]));
657 
658     if(Ts[0] > 0 && gapMax[0] > 0.5 * Ts[0]) {
659       Msg::Debug(
660         "getContinuousUVOnLoop: reject because gap on boundary: %f (period %f)",
661         gapMax[0], Ts[0]);
662       return false;
663     }
664     if(Ts[1] > 0 && gapMax[1] > 0.5 * Ts[1]) {
665       Msg::Debug(
666         "getContinuousUVOnLoop: reject because gap on boundary: %f (period %f)",
667         gapMax[1], Ts[1]);
668       return false;
669     }
670 
671     return true;
672   }
673 
674   bool
getPlanarParametrization(const GFaceMeshPatch & patch,std::unordered_map<MVertex *,SPoint2> & vertexParam)675   getPlanarParametrization(const GFaceMeshPatch &patch,
676                            std::unordered_map<MVertex *, SPoint2> &vertexParam)
677   {
678     if(patch.intVertices.size() == 0) return false;
679     GFace *gf = patch.gf;
680     if(!gf->haveParametrization()) return false;
681     vertexParam.clear();
682 
683     /* If periodic parametrization, get periods */
684     double Ts[2] = {0., 0.};
685     if(gf->periodic(0)) Ts[0] = gf->period(0);
686     if(gf->periodic(1)) Ts[1] = gf->period(1);
687 
688     std::unordered_map<MVertex *, std::vector<MVertex *> > v2v;
689     buildVertexToVertexMap(patch.elements, v2v);
690     MVertex *v0 = patch.intVertices[0];
691 
692     std::queue<MVertex *> Q;
693     Q.push(v0);
694     SPoint2 uv0(0., 0.);
695     v0->getParameter(0, uv0.data()[0]);
696     v0->getParameter(1, uv0.data()[1]);
697     vertexParam[v0] = uv0;
698     while(Q.size() > 0) {
699       MVertex *v = Q.front();
700       Q.pop();
701       SPoint2 uv = vertexParam[v];
702       for(MVertex *v2 : v2v[v]) {
703         auto it = vertexParam.find(v2);
704         if(it != vertexParam.end()) {
705           if(Ts[0] != 0. || Ts[1] != 0.) {
706             /* Check jump */
707             SPoint2 uv2 = it->second;
708             if(Ts[0] != 0. && std::abs(uv.x() - uv2.x()) > 0.5 * Ts[0])
709               return false;
710             if(Ts[1] != 0. && std::abs(uv.y() - uv2.y()) > 0.5 * Ts[1])
711               return false;
712           }
713           continue;
714         }
715         SPoint2 uv2(0., 0.);
716         bool okr = reparamMeshVertexOnFaceWithRef(patch.gf, v2, uv, uv2);
717         if(!okr) {
718           Msg::Debug("getPlanarParametrization | failed to reparam v2");
719           return false;
720         }
721         vertexParam[v2] = uv2;
722         Q.push(v2);
723       }
724     }
725 
726     return true;
727   }
728 
solveLaplaceLinearSystem(size_t nInterior,const vector<vector<size_t>> & v2v,vector<SPoint2> & uvs)729   bool solveLaplaceLinearSystem(size_t nInterior,
730                                 const vector<vector<size_t> > &v2v,
731                                 vector<SPoint2> &uvs)
732   {
733     if(nInterior > 100) {
734       Msg::Debug("... solve laplace linear system (%li unknowns) ...",
735                  nInterior);
736     }
737 
738 #if defined(HAVE_EIGEN)
739     size_t N = uvs.size();
740     Eigen::VectorXd x_u(N), x_v(N), b_u(N), b_v(N);
741     Eigen::SparseMatrix<double> A(N, N);
742     b_u.fill(0.);
743     b_v.fill(0.);
744     double PENALTY = 1.e8;
745 
746     std::vector<Eigen::Triplet<double, size_t> > triplets;
747     for(size_t v = 0; v < uvs.size(); ++v) {
748       if(v < nInterior) {
749         triplets.push_back({v, v, 1.});
750         if(v2v[v].size() == 0) continue;
751         double sum = double(v2v[v].size());
752         for(size_t v2 : v2v[v]) { triplets.push_back({v, v2, -1. / sum}); }
753       }
754       else { /* fixed value */
755         triplets.push_back({v, v, PENALTY});
756         b_u[v] = PENALTY * uvs[v][0];
757         b_v[v] = PENALTY * uvs[v][1];
758       }
759     }
760     A.setFromTriplets(triplets.begin(), triplets.end());
761 
762     bool solveOk = true;
763     { /* Try SparseLU */
764       Eigen::SparseLU<Eigen::SparseMatrix<double> > solver;
765       solver.analyzePattern(A);
766       solver.factorize(A);
767       x_u = solver.solve(b_u);
768       if(solver.info() != Eigen::ComputationInfo::Success) {
769         Msg::Warning(
770           "failed to solve linear system with SparseLU (%li variables)", N);
771         solveOk = false;
772       }
773       x_v = solver.solve(b_v);
774       if(solver.info() != Eigen::ComputationInfo::Success) {
775         Msg::Warning(
776           "failed to solve linear system with SparseLU (%li variables)", N);
777         solveOk = false;
778       }
779     }
780     if(!solveOk) { /* Try least square */
781       solveOk = true;
782       Eigen::LeastSquaresConjugateGradient<Eigen::SparseMatrix<double> > solver;
783       solver.compute(A);
784       x_u = solver.solve(b_u);
785       if(solver.info() != Eigen::ComputationInfo::Success) {
786         Msg::Warning(
787           "failed to solve linear system with least-square (%li variables)", N);
788         solveOk = false;
789       }
790       x_v = solver.solve(b_v);
791       if(solver.info() != Eigen::ComputationInfo::Success) {
792         Msg::Warning(
793           "failed to solve linear system with least-square (%li variables)", N);
794         solveOk = false;
795       }
796     }
797 
798     for(size_t v = 0; v < nInterior; ++v) {
799       uvs[v][0] = x_u[v];
800       uvs[v][1] = x_v[v];
801     }
802     if(!solveOk) {
803       Msg::Error("failed to solve linear system to solve uv");
804       return false;
805     }
806 
807 #else
808     Msg::Error("solveLaplaceLinearSystem requires the EIGEN module");
809     return -1;
810 #endif
811     return true;
812   }
813 
814   /* p0, p1, p2, p3: the four (ordered and oriented) corners
815    * N: reference normal (normalized) */
quad_shape(const vec3 & p0,const vec3 & p1,const vec3 & p2,const vec3 & p3,const vec3 & N)816   inline double quad_shape(const vec3 &p0, const vec3 &p1, const vec3 &p2,
817                            const vec3 &p3, const vec3 &N)
818   {
819     /* Based on Sandia Verdict document */
820     constexpr double EPS = 1.e-16;
821     constexpr double EPS2 = EPS * EPS;
822     const vec3 L0 = p1 - p0;
823     const vec3 L1 = p2 - p1;
824     const vec3 L2 = p3 - p2;
825     const vec3 L3 = p0 - p3;
826     const double len0_sq = length2(L0);
827     const double len1_sq = length2(L1);
828     const double len2_sq = length2(L2);
829     const double len3_sq = length2(L3);
830     if(len0_sq < EPS2 || len1_sq < EPS2 || len2_sq < EPS2 || len3_sq < EPS2)
831       return -1.;
832     const vec3 N0 = cross(L3, L0);
833     const vec3 N1 = cross(L0, L1);
834     const vec3 N2 = cross(L1, L2);
835     const vec3 N3 = cross(L2, L3);
836     const double a0 = dot(N, N0); /* bad if non planar quad ? */
837     const double a1 = dot(N, N1);
838     const double a2 = dot(N, N2);
839     const double a3 = dot(N, N3);
840     const double q0 = a0 / (len3_sq + len0_sq);
841     const double q1 = a1 / (len0_sq + len1_sq);
842     const double q2 = a2 / (len1_sq + len2_sq);
843     const double q3 = a3 / (len2_sq + len3_sq);
844     if(SHOW_QUALITY && (q0 < 0 || q1 < 0 || q2 < 0 || q3 < 0)) {
845       DBG("------");
846       DBG(len0_sq, len1_sq, len2_sq, len3_sq);
847       DBG(N);
848       DBG(N0, N1, N2, N3);
849       DBG(q0, q1, q2, q3);
850       vec3 mid = (p0 + p1 + p2 + p3) * 0.25;
851       GeoLog::add(mid, N, "Ni");
852       GeoLog::add(p0, N0, "Ni");
853       GeoLog::add(p1, N1, "Ni");
854       GeoLog::add(p2, N2, "Ni");
855       GeoLog::add(p3, N3, "Ni");
856       GeoLog::add({p0, p1, p2, p3}, 0., "quad");
857       GeoLog::flush();
858       gmsh::fltk::run();
859       abort();
860     }
861     return 2. * std::min(std::min(q0, q1), std::min(q2, q3));
862   }
863 
864   inline double
computeQualityQuadOneRing(const vec5 & v,const OneRing & ring,const vec3 & normal,double breakIfBelowThreshold=-DBL_MAX)865   computeQualityQuadOneRing(const vec5 &v, const OneRing &ring,
866                             const vec3 &normal,
867                             double breakIfBelowThreshold = -DBL_MAX)
868   {
869     if(ring.n % 2 != 0) return -DBL_MAX;
870     double qmin = DBL_MAX;
871     const vec3 p = {v[0], v[1], v[2]};
872     for(uint32_t i = 0; i < ring.n / 2; ++i) {
873       const vec3 p0 = {ring.p_uv[2 * i + 0][0], ring.p_uv[2 * i + 0][1],
874                        ring.p_uv[2 * i + 0][2]};
875       const vec3 p1 = {ring.p_uv[2 * i + 1][0], ring.p_uv[2 * i + 1][1],
876                        ring.p_uv[2 * i + 1][2]};
877       const size_t i2 = (2 * i + 2) % ring.n;
878       const vec3 p2 = {ring.p_uv[i2][0], ring.p_uv[i2][1], ring.p_uv[i2][2]};
879       const double q = quad_shape(p0, p1, p2, p, normal);
880       qmin = std::min(q, qmin);
881       if(qmin < breakIfBelowThreshold) return qmin;
882     }
883     return qmin;
884   }
885 
dist_abs(double a,double b)886   inline double dist_abs(double a, double b) { return std::abs(a - b); }
887 
uv_adjust_jump(vec2 uv,const vec2 & uv_ref,const vec2 & jump)888   inline vec2 uv_adjust_jump(vec2 uv, const vec2 &uv_ref, const vec2 &jump)
889   {
890     for(uint32_t d = 0; d < 2; ++d)
891       if(jump[d] != 0.) {
892         if(uv[d] < uv_ref[d]) {
893           double cand = uv[d] + jump[d];
894           while(dist_abs(cand, uv_ref[d]) < dist_abs(uv[d], uv_ref[d])) {
895             uv[d] = cand;
896             cand = uv[d] + jump[d];
897           }
898         }
899         else {
900           double cand = uv[d] - jump[d];
901           while(dist_abs(cand, uv_ref[d]) < dist_abs(uv[d], uv_ref[d])) {
902             uv[d] = cand;
903             cand = uv[d] - jump[d];
904           }
905         }
906       }
907     return uv;
908   }
909 
abs_diff_wperiod(double a,double b,double T)910   inline double abs_diff_wperiod(double a, double b, double T)
911   {
912     if(T == 0.) return std::abs(a - b);
913     return std::abs(fmod(a - b, T));
914   }
915 
getDeltaUV(const vec5 & v,const OneRing & ring)916   inline vec2 getDeltaUV(const vec5 &v, const OneRing &ring)
917   {
918     constexpr bool useBboxUV = false;
919     bool periodic = (ring.jump[0] != 0. || ring.jump[1] != 0.);
920     if(useBboxUV) {
921       const vec2 uv_0 = {v[3], v[4]};
922       double u_range[2] = {DBL_MAX, -DBL_MAX};
923       double v_range[2] = {DBL_MAX, -DBL_MAX};
924       for(uint32_t i = 0; i < ring.n; ++i) {
925         vec2 uv_i = {ring.p_uv[i][3], ring.p_uv[i][4]};
926         if(periodic) uv_i = uv_adjust_jump(uv_i, uv_0, ring.jump);
927         u_range[0] = std::min(u_range[0], uv_i[0]);
928         u_range[1] = std::max(u_range[1], uv_i[0]);
929         v_range[0] = std::min(v_range[0], uv_i[1]);
930         v_range[1] = std::max(v_range[1], uv_i[1]);
931       }
932       const double du = u_range[1] - u_range[0];
933       const double dv = v_range[1] - v_range[0];
934       return {du, dv};
935     }
936     else {
937       bool periodic = (ring.jump[0] != 0. || ring.jump[1] != 0.);
938       const vec2 uv_0 = {v[3], v[4]};
939       double du = 0.;
940       double dv = 0.;
941       // DBG(uv_0, ring.jump);
942       for(uint32_t i = 0; i < ring.n; ++i) {
943         double duc = abs_diff_wperiod(
944           ring.p_uv[i][3], ring.p_uv[(i + 1) % ring.n][3], ring.jump[0]);
945         double dvc = abs_diff_wperiod(
946           ring.p_uv[i][4], ring.p_uv[(i + 1) % ring.n][4], ring.jump[1]);
947         if(ring.jump[0] > 0. && duc > 0.5 * ring.jump[0]) duc = 0.;
948         if(ring.jump[1] > 0. && dvc > 0.5 * ring.jump[1]) dvc = 0.;
949         du = std::max(du, duc);
950         dv = std::max(dv, dvc);
951       }
952       return {2. * du, 2. * dv};
953     }
954   }
955 
getRingDispMax(const vec5 & v,const OneRing & ring)956   inline double getRingDispMax(const vec5 &v, const OneRing &ring)
957   {
958     double dMax2 = 0.;
959     for(uint32_t i = 0; i < ring.n; ++i) {
960       vec3 p_i = {ring.p_uv[i][0], ring.p_uv[i][1], ring.p_uv[i][2]};
961       vec3 p_n = {ring.p_uv[(i + 1) % ring.n][0],
962                   ring.p_uv[(i + 1) % ring.n][1],
963                   ring.p_uv[(i + 1) % ring.n][2]};
964       dMax2 = std::max(dMax2, length2(p_i - p_n));
965     }
966     return std::sqrt(dMax2);
967   }
968 
getGrid(const vec5 & v,vec2 deltaUV,size_t n,double w)969   inline vec4 getGrid(const vec5 &v, vec2 deltaUV, size_t n, double w)
970   {
971     const vec2 uv_0 = {v[3], v[4]};
972     const vec4 grid = {
973       uv_0[0] - w * deltaUV[0], /* grid_u_min */
974       uv_0[1] - w * deltaUV[1], /* grid_v_min */
975       uv_0[0] + w * deltaUV[0], /* grid_u_max */
976       uv_0[1] + w * deltaUV[1] /* grid_v_max */
977     };
978     return grid;
979   }
980 
dmoOptimizeVertexPosition(GFace * gf,vec5 v,const OneRing & ring,size_t n,size_t nIter,vec3 normal,double & qmax)981   vec5 dmoOptimizeVertexPosition(GFace *gf, vec5 v, const OneRing &ring,
982                                  size_t n, size_t nIter, vec3 normal,
983                                  double &qmax)
984   {
985     double w = 0.5;
986     if(qmax == DBL_MAX || qmax == -DBL_MAX) {
987       /* Only compute input quality if not given */
988       qmax = computeQualityQuadOneRing(v, ring, normal);
989     }
990     vec5 vmax = v;
991     vec2 deltaUV = getDeltaUV(v, ring);
992 
993     /* Sanity check on the UV variation */
994     if(ring.jump[0] > 0. && deltaUV[0] > 0.5 * ring.jump[0]) return v;
995     if(ring.jump[1] > 0. && deltaUV[1] > 0.5 * ring.jump[1]) return v;
996     if(deltaUV[0] > 0.05 * ring.range[0]) return v;
997     if(deltaUV[1] > 0.05 * ring.range[1]) return v;
998 
999     const bool checkDisplacement = ring.jump[0] != 0. || ring.jump[1] != 0.;
1000     const double dispMax = checkDisplacement ? getRingDispMax(v, ring) : 0.;
1001 
1002     for(size_t iter = 0; iter < nIter; ++iter) {
1003       vec4 grid = getGrid(v, deltaUV, n, w);
1004       SPoint2 uv;
1005       for(size_t i = 0; i < n; ++i) {
1006         uv.data()[0] = double(i) / double(n - 1) * grid[0] +
1007                        double(n - 1 - i) / double(n - 1) * grid[2];
1008         for(size_t j = 0; j < n; ++j) {
1009           uv.data()[1] = double(j) / double(n - 1) * grid[1] +
1010                          double(n - 1 - j) / double(n - 1) * grid[3];
1011           GPoint newPos = gf->point(uv);
1012           if(!newPos.succeeded()) continue;
1013           if(checkDisplacement) {
1014             double disp = length(vec3{newPos.x(), newPos.y(), newPos.z()} -
1015                                  vec3{v[0], v[1], v[2]});
1016             // if (disp > 1 || dispMax > 1) {
1017             //   // DBG(disp, dispMax, v, deltaUV, uv, ring.jump);
1018             //   // abort();
1019             //   //gmsh::fltk::run();
1020             //   //abort();
1021             // }
1022             if(disp > dispMax) continue;
1023           }
1024           const vec5 v2 = {newPos.x(), newPos.y(), newPos.z(), uv.data()[0],
1025                            uv.data()[1]};
1026           double q2 = computeQualityQuadOneRing(v2, ring, normal, qmax);
1027           if(q2 > qmax) {
1028             vmax = v2;
1029             qmax = q2;
1030           }
1031         }
1032       }
1033       w = w * 2. / double(n - 1);
1034     }
1035 
1036     return vmax;
1037   }
1038 } // namespace QMT
1039 using namespace QMT;
1040 
computeSICN(const std::vector<MElement * > & elements,double & sicnMin,double & sicnAvg)1041 void computeSICN(const std::vector<MElement *> &elements, double &sicnMin,
1042                  double &sicnAvg)
1043 {
1044   sicnMin = DBL_MAX;
1045   sicnAvg = 0.;
1046   for(size_t i = 0; i < elements.size(); ++i) {
1047     double q = elements[i]->minSICNShapeMeasure();
1048     if(std::isnan(q)) { q = -1.; }
1049     sicnMin = std::min(sicnMin, q);
1050     sicnAvg += q;
1051   }
1052   if(elements.size() > 0) sicnAvg /= double(elements.size());
1053 }
1054 
patchOptimizeGeometryGlobal(GFaceMeshPatch & patch,GeomOptimStats & stats)1055 int patchOptimizeGeometryGlobal(GFaceMeshPatch &patch, GeomOptimStats &stats)
1056 {
1057   GFace *gf = patch.gf;
1058   if(gf == NULL) return -1;
1059   if(!gf->haveParametrization()) {
1060     Msg::Debug("optimize geometry global: face %i have no parametrization",
1061                gf->tag());
1062     return -2;
1063   }
1064   if(patch.bdrVertices.size() != 1) {
1065     Msg::Debug(
1066       "optimize geometry global: patch has multiple boundary loops (%li)",
1067       patch.bdrVertices.size());
1068     return -1;
1069   }
1070   if(patch.intVertices.size() == 0) {
1071     Msg::Debug("optimize geometry global: no interior vertices (%li bdr "
1072                "vertices, %li elements)",
1073                patch.bdrVertices.front().size(), patch.elements.size());
1074     return -1;
1075   }
1076 
1077   bool debugCreateViews = DBG_VIZU_G;
1078   if(Msg::GetVerbosity() >= 999 &&
1079      CTX::instance()->debugSurface == patch.gf->tag()) {
1080     debugCreateViews = true;
1081   }
1082   const int rdi = (int)(((double)rand() / RAND_MAX) *
1083                         1e4); /* only to get a random name for debugging */
1084 
1085   double t1 = Cpu();
1086 
1087   std::unordered_map<MVertex *, size_t> old2new;
1088   std::vector<vector<size_t> > v2v;
1089   bool oks =
1090     buildCondensedStructure(patch.intVertices, patch.elements, old2new, v2v);
1091   if(!oks) {
1092     Msg::Debug("optimize geometry global: failed to build edge graph");
1093     return -1;
1094   }
1095 
1096   /* uvs/v2v: first all the free vertices, then the bdr vertices */
1097   vector<SPoint2> uvs(old2new.size(), SPoint2(DBL_MAX, DBL_MAX));
1098   /* Interior vertices initialized to (0,0) */
1099   size_t nInterior = patch.intVertices.size();
1100   for(size_t v = 0; v < nInterior; ++v) {
1101     sort_unique(v2v[v]);
1102     uvs[v] = SPoint2(0., 0.);
1103   }
1104 
1105   /* Boundary vertices */
1106   for(size_t loop = 0; loop < patch.bdrVertices.size(); ++loop) {
1107     vector<SPoint2> bndUvs;
1108     bool okl = getContinuousUVOnLoop(gf, patch.bdrVertices[loop], bndUvs);
1109     if(!okl) {
1110       Msg::Debug("optimize geometry global: failed to get continuous UV on "
1111                  "boundary loop");
1112       return -2;
1113     }
1114     for(size_t i = 0; i < patch.bdrVertices[loop].size(); ++i) {
1115       MVertex *v = patch.bdrVertices[loop][i];
1116       auto it = old2new.find(v);
1117       if(it == old2new.end()) {
1118         Msg::Error("optimize geometry global: bdr vertex not found in old2new");
1119         return -1;
1120       }
1121       size_t idx = it->second;
1122       if(uvs[idx].x() != DBL_MAX) continue;
1123       uvs[idx] = bndUvs[i];
1124       double dist =
1125         std::pow(bndUvs[(i + 1) % bndUvs.size()].x() - bndUvs[i].x(), 2) +
1126         std::pow(bndUvs[(i + 1) % bndUvs.size()].y() - bndUvs[i].y(), 2);
1127       dist = std::sqrt(dist);
1128     }
1129   }
1130 
1131   computeSICN(patch.elements, stats.sicnMinBefore, stats.sicnAvgBefore);
1132 
1133   /* Laplacian smoothing via linear system */
1134   bool ok = solveLaplaceLinearSystem(nInterior, v2v, uvs);
1135   if(!ok) {
1136     Msg::Warning("optimize geometry global: failed to solve linear system");
1137     return -1;
1138   }
1139 
1140   if(debugCreateViews) {
1141     GeoLog::add(patch.elements, "optim_Duv_IN_" + std::to_string(rdi) + "_" +
1142                                   std::to_string(stats.sicnAvgBefore));
1143   }
1144 
1145   /* Apply CAD mapping */
1146   for(MVertex *v : patch.intVertices) {
1147     size_t idx = old2new[v];
1148     SPoint2 uv = uvs[idx];
1149     v->setParameter(0, uv[0]);
1150     v->setParameter(1, uv[1]);
1151     GPoint p = gf->point(uv);
1152     if(p.succeeded()) { v->setXYZ(p.x(), p.y(), p.z()); }
1153     else {
1154       Msg::Error("optimize geometry global: CAD evaluation failed on face %i "
1155                  "at uv=(%f,%f)",
1156                  gf->tag(), uv[0], uv[1]);
1157     }
1158   }
1159 
1160   computeSICN(patch.elements, stats.sicnMinAfter, stats.sicnAvgAfter);
1161 
1162   if(debugCreateViews) {
1163     GeoLog::add(patch.elements, "optim_Duv_OUT_" + std::to_string(rdi) + "_" +
1164                                   std::to_string(stats.sicnAvgAfter));
1165   }
1166 
1167   double t2 = Cpu();
1168   stats.timeCpu = t2 - t1;
1169   stats.nFree = patch.intVertices.size();
1170   stats.nLock = patch.bdrVertices.front().size();
1171 
1172   Msg::Debug(
1173     "optimize geometry global (UV Laplacian): %li/%li free vertices, %li "
1174     "elements, SICN min: %.3f -> %.3f, SICN avg: %.3f -> %.3f, time: %.3fsec",
1175     patch.intVertices.size(),
1176     patch.intVertices.size() + patch.bdrVertices.front().size(),
1177     patch.elements.size(), stats.sicnMinBefore, stats.sicnMinAfter,
1178     stats.sicnAvgBefore, stats.sicnAvgAfter, stats.timeCpu);
1179 
1180   return 0;
1181 }
1182 
kernelLoopWithParametrization(GFaceMeshPatch & patch,const GeomOptimOptions & opt,GeomOptimStats & stats)1183 bool kernelLoopWithParametrization(GFaceMeshPatch &patch,
1184                                    const GeomOptimOptions &opt,
1185                                    GeomOptimStats &stats)
1186 {
1187   GFace *gf = patch.gf;
1188   if(!gf->haveParametrization()) {
1189     Msg::Error("optimize geometry kernel: face %i have no parametrization",
1190                gf->tag());
1191     return false;
1192   }
1193 
1194   /* Data for smoothing */
1195   std::vector<vec5> point_uv;
1196   std::vector<size_t> one_ring_first;
1197   std::vector<uint32_t> one_ring_values;
1198   std::vector<MVertex *> new2old;
1199   bool okb = buildUVSmoothingDataStructures(
1200     gf, patch.elements, patch.intVertices, point_uv, one_ring_first,
1201     one_ring_values, new2old);
1202   if(!okb) {
1203     Msg::Warning(
1204       "optimize geometry kernel: failed to build adjacency datastructures");
1205     return false;
1206   }
1207   OneRing ring;
1208   std::vector<vec5> ringGeometric(10);
1209   if(gf->periodic(0)) ring.jump[0] = gf->period(0);
1210   if(gf->periodic(1)) ring.jump[1] = gf->period(1);
1211   ring.range[0] = gf->parBounds(0).high() - gf->parBounds(0).low();
1212   ring.range[1] = gf->parBounds(1).high() - gf->parBounds(1).low();
1213   ring.p_uv = ringGeometric.data();
1214   const double sign = opt.invertCADNormals ? -1. : 1.;
1215 
1216   size_t dmo_grid_width = 8;
1217   size_t dmo_grid_depth = 3;
1218   if(!haveNiceParametrization(gf)) {
1219     dmo_grid_width = 12;
1220     dmo_grid_depth = 4;
1221   }
1222 
1223   /* Initialization: all vertices unlocked */
1224   std::vector<bool> locked(patch.intVertices.size(), false);
1225   if(opt.qualityRangeTechnique) {
1226     std::fill(locked.begin(), locked.end(), true);
1227   }
1228 
1229   /* Explicit smoothing loop */
1230   const size_t nFree = patch.intVertices.size();
1231   for(size_t iter = 0; iter < opt.outerLoopIterMax; ++iter) {
1232     size_t nMoved = 0;
1233     double iterQmin = 1.;
1234 
1235     /* Loop over interior vertices */
1236     for(size_t v = 0; v < nFree; ++v) {
1237       /* Fill the OneRing geometric information */
1238       ring.n = one_ring_first[v + 1] - one_ring_first[v];
1239       if(ring.n >= ringGeometric.size()) {
1240         ringGeometric.resize(ring.n);
1241         ring.p_uv = ringGeometric.data();
1242       }
1243       for(size_t lv = 0; lv < ring.n; ++lv) {
1244         ring.p_uv[lv] = point_uv[one_ring_values[one_ring_first[v] + lv]];
1245       }
1246 
1247       /* Current position, normal and quality */
1248       vec5 pos = point_uv[v];
1249       vec3 normal = sign * gf->normal(SPoint2(pos[3], pos[4]));
1250       if(length2(normal) == 0.) {
1251         Msg::Warning("optimize geometry kernel: CAD normal length is 0 !");
1252         continue;
1253       }
1254       normalize(normal);
1255       double quality = computeQualityQuadOneRing(pos, ring, normal);
1256 
1257       if(opt.qualityRangeTechnique && iter == 0) {
1258         /* First iteration: unlocked low quality vertices */
1259         if(quality < opt.qualityRangeMin) { locked[v] = false; }
1260         else {
1261           continue;
1262         }
1263       }
1264       if(opt.qualityRangeTechnique && quality > opt.qualityRangeMax) {
1265         locked[v] = true;
1266         continue;
1267       }
1268 
1269       /* Optimize vertex position with DMO (adaptive grid sampling) */
1270       if(opt.qualityRangeTechnique || quality < opt.useDmoIfSICNbelow) {
1271         vec5 newPos = dmoOptimizeVertexPosition(
1272           gf, pos, ring, dmo_grid_width, dmo_grid_depth, normal, quality);
1273         point_uv[v] = newPos;
1274         iterQmin = std::min(iterQmin, quality);
1275         nMoved += 1;
1276       }
1277 
1278       if(opt.qualityRangeTechnique) {
1279         if(quality > opt.qualityRangeTechnique) {
1280           locked[v] = true;
1281           continue;
1282         }
1283         else { /* Unlock neighbors ! */
1284           for(size_t lv = 0; lv < ring.n; ++lv) {
1285             uint32_t v2 = one_ring_values[one_ring_first[v] + lv];
1286             if(v2 < nFree) locked[v2] = false;
1287           }
1288         }
1289       }
1290       else {
1291         // TODO: lock vertex is local dx reduction
1292       }
1293     }
1294     // DBG(iter, nMoved, iterQmin);
1295   }
1296 
1297   /* Update the positions */
1298   for(size_t i = 0; i < patch.intVertices.size(); ++i) {
1299     new2old[i]->setParameter(0, point_uv[i][3]);
1300     new2old[i]->setParameter(1, point_uv[i][4]);
1301     new2old[i]->setXYZ(point_uv[i][0], point_uv[i][1], point_uv[i][2]);
1302   }
1303 
1304   return true;
1305 }
1306 
movePointWithKernelAndProjection(uint32_t v,const std::vector<std::array<double,3>> & points,const std::vector<size_t> & one_ring_first,const std::vector<uint32_t> & one_ring_values,const SmoothingKernel & kernelRegular,const SmoothingKernel & kernelIrregular,bool project,SurfaceProjector * sp,vec3 & newPos,vec2 & newUv,bool smartVariant=false)1307 bool movePointWithKernelAndProjection(
1308   uint32_t v, const std::vector<std::array<double, 3> > &points,
1309   const std::vector<size_t> &one_ring_first,
1310   const std::vector<uint32_t> &one_ring_values,
1311   const SmoothingKernel &kernelRegular, const SmoothingKernel &kernelIrregular,
1312   bool project, SurfaceProjector *sp, vec3 &newPos, vec2 &newUv,
1313   bool smartVariant = false)
1314 {
1315   if(project && sp == nullptr) {
1316     Msg::Error("cannot project with surface projector");
1317     return false;
1318   }
1319 
1320   size_t n = one_ring_first[v + 1] - one_ring_first[v];
1321 
1322   GPoint proj;
1323   double stDx = 0.;
1324   double sicnMinB = 0.; /* only used if smartVariant */
1325   double sicnMinA = 0.; /* only used if smartVariant */
1326   if(n == 8 && kernelRegular ==
1327                  SmoothingKernel::WinslowFDM) { /* Winslow for regular vertex */
1328     /* Extract geometric stencil */
1329     std::array<vec3, 8> stencil =
1330       fillStencilRegular(v, points, one_ring_first, one_ring_values);
1331     if(smartVariant) {
1332       sicnMinB = stencilQualitySICNmin(points[v], stencil);
1333       if(sicnMinB < 0.3)
1334         sicnMinB = stencilQualitySICNminGmsh(points[v], stencil);
1335     }
1336 
1337     /* Smoothing (in 3D, not on surface) */
1338     bool ok = kernelWinslow(stencil, newPos);
1339     if(!ok) return false;
1340 
1341     if(project) { /* Projection on surface */
1342       proj = sp->closestPoint(newPos.data(), false, false);
1343       if(!proj.succeeded()) {
1344         Msg::Debug("kernel smoothing: projection failed");
1345         return false;
1346       }
1347       newPos = {proj.x(), proj.y(), proj.z()};
1348       newUv = {proj.u(), proj.v()};
1349     }
1350 
1351     if(smartVariant) {
1352       sicnMinA = stencilQualitySICNmin(newPos, stencil);
1353       if(sicnMinA < 0.3) sicnMinA = stencilQualitySICNminGmsh(newPos, stencil);
1354     }
1355   }
1356   else { /* irregular vertex */
1357     std::vector<vec3> stencilIrreg(8);
1358 
1359     bool angleBased = false;
1360     if(n > 4 && n % 2 == 0) {
1361       if(n == 8 && kernelRegular == AngleBased) { angleBased = true; }
1362       else if(kernelIrregular == AngleBased) {
1363         angleBased = true;
1364       }
1365     }
1366 
1367     /* Extract geometric stencil */
1368     bool oneOverTwo = angleBased; /* angle-based does not use diagonals */
1369     fillStencilIrregular(v, points, one_ring_first, one_ring_values,
1370                          stencilIrreg, oneOverTwo);
1371 
1372     if(smartVariant) {
1373       std::vector<vec3> stencilIrregFull = stencilIrreg;
1374       if(oneOverTwo) {
1375         fillStencilIrregular(v, points, one_ring_first, one_ring_values,
1376                              stencilIrregFull, false);
1377       }
1378       sicnMinB = stencilQualitySICNmin(points[v], stencilIrregFull);
1379       if(sicnMinB < 0.3)
1380         sicnMinB = stencilQualitySICNminGmsh(points[v], stencilIrregFull);
1381     }
1382 
1383     /* Smoothing (in 3D, not on surface) */
1384     bool ok = false;
1385     if(angleBased) { ok = kernelAngleBased(points[v], stencilIrreg, newPos); }
1386     else {
1387       ok = kernelLaplacian(stencilIrreg, newPos);
1388     }
1389     if(!ok) return false;
1390 
1391     if(project) { /* Projection on surface */
1392       proj = sp->closestPoint(newPos.data(), false, false);
1393       if(!proj.succeeded()) {
1394         Msg::Debug("kernel smoothing: projection failed");
1395         return false;
1396       }
1397       newPos = {proj.x(), proj.y(), proj.z()};
1398       newUv = {proj.u(), proj.v()};
1399     }
1400 
1401     if(smartVariant) {
1402       std::vector<vec3> stencilIrregFull = stencilIrreg;
1403       if(oneOverTwo) {
1404         fillStencilIrregular(v, points, one_ring_first, one_ring_values,
1405                              stencilIrregFull, false);
1406       }
1407       sicnMinA = stencilQualitySICNmin(newPos, stencilIrregFull);
1408       if(sicnMinA < 0.3)
1409         sicnMinA = stencilQualitySICNminGmsh(newPos, stencilIrregFull);
1410     }
1411   }
1412   if(smartVariant && sicnMinA < sicnMinB) return false;
1413   return true;
1414 }
1415 
kernelLoopWithProjection(GFaceMeshPatch & patch,const GeomOptimOptions & opt,GeomOptimStats & stats,bool finalCADprojection=false)1416 bool kernelLoopWithProjection(GFaceMeshPatch &patch,
1417                               const GeomOptimOptions &opt,
1418                               GeomOptimStats &stats,
1419                               bool finalCADprojection = false)
1420 {
1421   GFace *gf = patch.gf;
1422   if(opt.sp == nullptr) {
1423     Msg::Error("kernel loop with projection: no surface projector");
1424     return false;
1425   }
1426 
1427   /* Data for smoothing */
1428   std::vector<size_t> one_ring_first;
1429   std::vector<uint32_t> one_ring_values;
1430   std::vector<MVertex *> new2old;
1431   std::unordered_map<MVertex *, uint32_t> old2new;
1432   std::vector<std::array<double, 3> > points;
1433   {
1434     std::vector<std::array<uint32_t, 4> > quads;
1435     std::vector<std::vector<uint32_t> > v2q;
1436     std::vector<std::vector<uint32_t> > oneRings;
1437     bool okc =
1438       buildCondensedStructure(patch.elements, patch.intVertices, old2new,
1439                               new2old, quads, v2q, oneRings, points);
1440     if(!okc) {
1441       Msg::Warning(
1442         "kernelLoopWithProjection: failed to build condensed representation");
1443       return false;
1444     }
1445     compress(oneRings, one_ring_first, one_ring_values);
1446   }
1447   const size_t nFree = patch.intVertices.size();
1448 
1449   std::vector<std::array<double, 2> > point_uvs;
1450   if(gf->haveParametrization()) {
1451     point_uvs.resize(points.size());
1452     for(size_t v = 0; v < nFree; ++v) {
1453       new2old[v]->getParameter(0, point_uvs[v][0]);
1454       new2old[v]->getParameter(1, point_uvs[v][1]);
1455     }
1456   }
1457 
1458   /* Local sizes */
1459   vector<double> localAvgSize(nFree, 0.);
1460   for(size_t v = 0; v < nFree; ++v) {
1461     size_t n = one_ring_first[v + 1] - one_ring_first[v];
1462     if(n == 0) continue;
1463     for(size_t lv = 0; lv < n; ++lv) {
1464       uint32_t v2 = one_ring_values[one_ring_first[v] + lv];
1465       localAvgSize[v] += length(points[v] - points[v2]);
1466     }
1467     localAvgSize[v] /= double(n);
1468   }
1469 
1470   bool project = opt.project;
1471   if(opt.project && gf->geomType() == GFace::GeomType::Plane) project = false;
1472 
1473   /* Initialization: all vertices unlocked */
1474   std::vector<bool> locked(patch.intVertices.size(), false);
1475 
1476   /* Explicit smoothing loop */
1477   double t0 = Cpu();
1478   double sum_dx0 = 0;
1479   std::vector<vec3> stencilIrreg(10);
1480   for(size_t iter = 0; iter < opt.outerLoopIterMax; ++iter) {
1481     size_t nMoved = 0;
1482 
1483     double sum_dx = 0.;
1484     /* Loop over interior vertices */
1485     for(size_t v = 0; v < nFree; ++v) {
1486       if(locked[v]) continue;
1487 
1488       vec3 newPos = points[v];
1489       vec2 newUv;
1490       if(point_uvs.size()) { newUv = point_uvs[v]; }
1491 
1492       bool ok = movePointWithKernelAndProjection(
1493         v, points, one_ring_first, one_ring_values, opt.kernelRegular,
1494         opt.kernelIrregular, project, opt.sp, newPos, newUv);
1495       if(ok) {
1496         double dx = length(newPos - points[v]);
1497         sum_dx += dx;
1498         /* Modify the coordinates */
1499         points[v] = newPos;
1500         if(point_uvs.size()) point_uvs[v] = newUv;
1501         if(opt.localLocking && dx < opt.dxLocalMax * localAvgSize[v]) {
1502           locked[v] = true;
1503         }
1504         else {
1505           nMoved += 1;
1506         }
1507       }
1508       else {
1509         locked[v] = true;
1510       }
1511     }
1512 
1513     if(iter == 0) { sum_dx0 = sum_dx; }
1514     else {
1515       if(sum_dx < opt.dxGlobalMax * sum_dx0) {
1516         Msg::Debug("kernelLoopWithProjection: stop at iter %li/%li because "
1517                    "sum_dx = %f < %f",
1518                    iter, opt.outerLoopIterMax, sum_dx,
1519                    opt.dxGlobalMax * sum_dx0);
1520         break;
1521       }
1522       if(nMoved == 0) {
1523         Msg::Debug("kernelLoopWithProjection: stop at iter %li/%li because no "
1524                    "point moved",
1525                    iter, opt.outerLoopIterMax);
1526         break;
1527       }
1528     }
1529 
1530     if(Cpu() - t0 > opt.timeMax) {
1531       Msg::Debug("kernelLoopWithProjection: stop at iter %li/%li because "
1532                  "timeout (%f sec)",
1533                  iter, opt.outerLoopIterMax, Cpu() - t0);
1534       break;
1535     }
1536   }
1537 
1538   /* Update the positions */
1539   for(size_t i = 0; i < patch.intVertices.size(); ++i) {
1540     MVertex *v = new2old[i];
1541     v->setXYZ(points[i][0], points[i][1], points[i][2]);
1542     if(point_uvs.size()) {
1543       v->setParameter(0, point_uvs[i][0]);
1544       v->setParameter(1, point_uvs[i][1]);
1545 
1546       if(finalCADprojection) {
1547         SPoint3 query = v->point();
1548         GPoint proj = gf->closestPoint(query, point_uvs[i].data());
1549         if(proj.succeeded()) {
1550           v->setXYZ(proj.x(), proj.y(), proj.z());
1551           v->setParameter(0, proj.u());
1552           v->setParameter(1, proj.v());
1553         }
1554       }
1555     }
1556   }
1557 
1558   return true;
1559 }
1560 
quad_opposite_right_angled_corner(vec3 pPrev,vec3 pCorner,vec3 pNext)1561 vec3 quad_opposite_right_angled_corner(vec3 pPrev, vec3 pCorner, vec3 pNext)
1562 {
1563   double radius = 0.5 * length(pPrev - pNext);
1564   vec3 center = 0.5 * (pPrev + pNext);
1565   if(length(center - pCorner) < 1.e-10) { return center; }
1566   vec3 dir = center - pCorner;
1567   normalize(dir);
1568   return center + radius * dir;
1569 }
1570 
quadMeshSpecialAcuteCornerOptimization(GFace * gf,SurfaceProjector * sp=nullptr)1571 bool quadMeshSpecialAcuteCornerOptimization(GFace *gf,
1572                                             SurfaceProjector *sp = nullptr)
1573 {
1574   std::unordered_map<MVertex *, int> bdrVal;
1575   std::unordered_map<MVertex *, double> bdrAgl;
1576   std::unordered_map<MVertex *, std::vector<MQuadrangle *> > corner2quads;
1577   std::unordered_map<MVertex *, std::vector<MQuadrangle *> > v2quads;
1578   for(MQuadrangle *q : gf->quadrangles) {
1579     bool keep = false;
1580     for(size_t lv = 0; lv < 4; ++lv) {
1581       MVertex *v = q->getVertex(lv);
1582       if(v->onWhat() != nullptr && v->onWhat()->dim() < 2) {
1583         bdrVal[v] += 1;
1584         MVertex *vp = q->getVertex((lv - 1 + 4) % 4);
1585         MVertex *vn = q->getVertex((lv + 1) % 4);
1586         double agl = std::abs(angle3Vertices(vp, v, vn));
1587         bdrAgl[v] += agl;
1588         if(v->onWhat()->dim() == 0) {
1589           corner2quads[v].push_back(q);
1590           keep = true;
1591         }
1592       }
1593     }
1594     if(keep) {
1595       for(size_t lv = 0; lv < 4; ++lv) {
1596         MVertex *v = q->getVertex(lv);
1597         v2quads[v].push_back(q);
1598       }
1599     }
1600   }
1601 
1602   size_t nMoved = 0;
1603   for(auto &kv : corner2quads)
1604     if(kv.second.size() == 1) {
1605       MVertex *v = kv.first;
1606       MQuadrangle *q = kv.second[0];
1607       double agl = bdrAgl[v];
1608       if(agl * 180. / M_PI > 30) continue; // not acute corner
1609       for(size_t lv = 0; lv < 4; ++lv) {
1610         MVertex *vc = q->getVertex(lv);
1611         if(vc != v) continue;
1612         MVertex *vp = q->getVertex((lv - 1 + 4) % 4);
1613         MVertex *vn = q->getVertex((lv + 1) % 4);
1614         MVertex *vo = q->getVertex((lv + 2) % 4);
1615         if(vo->onWhat() != nullptr && vo->onWhat()->dim() != 2) continue;
1616 
1617         const bool BY_OPTIM = true;
1618         bool oku = false;
1619         if(BY_OPTIM) {
1620           GFaceMeshPatch patch;
1621           bool okp = patchFromQuads(gf, v2quads[vo], patch);
1622           if(!okp) continue;
1623           bool backupRestore = true;
1624           GeometryOptimizer optu;
1625           optu.initialize(patch);
1626           bool projectOnCad = true;
1627           size_t iter = 10;
1628           GeometryOptimizer::PlanarMethod method =
1629             GeometryOptimizer::PlanarMethod::MeanPlane;
1630           oku = optu.smoothWithWinslowUntangler(method, iter, backupRestore,
1631                                                 projectOnCad);
1632         }
1633 
1634         if(!oku) {
1635           double sige_before = q->minSIGEShapeMeasure();
1636           SPoint3 backup = vc->point();
1637           vec3 pp = vp->point();
1638           vec3 pc = vc->point();
1639           vec3 pn = vn->point();
1640           vec3 po = quad_opposite_right_angled_corner(pp, pc, pn);
1641           vo->setXYZ(po[0], po[1], po[2]);
1642           if(sp) {
1643             GPoint proj = sp->closestPoint(po.data(), false, false);
1644             if(proj.succeeded()) { vo->setXYZ(proj.x(), proj.y(), proj.z()); }
1645           }
1646           double sige_after = q->minSIGEShapeMeasure();
1647           if(sige_after < sige_before) {
1648             vo->setXYZ(backup.x(), backup.y(), backup.z());
1649           }
1650           else {
1651             nMoved += 1;
1652           }
1653         }
1654       }
1655     }
1656   Msg::Debug(
1657     "- acute corners, set right-angle position of %li interior vertices",
1658     nMoved);
1659 
1660   return true;
1661 }
1662 
nbVerticesOnBoundary(const GFaceMeshPatch & patch)1663 size_t nbVerticesOnBoundary(const GFaceMeshPatch &patch)
1664 {
1665   size_t n = 0;
1666   for(size_t i = 0; i < patch.bdrVertices.size(); ++i)
1667     n += patch.bdrVertices[i].size();
1668   return n;
1669 }
1670 
patchOptimizeGeometryWithKernel(GFaceMeshPatch & patch,const GeomOptimOptions & opt,GeomOptimStats & stats)1671 bool patchOptimizeGeometryWithKernel(GFaceMeshPatch &patch,
1672                                      const GeomOptimOptions &opt,
1673                                      GeomOptimStats &stats)
1674 {
1675   /* Debug visualization */
1676   bool debugCreateViews = DBG_VIZU_K;
1677   if(Msg::GetVerbosity() >= 999 &&
1678      CTX::instance()->debugSurface == patch.gf->tag()) {
1679     debugCreateViews = true;
1680   }
1681   const int rdi = (int)(((double)rand() / RAND_MAX) *
1682                         1e4); /* only to get a random name for debugging */
1683 
1684   GFace *gf = patch.gf;
1685   if(gf == NULL || patch.elements.size() == 0) return false;
1686   if(patch.intVertices.size() == 0) return false;
1687 
1688   stats.sicnMinBefore = -1.; /* if no element */
1689   stats.sicnAvgBefore = -1.;
1690   double t1 = Cpu();
1691   computeSICN(patch.elements, stats.sicnMinBefore, stats.sicnAvgBefore);
1692 
1693   if(patch.intVertices.size() == 0) {
1694     Msg::Debug("optimize geometry kernel: no interior vertices (%li elements)",
1695                patch.elements.size());
1696     stats.sicnMinAfter = stats.sicnMinBefore;
1697     stats.sicnAvgAfter = stats.sicnAvgBefore;
1698     return true;
1699   }
1700 
1701   PatchGeometryBackup *backup = nullptr;
1702   if(opt.withBackup) { backup = new PatchGeometryBackup(patch); }
1703 
1704   bool useParam = gf->haveParametrization();
1705   if(opt.sp != nullptr) useParam = false;
1706   if(opt.force3DwithProjection) useParam = false;
1707 
1708   if(debugCreateViews) {
1709     std::string method =
1710       "K" + (useParam ? std::string("uv") : std::string("3d+p"));
1711     GeoLog::add(patch.elements, "optim_" + method + "_IN_" +
1712                                   std::to_string(rdi) + "_" +
1713                                   std::to_string(stats.sicnAvgBefore));
1714   }
1715 
1716   if(useParam) {
1717     bool okl = kernelLoopWithParametrization(patch, opt, stats);
1718     if(!okl) {
1719       Msg::Warning(
1720         "optimize geometry kernel: the loop with param. failed (%li elements)",
1721         patch.elements.size());
1722       return -1;
1723     }
1724   }
1725   else {
1726     bool okl = kernelLoopWithProjection(patch, opt, stats);
1727     if(!okl) {
1728       Msg::Warning("optimize geometry kernel: the loop with projection failed "
1729                    "(%li elements)",
1730                    patch.elements.size());
1731       return -1;
1732     }
1733   }
1734 
1735   /* Statistics */
1736   computeSICN(patch.elements, stats.sicnMinAfter, stats.sicnAvgAfter);
1737 
1738   bool cancel = false;
1739 
1740   if(opt.withBackup && backup && stats.sicnMinAfter < stats.sicnMinBefore) {
1741     Msg::Debug("optimize geometry kernel: restore patch geometry with backup",
1742                patch.elements.size());
1743     backup->restore();
1744     stats.sicnMinAfter = stats.sicnMinBefore;
1745     stats.sicnAvgAfter = stats.sicnAvgBefore;
1746     cancel = true;
1747   }
1748 
1749   /* Debug visualization */
1750   if(debugCreateViews) {
1751     std::string method =
1752       "K" + (useParam ? std::string("uv") : std::string("3d+p"));
1753     GeoLog::add(patch.elements, "optim_" + method + "_OUT_" +
1754                                   std::to_string(rdi) + "_" +
1755                                   std::to_string(stats.sicnAvgAfter));
1756     GeoLog::flush();
1757   }
1758 
1759   double t2 = Cpu();
1760   stats.timeCpu = t2 - t1;
1761   stats.nFree = patch.intVertices.size();
1762   stats.nLock = nbVerticesOnBoundary(patch);
1763 
1764   if(backup) delete backup;
1765 
1766   if(cancel) return true;
1767 
1768   if(useParam) {
1769     Msg::Debug(
1770       "optimize geometry kernel (using CAD param): %li/%li free vertices, %li "
1771       "elements, SICN min: %.3f -> %.3f, SICN avg: %.3f -> %.3f, time: %.3fsec",
1772       patch.intVertices.size(), stats.nFree + stats.nLock,
1773       patch.elements.size(), stats.sicnMinBefore, stats.sicnMinAfter,
1774       stats.sicnAvgBefore, stats.sicnAvgAfter, stats.timeCpu);
1775   }
1776   else {
1777     Msg::Debug("optimize geometry kernel (using triangulation): %li/%li free "
1778                "vertices, %li elements, SICN min: %.3f -> %.3f, SICN avg: %.3f "
1779                "-> %.3f, time: %.3fsec",
1780                patch.intVertices.size(), stats.nFree + stats.nLock,
1781                patch.elements.size(), stats.sicnMinBefore, stats.sicnMinAfter,
1782                stats.sicnAvgBefore, stats.sicnAvgAfter, stats.timeCpu);
1783   }
1784 
1785   return true;
1786 }
1787 
patchProjectOnSurface(GFaceMeshPatch & patch,SurfaceProjector * sp)1788 bool patchProjectOnSurface(GFaceMeshPatch &patch, SurfaceProjector *sp)
1789 {
1790   Msg::Debug("patch surface projection (%i vertices) ...",
1791              patch.intVertices.size());
1792   bool useParam = patch.gf->haveParametrization();
1793   for(MVertex *v : patch.intVertices) {
1794     GPoint proj;
1795     if(sp != nullptr) {
1796       /* Triangulation projection */
1797       proj = sp->closestPoint(v->point().data(), false, false);
1798       if(proj.succeeded()) { v->setXYZ(proj.x(), proj.y(), proj.z()); }
1799       if(useParam) {
1800         /* CAD projection */
1801         double uv[2] = {proj.u(), proj.v()};
1802         GPoint proj2 = patch.gf->closestPoint(v->point(), uv);
1803         if(proj2.succeeded()) {
1804           v->setXYZ(proj2.x(), proj2.y(), proj2.z());
1805           v->setParameter(0, proj2.u());
1806           v->setParameter(1, proj2.v());
1807         }
1808       }
1809     }
1810     else if(useParam) {
1811       double uv[2];
1812       v->getParameter(0, uv[0]);
1813       v->getParameter(1, uv[1]);
1814       /* CAD projection */
1815       GPoint proj2 = patch.gf->closestPoint(v->point(), uv);
1816       if(proj2.succeeded()) {
1817         v->setXYZ(proj2.x(), proj2.y(), proj2.z());
1818         v->setParameter(0, proj2.u());
1819         v->setParameter(1, proj2.v());
1820       }
1821     }
1822     else {
1823       Msg::Error(
1824         "patch projection: no parametrization and no surface projector");
1825       return false;
1826     }
1827   }
1828   return true;
1829 }
1830 
optimizeGeometryQuadMesh(GFace * gf,SurfaceProjector * sp,double timeMax,bool withBackup)1831 bool optimizeGeometryQuadMesh(GFace *gf, SurfaceProjector *sp, double timeMax,
1832                               bool withBackup)
1833 {
1834   // TODO FIXME: replace by optimizeGeometryQuadTriMesh when it works well
1835 
1836   if(!gf->haveParametrization() && sp == nullptr) {
1837     Msg::Error("optimize geometry: face %i, no CAD and no surface projector",
1838                gf->tag());
1839     return false;
1840   }
1841 
1842   if(gf->quadrangles.size() == 0) {
1843     Msg::Error("optimize geometry: face %i, no quads", gf->tag());
1844     return false;
1845   }
1846 
1847   bool forceEvenIfBadBoundary = true;
1848   GFaceMeshPatch patch;
1849   bool okp = patchFromQuads(gf, gf->quadrangles, patch, forceEvenIfBadBoundary);
1850   if(!okp) {
1851     Msg::Warning("optimize geometry: face %i, failed to build patch",
1852                  gf->tag());
1853     return false;
1854   }
1855 
1856   bool debugCreateViews = DBG_VIZU_G;
1857   if(Msg::GetVerbosity() >= 999 &&
1858      CTX::instance()->debugSurface == patch.gf->tag()) {
1859     debugCreateViews = true;
1860   }
1861   const int rdi = (int)(((double)rand() / RAND_MAX) *
1862                         1e4); /* only to get a random name for debugging */
1863 
1864   /* Idea is to try many things (with backups each time):
1865    * - if planar: Winslow untangler
1866    * - else:
1867    *   - if CAD: try Winslow untangler in uv
1868    *   - try Winslow untangler on mean plane
1869    * - kernel smoother with proj
1870    */
1871 
1872   bool backupRestore = true;
1873   GeometryOptimizer optu;
1874   optu.initialize(patch, sp);
1875   bool oku = false;
1876   if(gf->geomType() == GEntity::Plane || !gf->haveParametrization()) {
1877     /* Winslow untangler on mean plane */
1878     size_t iter = 10;
1879     bool projectOnCad = true;
1880     GeometryOptimizer::PlanarMethod method =
1881       GeometryOptimizer::PlanarMethod::MeanPlane;
1882     oku = optu.smoothWithWinslowUntangler(method, iter, backupRestore,
1883                                           projectOnCad);
1884   }
1885   else if(gf->haveParametrization()) {
1886     /* Winslow untangler on uv param */
1887     size_t iter = 10;
1888     bool projectOnCad = true;
1889     GeometryOptimizer::PlanarMethod method =
1890       GeometryOptimizer::PlanarMethod::ParamCAD;
1891     // TODOMX: untangler in UV disabled for the moment
1892     //         should enable it only if param has no large distortion ?
1893     // oku = optu.smoothWithWinslowUntangler(method, iter, backupRestore,
1894     // projectOnCad);
1895     if(!oku) { /* try mean plane ... */
1896       method = GeometryOptimizer::PlanarMethod::MeanPlane;
1897       oku = optu.smoothWithWinslowUntangler(method, iter, backupRestore,
1898                                             projectOnCad);
1899     }
1900   }
1901 
1902   double minSICN = DBL_MAX;
1903   double avgSICN = 0.;
1904   computeSICN(patch.elements, minSICN, avgSICN);
1905   if(!oku || minSICN < 0.5) {
1906     int countMax = 3;
1907     bool running = true;
1908     size_t niter = 50;
1909     int count = 0;
1910     if(gf->geomType() == GEntity::Plane) {
1911       /* Much faster projection, we can smooth */
1912       niter = 100;
1913     }
1914     GeomOptimOptions opt;
1915     opt.sp = sp;
1916     opt.outerLoopIterMax = niter;
1917     opt.timeMax = timeMax;
1918     opt.localLocking = true;
1919     opt.dxGlobalMax = 1.e-3;
1920     opt.dxLocalMax = 1.e-5;
1921     opt.force3DwithProjection = true;
1922     opt.withBackup = false;
1923     SmoothingKernel kernelRegular = SmoothingKernel::WinslowFDM;
1924 
1925     if(false && gf->tag() == 100170) {
1926       Msg::Warning("SPECIAL SMOOTHING FOR DEBUGGING");
1927       niter = 100;
1928       countMax = 5;
1929       opt.dxGlobalMax = 1.e-5;
1930       opt.dxLocalMax = 1.e-7;
1931       kernelRegular = SmoothingKernel::Laplacian;
1932     }
1933 
1934     double t0 = Cpu();
1935 
1936     bool smartVariant = false;
1937     while(running && count < countMax) {
1938       running = false;
1939       count += 1;
1940       if(Cpu() - t0 > timeMax) {
1941         Msg::Debug("optimize geometry: face %i, reached time limit", gf->tag());
1942         break;
1943       }
1944 
1945       PatchGeometryBackup backup(patch);
1946       double minSICNb = DBL_MAX;
1947       double avgSICNb = 0.;
1948       computeSICN(patch.elements, minSICNb, avgSICNb);
1949 
1950       bool finalCADprojection = false;
1951       double t1 = Cpu();
1952       optu.smoothWithKernel(kernelRegular, SmoothingKernel::Laplacian, timeMax,
1953                             niter, false, true, 1.e-5, 1.e-3, true,
1954                             finalCADprojection, smartVariant);
1955 
1956       double etime = Cpu() - t1;
1957       double minSICNa = DBL_MAX;
1958       double avgSICNa = 0.;
1959       computeSICN(patch.elements, minSICNa, avgSICNa);
1960 
1961       bool keep = true;
1962       if(minSICNa < minSICNb && avgSICNa < avgSICNb) keep = false;
1963       if(minSICNa < 0.1 && minSICNa < minSICNb) keep = false;
1964       if(minSICNa < 0.75 * minSICNb) keep = false;
1965 
1966       if(false && gf->tag() == 100170) {
1967         Msg::Warning("SPECIAL SMOOTHING FOR DEBUGGING");
1968         keep = true;
1969         niter *= 1.5;
1970         running = true;
1971       }
1972 
1973       if(keep) {
1974         Msg::Debug("- Face %i: kernel smoothing (Mix Winslow/Angle, explicit "
1975                    "with projections, %li vertices, %li iter max, %.3f sec), "
1976                    "SICN min: %f -> %f, avg: %f -> %f",
1977                    gf->tag(), gf->mesh_vertices.size(), niter, etime, minSICNb,
1978                    minSICNa, avgSICNb, avgSICNa);
1979         if(minSICNa >= minSICNb && 0.99 * avgSICNa > avgSICNb && etime < 1) {
1980           niter *= 1.5;
1981           running = true;
1982         }
1983       }
1984       else {
1985         if(smartVariant) {
1986           /* Already using smart variant, time to stop .. */
1987           Msg::Debug("- Face %i: worst quality after global smoothing (Mix "
1988                      "Winslow/Angle, explicit, %li iter max), roll back (SICN "
1989                      "min: %f -> %f, avg: %f -> %f)",
1990                      gf->tag(), niter, minSICNb, minSICNa, avgSICNb, avgSICNa);
1991           if(withBackup) { backup.restore(); }
1992           break;
1993         }
1994         else {
1995           Msg::Debug("- Face %i: worst quality after global smoothing (Mix "
1996                      "Winslow/Angle, explicit, %li iter max), roll back, try "
1997                      "smart (SICN min: %f -> %f, avg: %f -> %f)",
1998                      gf->tag(), niter, minSICNb, minSICNa, avgSICNb, avgSICNa);
1999           if(withBackup) { backup.restore(); }
2000           /* Try to smooth with smart variant ... */
2001           running = true;
2002           smartVariant = true;
2003           count -= 1;
2004         }
2005       }
2006     }
2007   }
2008 
2009   double minSICNf = DBL_MAX;
2010   double avgSICNf = 0.;
2011   computeSICN(patch.elements, minSICNf, avgSICNf);
2012   Msg::Info("- Face %i: SICN min: %f -> %f, avg: %f -> %f", gf->tag(), minSICN,
2013             minSICNf, avgSICN, avgSICNf);
2014 
2015   return true;
2016 }
2017 
optimizeGeometryQuadTriMesh(GFace * gf,SurfaceProjector * sp,double timeMax)2018 bool optimizeGeometryQuadTriMesh(GFace *gf, SurfaceProjector *sp,
2019                                  double timeMax)
2020 {
2021   // TODO FIXME: replace by optimizeGeometryQuadMesh when it works well
2022 
2023   if(!gf->haveParametrization() && sp == nullptr) {
2024     Msg::Error("optimize geometry: face %i, no CAD and no surface projector",
2025                gf->tag());
2026     return false;
2027   }
2028 
2029   if(gf->quadrangles.size() == 0) {
2030     Msg::Error("optimize geometry: face %i, no quads", gf->tag());
2031     return false;
2032   }
2033 
2034   bool forceEvenIfBadBoundary = true;
2035   GFaceMeshPatch patch;
2036   std::vector<MElement *> elements =
2037     dynamic_cast_vector<MQuadrangle *, MElement *>(gf->quadrangles);
2038   append(elements, dynamic_cast_vector<MTriangle *, MElement *>(gf->triangles));
2039   bool okp = patchFromElements(gf, elements, patch, forceEvenIfBadBoundary);
2040   if(!okp) {
2041     Msg::Warning("optimize geometry: face %i, failed to build patch",
2042                  gf->tag());
2043     return false;
2044   }
2045 
2046   bool debugCreateViews = DBG_VIZU_G;
2047   if(Msg::GetVerbosity() >= 999 &&
2048      CTX::instance()->debugSurface == patch.gf->tag()) {
2049     debugCreateViews = true;
2050   }
2051   const int rdi = (int)(((double)rand() / RAND_MAX) *
2052                         1e4); /* only to get a random name for debugging */
2053 
2054   int countMax = 3;
2055   bool running = true;
2056   size_t niter = 50;
2057   int count = 0;
2058   if(gf->geomType() == GEntity::Plane) {
2059     /* Much faster projection, we can smooth */
2060     niter = 100;
2061   }
2062 
2063   double t0 = Cpu();
2064 
2065   while(running && count < countMax) {
2066     running = false;
2067     count += 1;
2068     if(Cpu() - t0 > timeMax) {
2069       Msg::Debug("optimize geometry: face %i, reached time limit", gf->tag());
2070       break;
2071     }
2072 
2073     PatchGeometryBackup backup(patch);
2074 
2075     double minSICNb = DBL_MAX;
2076     double avgSICNb = 0.;
2077     computeSICN(patch.elements, minSICNb, avgSICNb);
2078 
2079     if(debugCreateViews) {
2080       std::string method = "GFace_K";
2081       GeoLog::add(patch.elements, "optim_" + method + "_IN_" +
2082                                     std::to_string(rdi) + "_" +
2083                                     std::to_string(minSICNb));
2084     }
2085 
2086     GeomOptimStats stats;
2087     GeomOptimOptions opt;
2088     opt.sp = sp;
2089     opt.outerLoopIterMax = niter;
2090     opt.timeMax = timeMax;
2091     opt.localLocking = true;
2092     opt.dxGlobalMax = 1.e-3;
2093     opt.dxLocalMax = 1.e-5;
2094     opt.force3DwithProjection = true;
2095     opt.withBackup = false;
2096     double t1 = Cpu();
2097     bool okk = kernelLoopWithProjection(patch, opt, stats);
2098     if(!okk) { return false; }
2099     double etime = Cpu() - t1;
2100 
2101     double minSICNa = DBL_MAX;
2102     double avgSICNa = 0.;
2103     computeSICN(patch.elements, minSICNa, avgSICNa);
2104 
2105     bool keep = true;
2106     if(minSICNa < minSICNb && avgSICNa < avgSICNb) keep = false;
2107     if(minSICNa < 0.1 && minSICNa < minSICNb) keep = false;
2108     if(minSICNa < 0.75 * minSICNb) keep = false;
2109 
2110     if(debugCreateViews) {
2111       std::string method = "GFace_K";
2112       GeoLog::add(patch.elements,
2113                   "optim_" + method + "_OUT_" + std::to_string(rdi) + "_" +
2114                     std::to_string(minSICNa) + "_keep=" + std::to_string(keep));
2115     }
2116 
2117     if(keep) {
2118       Msg::Debug("- Face %i: kernel smoothing (Mix Winslow/Angle, explicit "
2119                  "with projections, %li vertices, %li iter max, %.3f sec), "
2120                  "SICN min: %f -> %f, avg: %f -> %f",
2121                  gf->tag(), gf->mesh_vertices.size(), niter, etime, minSICNb,
2122                  minSICNa, avgSICNb, avgSICNa);
2123       if(minSICNa >= minSICNb && 0.99 * avgSICNa > avgSICNb && etime < 1) {
2124         niter *= 1.5;
2125         running = true;
2126       }
2127     }
2128     else {
2129       Msg::Debug("- Face %i: worst quality after global smoothing (Mix "
2130                  "Winslow/Angle, explicit, %li iter max), roll back (SICN min: "
2131                  "%f -> %f, avg: %f -> %f)",
2132                  gf->tag(), niter, minSICNb, minSICNa, avgSICNb, avgSICNa);
2133       backup.restore();
2134       break;
2135     }
2136   }
2137 
2138   return true;
2139 }
2140 
initialize(GFaceMeshPatch & patch,SurfaceProjector * _sp)2141 bool GeometryOptimizer::initialize(GFaceMeshPatch &patch, SurfaceProjector *_sp)
2142 {
2143   std::vector<std::vector<uint32_t> > v2q;
2144   std::vector<std::vector<uint32_t> > oneRings;
2145   patchPtr = &patch;
2146   sp = _sp;
2147   bool okc = buildCondensedStructure(patch.elements, patch.intVertices, old2new,
2148                                      new2old, quads, v2q, oneRings, points);
2149   if(!okc) {
2150     Msg::Warning(
2151       "GeometryOptimizer initialize: failed to build condensed representation");
2152     return false;
2153   }
2154   compress(oneRings, one_ring_first, one_ring_values);
2155   nFree = patch.intVertices.size();
2156   if(patch.gf->haveParametrization()) {
2157     uvs.resize(points.size());
2158     for(size_t v = 0; v < nFree; ++v) {
2159       new2old[v]->getParameter(0, uvs[v][0]);
2160       new2old[v]->getParameter(1, uvs[v][1]);
2161     }
2162   }
2163   return true;
2164 }
2165 
smoothWithKernel(SmoothingKernel kernelRegular,SmoothingKernel kernelIrregular,double timeMax,int iterMax,double withBackup,bool localLocking,double dxLocalMax,double dxGlobalMax,bool project,bool finalCADprojection,bool smartVariant)2166 bool GeometryOptimizer::smoothWithKernel(
2167   SmoothingKernel kernelRegular, SmoothingKernel kernelIrregular,
2168   double timeMax, int iterMax, double withBackup,
2169   bool localLocking, /* Lock if small displacement, unlocked neighbors else */
2170   double dxLocalMax, /* lock a vertex if dx < dxLocalMax * local_size */
2171   double dxGlobalMax, /* stop if sum(dx) < dxGlobalMax * sum(dx_0) */
2172   bool project, /* project with SurfaceProjector */
2173   bool finalCADprojection, bool smartVariant)
2174 {
2175   /* Geometry backup */
2176   PatchGeometryBackup *backup = nullptr;
2177   double sicnMinBefore = -1.; /* if no element */
2178   double sicnAvgBefore = -1.;
2179   if(withBackup) {
2180     computeSICN(patchPtr->elements, sicnMinBefore, sicnAvgBefore);
2181     backup = new PatchGeometryBackup(*patchPtr);
2182   }
2183 
2184   /* Local sizes */
2185   vector<double> localAvgSize(nFree, 0.);
2186   for(size_t v = 0; v < nFree; ++v) {
2187     size_t n = one_ring_first[v + 1] - one_ring_first[v];
2188     if(n == 0) continue;
2189     for(size_t lv = 0; lv < n; ++lv) {
2190       uint32_t v2 = one_ring_values[one_ring_first[v] + lv];
2191       localAvgSize[v] += length(points[v] - points[v2]);
2192     }
2193     localAvgSize[v] /= double(n);
2194   }
2195 
2196   /* Initialization: all free vertices unlocked */
2197   std::vector<bool> locked(nFree, false);
2198 
2199   /* Explicit smoothing loop */
2200   double t0 = Cpu();
2201   double sum_dx0 = 0;
2202   std::vector<vec3> stencilIrreg(10);
2203   for(size_t iter = 0; iter < iterMax; ++iter) {
2204     size_t nMoved = 0;
2205 
2206     double sum_dx = 0.;
2207     /* Loop over interior vertices */
2208     for(size_t v = 0; v < nFree; ++v) {
2209       if(locked[v]) continue;
2210 
2211       vec3 newPos = points[v];
2212       vec2 newUv;
2213       if(uvs.size()) { newUv = uvs[v]; }
2214 
2215       bool ok = movePointWithKernelAndProjection(
2216         v, points, one_ring_first, one_ring_values, kernelRegular,
2217         kernelIrregular, project, sp, newPos, newUv, smartVariant);
2218       if(ok) {
2219         double dx = length(newPos - points[v]);
2220         sum_dx += dx;
2221         /* Modify the coordinates */
2222         points[v] = newPos;
2223         if(uvs.size()) uvs[v] = newUv;
2224         if(localLocking && dx < dxLocalMax * localAvgSize[v]) {
2225           locked[v] = true;
2226         }
2227         else {
2228           nMoved += 1;
2229         }
2230       }
2231       else {
2232         locked[v] = true;
2233       }
2234     }
2235 
2236     if(iter == 0) { sum_dx0 = sum_dx; }
2237     else {
2238       if(sum_dx < dxGlobalMax * sum_dx0) {
2239         Msg::Debug(
2240           "smoothWithKernel: stop at iter %li/%li because sum_dx = %f < %f",
2241           iter, iterMax, sum_dx, dxGlobalMax * sum_dx0);
2242         break;
2243       }
2244       if(nMoved == 0) {
2245         Msg::Debug(
2246           "smoothWithKernel: stop at iter %li/%li because no point moved", iter,
2247           iterMax);
2248         break;
2249       }
2250     }
2251 
2252     if(Cpu() - t0 > timeMax) {
2253       Msg::Debug(
2254         "smoothWithKernel: stop at iter %li/%li because timeout (%f sec)", iter,
2255         iterMax, Cpu() - t0);
2256       break;
2257     }
2258   }
2259 
2260   /* Update the positions */
2261   for(size_t i = 0; i < nFree; ++i) {
2262     MVertex *v = new2old[i];
2263     v->setXYZ(points[i][0], points[i][1], points[i][2]);
2264     if(uvs.size()) {
2265       v->setParameter(0, uvs[i][0]);
2266       v->setParameter(1, uvs[i][1]);
2267 
2268       if(finalCADprojection) {
2269         SPoint3 query = v->point();
2270         GPoint proj = patchPtr->gf->closestPoint(query, uvs[i].data());
2271         if(proj.succeeded()) {
2272           v->setXYZ(proj.x(), proj.y(), proj.z());
2273           v->setParameter(0, proj.u());
2274           v->setParameter(1, proj.v());
2275         }
2276       }
2277     }
2278   }
2279 
2280   if(withBackup && backup) {
2281     double sicnMinAfter;
2282     double sicnAvgAfter;
2283     computeSICN(patchPtr->elements, sicnMinAfter, sicnAvgAfter);
2284     if(sicnMinAfter < sicnMinBefore) { backup->restore(); }
2285   }
2286 
2287   if(backup) delete backup;
2288 
2289   return true;
2290 }
2291 
invertTrianglesIfNecessary(const std::vector<std::array<double,2>> & points,std::vector<std::array<uint32_t,3>> & tris)2292 void invertTrianglesIfNecessary(
2293   const std::vector<std::array<double, 2> > &points,
2294   std::vector<std::array<uint32_t, 3> > &tris)
2295 {
2296   double area = 0.;
2297   for(size_t i = 0; i < tris.size(); ++i) {
2298     area +=
2299       triangleArea(points[tris[i][0]], points[tris[i][1]], points[tris[i][2]]);
2300   }
2301   if(area < 0.) {
2302     Msg::Debug(
2303       "invert 2D triangle orientations (total area was %f for %li tris)", area,
2304       tris.size());
2305     for(size_t i = 0; i < tris.size(); ++i) {
2306       uint32_t v1 = tris[i][1];
2307       tris[i][1] = tris[i][2];
2308       tris[i][2] = v1;
2309     }
2310   }
2311 }
2312 
smoothWithWinslowUntangler(PlanarMethod planar,int iterMax,double withBackup,bool finalCADprojection,double nonPlanarRatioMax)2313 bool GeometryOptimizer::smoothWithWinslowUntangler(PlanarMethod planar,
2314                                                    int iterMax,
2315                                                    double withBackup,
2316                                                    bool finalCADprojection,
2317                                                    double nonPlanarRatioMax)
2318 {
2319   if(nFree == 0) {
2320     Msg::Debug("geometry optimize: no free vertices, nothing to optimize");
2321     return true;
2322   }
2323   if(nFree == points.size()) {
2324     Msg::Debug("geometry optimize: no locked vertices, cannot optimize");
2325     return false;
2326   }
2327   vector<bool> locked(points.size(), false);
2328   for(size_t v = nFree; v < points.size(); ++v) { locked[v] = true; }
2329   Msg::Debug("- smoothWithWinslowUntangler: method %i, %li/%li free vertices",
2330              planar, nFree, points.size());
2331 
2332   /* Initial quality computation */
2333   double sicnMin, sicnAvg;
2334   computeSICN(patchPtr->elements, sicnMin, sicnAvg);
2335   if(DBG_VIZU_U) {
2336     GeoLog::add(patchPtr->elements, "optim_NU_IN_" + std::to_string(sicnMin));
2337     GeoLog::flush();
2338   }
2339 
2340   /* Backup of current geometry */
2341   PatchGeometryBackup backup(*patchPtr);
2342 
2343   double t1 = Cpu();
2344 
2345   /* Get planar points for the untangler */
2346   std::vector<vec2> points_2D(points.size());
2347   mean_plane mp;
2348   SVector3 normal, tangent, binormal;
2349   SPoint3 pop;
2350   if(planar == PlanarMethod::MeanPlane) {
2351     /* Compute the mean plane from the locked vertices */
2352     std::vector<SPoint3> vInit;
2353     vInit.reserve(points.size() - nFree);
2354     for(size_t v = nFree; v < points.size(); ++v) {
2355       vInit.push_back(SPoint3(points[v][0], points[v][1], points[v][2]));
2356     }
2357     computeMeanPlaneSimple(vInit, mp);
2358     double denom = std::pow(mp.a, 2) + std::pow(mp.b, 2) + std::pow(mp.c, 2);
2359     if(denom < 1.e-16) {
2360       Msg::Warning("geometry optimize: invalid mean plane");
2361       return false;
2362     }
2363     normal = SVector3(mp.a, mp.b, mp.c);
2364     buildOrthoBasis(normal, tangent, binormal);
2365     pop = SPoint3(mp.x, mp.y, mp.z);
2366 
2367     /* Projects points on mean plane, with 2D coordinates, before smoothing */
2368     {
2369       double omax = 0.;
2370       double du[2] = {DBL_MAX,-DBL_MAX};
2371       double dv[2] = {DBL_MAX,-DBL_MAX};
2372       for(size_t v = 0; v < points.size(); ++v) {
2373         SPoint3 p(points[v][0], points[v][1], points[v][2]);
2374         points_2D[v] = {dot(p - pop, tangent), dot(p - pop, binormal)};
2375         omax = std::max(omax,std::abs(dot(p - pop, normal)));
2376         du[0] = std::min(du[0],points_2D[v][0]);
2377         du[1] = std::max(du[1],points_2D[v][0]);
2378         dv[0] = std::min(dv[0],points_2D[v][1]);
2379         dv[1] = std::max(dv[1],points_2D[v][1]);
2380       }
2381       double denom = std::max(du[1]-du[0],dv[1]-dv[0]);
2382       if (denom < 1.e-14) return false;
2383       double ratio = omax / denom;
2384       if (ratio > nonPlanarRatioMax) {
2385         if (this->patchPtr && this->patchPtr->gf) {
2386           Msg::Debug("- Face %i: cancel mean plane winslow untangling because non planar ratio = %.3f > %.3f",
2387               this->patchPtr->gf->tag(),
2388               ratio, nonPlanarRatioMax);
2389         } else {
2390           Msg::Debug("- Untangling: cancel mean plane winslow untangling because non planar ratio = %.3f > %.3f",
2391               ratio, nonPlanarRatioMax);
2392         }
2393         return false;
2394       }
2395     }
2396   }
2397   else if(planar == PlanarMethod::ParamCAD) {
2398     std::unordered_map<MVertex *, SPoint2> vertexParam;
2399     bool okp = getPlanarParametrization(*patchPtr, vertexParam);
2400     if(!okp) {
2401       Msg::Debug("- Untangle: failed to get planar parametrization from CAD");
2402       return false;
2403     }
2404     for(size_t v = 0; v < points.size(); ++v) {
2405       auto it = vertexParam.find(new2old[v]);
2406       points_2D[v] = {it->second.x(), it->second.y()};
2407     }
2408 
2409     if(DBG_VIZU_U) {
2410       for(size_t i = 0; i < quads.size(); ++i) {
2411         std::vector<vec3> pts = {
2412           {points_2D[quads[i][0]][0], points_2D[quads[i][0]][1], 0.},
2413           {points_2D[quads[i][1]][0], points_2D[quads[i][1]][1], 0.},
2414           {points_2D[quads[i][2]][0], points_2D[quads[i][2]][1], 0.},
2415           {points_2D[quads[i][3]][0], points_2D[quads[i][3]][1], 0.}};
2416         GeoLog::add(pts, 0., "nancy_b" + std::to_string(sicnMin));
2417       }
2418     }
2419   }
2420 
2421   /* Triangles from quads */
2422   std::vector<std::array<vec2, 3> > triIdealShapes;
2423   std::vector<std::array<uint32_t, 3> > triangles;
2424   bool preserveQuadAnisotropy = false;
2425   buildTrianglesAndTargetsFromElements(points_2D, quads, triangles,
2426                                        triIdealShapes, preserveQuadAnisotropy);
2427 
2428   /* Planar smoothing with Winslow untangler */
2429   Msg::Debug("- Untangle/Smooth quad mesh (%li quads -> %li optim tris, %li "
2430              "vertices, SICN min initial: %f) ...",
2431              quads.size(), triangles.size(), points_2D.size(), sicnMin);
2432   invertTrianglesIfNecessary(points_2D, triangles);
2433   int iterMaxOuter = iterMax;
2434   int iterMaxInner = 500;
2435   int nFailMax = 5;
2436   double lambda = 1. / 127.;
2437   int verbosity = 0;
2438   double timeMax = 1000;
2439   if(Msg::GetVerbosity() >= 99) verbosity = 1;
2440   std::string pp = "Debug   : ---- ";
2441   bool oku =
2442     untangle_triangles_2D(points_2D, locked, triangles, triIdealShapes, lambda,
2443                           iterMaxInner, iterMaxOuter, nFailMax, timeMax);
2444   if(!oku) { Msg::Debug("---- failed to untangle"); }
2445   // {
2446   //   for (size_t v = 0; v < points_2D.size(); ++v) {
2447   //     GeoLog::add({points_2D[v][0],points_2D[v][1],0.},double(locked[v]),"2D_aftersmooth");
2448   //   }
2449   //   GeoLog::flush();
2450   // }
2451 
2452   /* Project back to surface */
2453   if(planar == PlanarMethod::MeanPlane) {
2454     SPoint3 proj;
2455     // {
2456     //   for (size_t v = 0; v < points_2D.size(); ++v) {
2457     //     SPoint3 onPlane = SPoint3(mp.x,mp.y,mp.z)
2458     //       + points_2D[v][0] * tangent
2459     //       + points_2D[v][1] * binormal;
2460     //     GeoLog::add(onPlane,double(locked[v]),"reproj_plane");
2461     //   }
2462     //   GeoLog::flush();
2463     // }
2464     for(size_t v = 0; v < nFree; ++v) {
2465       SPoint3 onPlane =
2466         pop + points_2D[v][0] * tangent + points_2D[v][1] * binormal;
2467 
2468       GPoint proj;
2469       if(sp != nullptr) {
2470         proj = sp->closestPoint(onPlane.data(), false, false);
2471         if(proj.succeeded()) {
2472           points[v] = {proj.x(), proj.y(), proj.z()};
2473           if(uvs.size() > 0) { uvs[v] = {proj.u(), proj.v()}; }
2474         }
2475         else {
2476           Msg::Warning("sp proj failed");
2477         }
2478       }
2479       if(patchPtr->gf->haveParametrization() &&
2480          (!proj.succeeded() || finalCADprojection)) {
2481         double initGuess[2] = {0., 0.};
2482         if(uvs.size() > 0) {
2483           initGuess[0] = uvs[v][0];
2484           initGuess[1] = uvs[v][1];
2485         }
2486         proj = patchPtr->gf->closestPoint(onPlane.data(), initGuess);
2487         if(proj.succeeded()) {
2488           points[v] = {proj.x(), proj.y(), proj.z()};
2489           if(uvs.size() > 0) { uvs[v] = {proj.u(), proj.v()}; }
2490         }
2491         else {
2492           Msg::Warning("CAD proj failed");
2493         }
2494       }
2495     }
2496     /* Update the patch */
2497     for(size_t v = 0; v < nFree; ++v) {
2498       new2old[v]->setXYZ(points[v][0], points[v][1], points[v][2]);
2499       if(uvs.size()) {
2500         new2old[v]->setParameter(0, uvs[v][0]);
2501         new2old[v]->setParameter(1, uvs[v][1]);
2502       }
2503     }
2504   }
2505   else if(planar == PlanarMethod::ParamCAD) {
2506     for(size_t v = 0; v < nFree; ++v) {
2507       GPoint p = patchPtr->gf->point(points_2D[v][0], points_2D[v][1]);
2508       new2old[v]->setXYZ(p.x(), p.y(), p.z());
2509       new2old[v]->setParameter(0, points_2D[v][0]);
2510       new2old[v]->setParameter(1, points_2D[v][1]);
2511     }
2512   }
2513 
2514   double t2 = Cpu();
2515   double sicnMinA, sicnAvgA;
2516   computeSICN(patchPtr->elements, sicnMinA, sicnAvgA);
2517 
2518   if(DBG_VIZU_U) {
2519     GeoLog::add(patchPtr->elements, "optim_NU_OUT_" + std::to_string(sicnMinA));
2520     GeoLog::flush();
2521     if(planar == PlanarMethod::ParamCAD) {
2522       for(size_t i = 0; i < quads.size(); ++i) {
2523         std::vector<vec3> pts = {
2524           {points_2D[quads[i][0]][0], points_2D[quads[i][0]][1], 0.},
2525           {points_2D[quads[i][1]][0], points_2D[quads[i][1]][1], 0.},
2526           {points_2D[quads[i][2]][0], points_2D[quads[i][2]][1], 0.},
2527           {points_2D[quads[i][3]][0], points_2D[quads[i][3]][1], 0.}};
2528         GeoLog::add(pts, 0., "nancy_a" + std::to_string(sicnMinA));
2529       }
2530       GeoLog::flush();
2531     }
2532   }
2533 
2534   bool keep = true;
2535   if(sicnMinA < sicnMin && sicnAvgA < 0.99 * sicnAvg) keep = false;
2536   if(sicnMinA < 0. && sicnMinA < sicnMin) keep = false;
2537   if(sicnMinA < 0.75 * sicnMin) keep = false;
2538   if(keep) {
2539     Msg::Debug("optimize with Winslow untangler (uv param: %i): %li/%li free "
2540                "vertices, %li elements, SICN min: %.3f -> %.3f, SICN avg: %.3f "
2541                "-> %.3f, time: %.3fsec",
2542                planar, patchPtr->intVertices.size(), points.size(),
2543                patchPtr->elements.size(), sicnMin, sicnMinA, sicnAvg, sicnAvgA,
2544                t2 - t1);
2545   }
2546   else {
2547     backup.restore();
2548     Msg::Debug("optimize with Winslow untangler (uv param: %i): %li/%li free "
2549                "vertices, %li elements, SICN min: %.3f -> %.3f, SICN avg: %.3f "
2550                "-> %.3f, time: %.3fsec, rollback",
2551                planar, patchPtr->intVertices.size(), points.size(),
2552                patchPtr->elements.size(), sicnMin, sicnMinA, sicnAvg, sicnAvgA,
2553                t2 - t1);
2554     return false;
2555   }
2556 
2557   return true;
2558 }
2559 
optimizeGeometryQuadqs(GModel * gm)2560 bool optimizeGeometryQuadqs(GModel *gm)
2561 {
2562   Msg::Info("Optimize geometry of quad mesh ...");
2563 
2564   vector<GFace *> faces = model_faces(gm);
2565   for(size_t f = 0; f < faces.size(); ++f) {
2566     GFace *gf = faces[f];
2567     if(CTX::instance()->mesh.meshOnlyVisible && !gf->getVisibility()) continue;
2568     if(CTX::instance()->debugSurface > 0 &&
2569        gf->tag() != CTX::instance()->debugSurface)
2570       continue;
2571     if(gf->triangles.size() > 0 || gf->quadrangles.size() == 0) continue;
2572 
2573     SurfaceProjector sp;
2574     fillSurfaceProjector(gf, &sp);
2575     double tMax = double(gf->mesh_vertices.size()) / 100.;
2576     bool withBackup = true;
2577     optimizeGeometryQuadMesh(gf, &sp, tMax, withBackup);
2578     quadMeshSpecialAcuteCornerOptimization(gf, &sp);
2579   }
2580 
2581   return true;
2582 }
2583