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 ¢er, 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 ¢er,
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 ¢er,
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 ¢er,
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 ¢er,
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