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 "qmtDiskQuadrangulationRemeshing.h"
9
10 /* System includes */
11 // #include <vector>
12 // #include <array>
13 // #include <unordered_map>
14 // #include <cstdint>
15 // #include <cmath>
16 // #include <queue>
17 // #include <algorithm>
18
19 /* Gmsh includes */
20 #include "GmshMessage.h"
21 #include "OS.h"
22 #include "GVertex.h"
23 #include "GEdge.h"
24 #include "GFace.h"
25 #include "GModel.h"
26 #include "MVertex.h"
27 #include "MLine.h"
28 #include "MElement.h"
29 #include "MTriangle.h"
30 #include "MQuadrangle.h"
31 #include "BackgroundMesh.h"
32 #include "StringUtils.h"
33
34 /* QuadMeshingTools includes */
35 #include "cppUtils.h"
36 #include "qmtMeshUtils.h"
37 #include "qmtMeshGeometryOptimization.h"
38 #include "dataDiskQuadrangulationsSplit.hpp" // list of disk quadrangulations as many strings
39
40 using namespace CppUtils;
41
42 constexpr bool PARANO_QUALITY = false;
43
44 namespace QMT {
45 using id = uint32_t;
46 using id4 = std::array<id, 4>;
47
48 struct vidHash {
operator ()QMT::vidHash49 size_t operator()(const std::vector<id> &p) const noexcept
50 {
51 uint32_t hash = 0;
52 for(size_t i = 0; i < p.size(); ++i) {
53 hash += p[i];
54 hash += hash << 10;
55 hash ^= hash >> 6;
56 }
57 hash += hash << 3;
58 hash ^= hash >> 11;
59 hash += hash << 15;
60 return hash;
61 }
62 };
63
64 using std::unordered_map; /* robin_hood unordered_map much faster */
65 using std::vector;
66 using Quadrangulation = std::vector<std::array<id, 4> >;
67
68 vector<vector<Quadrangulation> > B_disk_quadrangulations;
69 vector<unordered_map<vector<id>, vector<id>, vidHash> > B_BVL_ids;
70
load_disk_quadrangulations_from_raw_data()71 bool load_disk_quadrangulations_from_raw_data()
72 {
73 if(B_disk_quadrangulations.size() != 0) return false;
74
75 Msg::Info("loading disk quadrangulations ...");
76 B_disk_quadrangulations.reserve(20);
77 B_BVL_ids.reserve(20);
78 // std::string data(disk_quadrangulations);
79 std::string data;
80 diskQuadrangulationConcat(data);
81 vector<std::string> lines = SplitString(data, '\n');
82 Quadrangulation qdrl;
83 vector<std::string> numbers;
84 vector<id> bdrValLoop;
85 size_t lastB = 0;
86 for(size_t i = 0; i < lines.size(); ++i) {
87 numbers = SplitString(lines[i], ' ');
88 if(numbers.size() < 7) continue;
89 size_t B = std::stoi(numbers[0]);
90 size_t I = std::stoi(numbers[1]);
91 size_t Q = std::stoi(numbers[2]);
92 if(numbers.size() != 3 + 4 * Q) {
93 Msg::Warning("load_disk_quadrangulations | wrong sizes: B=%li, I=%li, "
94 "Q=%li and numbers.size = %li",
95 B, I, Q, numbers.size());
96 continue;
97 }
98 lastB = B;
99 qdrl.resize(Q);
100 for(size_t j = 0; j < Q; ++j) {
101 for(size_t lv = 0; lv < 4; ++lv) {
102 qdrl[j][lv] = std::stoi(numbers[3 + 4 * j + lv]);
103 }
104 }
105
106 if(B >= B_disk_quadrangulations.size()) {
107 B_disk_quadrangulations.resize(B + 1);
108 B_disk_quadrangulations[B].reserve(1000);
109 B_BVL_ids.resize(B + 1);
110 }
111
112 id qId = B_disk_quadrangulations[B].size();
113 B_disk_quadrangulations[B].push_back(qdrl);
114
115 /* Assumes:
116 * - first B vertices are on the boundary
117 * - canonical valence ordering according to boundary valence loop
118 * (should be compatible with the generator) */
119 bdrValLoop.clear();
120 bdrValLoop.resize(B, 0);
121 for(size_t j = 0; j < Q; ++j)
122 for(size_t lv = 0; lv < 4; ++lv) {
123 id v = qdrl[j][lv];
124 if(v < B) bdrValLoop[v] += 1;
125 }
126 B_BVL_ids[B][bdrValLoop].push_back(qId);
127 }
128 Msg::Info("%li disk quadrangulations loaded", lines.size());
129
130 /* Add basic quadrangulations of the disk with no interior vertices
131 * for very high valences cases (due to "bugs" in quad meshers, but
132 * this happens frequently) */
133 for(size_t B = lastB + 1; B < 200; ++B) {
134 if(B % 2 != 0) continue;
135 size_t I = 0;
136 size_t Q = B / 2 - 1;
137
138 qdrl.resize(Q);
139 for(size_t j = 0; j < Q; ++j) {
140 id v0 = j;
141 id v1 = j + 1;
142 id v2 = B - 1 - j - 1;
143 id v3 = B - 1 - j;
144 qdrl[j] = {v0, v1, v2, v3};
145 }
146
147 if(B >= B_disk_quadrangulations.size()) {
148 B_disk_quadrangulations.resize(B + 1);
149 B_BVL_ids.resize(B + 1);
150 }
151
152 id qId = B_disk_quadrangulations[B].size();
153 B_disk_quadrangulations[B].push_back(qdrl);
154
155 bdrValLoop.clear();
156 bdrValLoop.resize(B, 0);
157 for(size_t j = 0; j < Q; ++j)
158 for(size_t lv = 0; lv < 4; ++lv) {
159 id v = qdrl[j][lv];
160 if(v < B) bdrValLoop[v] += 1;
161 }
162 B_BVL_ids[B][bdrValLoop].push_back(qId);
163 }
164
165 return true;
166 }
167
computeQuadMeshValences(const vector<id4> & quads,vector<int> & valence)168 bool computeQuadMeshValences(const vector<id4> &quads, vector<int> &valence)
169 {
170 valence.clear();
171 valence.reserve(quads.size() * 4);
172 for(size_t f = 0; f < quads.size(); ++f)
173 for(size_t lv = 0; lv < 4; ++lv) {
174 size_t v = quads[f][lv];
175 if(v >= valence.size()) valence.resize(v + 1, 0);
176 valence[v] += 1;
177 }
178 return true;
179 }
180
computeInputIrregularity(const std::vector<MElement * > & elements,const std::vector<MVertex * > & intVertices,const std::vector<MVertex * > & bdrVertices,const std::vector<int> & bndIdealValence,const std::vector<std::pair<int,int>> & bndAllowedValenceRange)181 double computeInputIrregularity(
182 const std::vector<MElement *> &elements,
183 const std::vector<MVertex *> &intVertices,
184 const std::vector<MVertex *> &bdrVertices,
185 const std::vector<int> &bndIdealValence,
186 const std::vector<std::pair<int, int> > &bndAllowedValenceRange)
187 {
188 /* Warning: the two way to compute irregularity must be the same than
189 * computeIrregularity(), because the values are compared */
190 double irregularity = 0.;
191 std::unordered_map<MVertex *, int> valence;
192 for(MElement *e : elements)
193 for(size_t lv = 0; lv < 4; ++lv) { valence[e->getVertex(lv)] += 1; }
194 /* Boundary vertices */
195 for(size_t bv = 0; bv < bdrVertices.size(); ++bv) {
196 MVertex *v = bdrVertices[bv];
197 if(valence[v] < bndAllowedValenceRange[bv].first) return DBL_MAX;
198 if(valence[v] > bndAllowedValenceRange[bv].second) return DBL_MAX;
199 irregularity += std::pow(bndIdealValence[bv] - valence[v], 2);
200 // if (bndIdealValence[bv] <= 1 && valence[v] >= 2) { /* probably making a
201 // 6+ ... */
202 // irregularity += 1000;
203 // } else {
204 // irregularity += 10*std::pow(bndIdealValence[bv]-valence[v],2);
205 // }
206 }
207 /* Interior vertices */
208 for(size_t iv = 0; iv < intVertices.size(); ++iv) {
209 irregularity += std::pow(4 - valence[intVertices[iv]], 2);
210 }
211 return irregularity;
212 }
213
computeIrregularity(const vector<id4> & quads,const vector<int> & valence,const std::vector<int> & bndIdealValence,const std::vector<std::pair<int,int>> & bndAllowedValenceRange)214 double computeIrregularity(
215 const vector<id4> &quads, const vector<int> &valence,
216 const std::vector<int> &bndIdealValence,
217 const std::vector<std::pair<int, int> > &bndAllowedValenceRange)
218 {
219 /* Warning: the two way to compute irregularity must be the same than
220 * computeInputIrregularity(), because the values are compared */
221
222 double irregularity = 0.;
223 /* Boundary vertices */
224 for(size_t bv = 0; bv < bndIdealValence.size(); ++bv) {
225 if(valence[bv] < bndAllowedValenceRange[bv].first) return DBL_MAX;
226 if(valence[bv] > bndAllowedValenceRange[bv].second) return DBL_MAX;
227 irregularity += std::pow(bndIdealValence[bv] - valence[bv], 2);
228 // if (bndIdealValence[bv] <= 1 && valence[bv] >= 2) { /* probably making
229 // a 6+ ... */
230 // irregularity += 1000;
231 // } else {
232 // irregularity += 10*std::pow(bndIdealValence[bv]-valence[bv],2);
233 // }
234 }
235 /* Interior vertices */
236 for(size_t iv = bndIdealValence.size(); iv < valence.size(); ++iv) {
237 irregularity += std::pow(4 - valence[iv], 2);
238 }
239 return irregularity;
240 }
241
computeBestMatchingConfiguration(const vector<id4> & quads,const vector<int> & valence,const vector<int> & bndIdealValence,const vector<std::pair<int,int>> & bndAllowedValenceRange,int & rotation,double & irregularity)242 bool computeBestMatchingConfiguration(
243 const vector<id4> &quads, const vector<int> &valence,
244 const vector<int> &bndIdealValence,
245 const vector<std::pair<int, int> > &bndAllowedValenceRange, int &rotation,
246 double &irregularity)
247 {
248 double best = DBL_MAX;
249 int rot = 0;
250 vector<int> biv = bndIdealValence;
251 vector<std::pair<int, int> > bav = bndAllowedValenceRange;
252
253 /* Initial config */
254 {
255 double irreg = computeIrregularity(quads, valence, biv, bav);
256 if(irreg < best) {
257 best = irreg;
258 rotation = rot;
259 }
260 }
261
262 /* Forward rotation */
263 for(size_t r = 1; r < biv.size(); ++r) {
264 rot += 1;
265 std::rotate(biv.begin(), biv.begin() + 1, biv.end());
266 std::rotate(bav.begin(), bav.begin() + 1, bav.end());
267 double irreg = computeIrregularity(quads, valence, biv, bav);
268 if(irreg < best) {
269 best = irreg;
270 rotation = rot;
271 }
272 }
273
274 /* Try in reverse order */
275 rot = 0;
276 biv = bndIdealValence;
277 bav = bndAllowedValenceRange;
278 std::reverse(biv.begin(), biv.end());
279 std::reverse(bav.begin(), bav.end());
280 for(size_t r = 1; r < biv.size(); ++r) {
281 rot -= 1;
282 std::rotate(biv.begin(), biv.begin() + 1, biv.end());
283 std::rotate(bav.begin(), bav.begin() + 1, bav.end());
284 double irreg = computeIrregularity(quads, valence, biv, bav);
285 if(irreg < best) {
286 best = irreg;
287 rotation = rot;
288 }
289 }
290
291 irregularity = best;
292 return (best != DBL_MAX);
293 }
294
295 /* WARNING: GFace is not modified, just the "floating" MVertex
296 * and MQuadrangle are created, they must be inserted in the GFace
297 * later is the pattern is kept.
298 * The vertex positions are random ! Need geometric smoothing after */
getDiskQuadrangulationRemeshing(GFace * gf,const std::vector<MVertex * > & bnd,int rotation,const std::vector<id4> & quads,std::vector<MVertex * > & newVertices,std::vector<MElement * > & newElements)299 bool getDiskQuadrangulationRemeshing(
300 GFace *gf, const std::vector<MVertex *> &bnd,
301 int rotation, /* rotation to apply to input */
302 const std::vector<id4> &quads, /* pattern */
303 std::vector<MVertex *> &newVertices, /* new vertices inside the cavity */
304 std::vector<MElement *> &newElements /* new quads inside the cavity */
305 )
306 {
307 if(bnd.size() < 4) return false;
308
309 const double EPS_RANDOM = 1.e-16;
310
311 std::vector<MVertex *> bndr = bnd;
312 if(rotation > 0) {
313 std::rotate(bndr.begin(), bndr.begin() + (size_t)rotation, bndr.end());
314 }
315 else if(rotation < 0) {
316 std::reverse(bndr.begin(), bndr.end());
317 std::rotate(bndr.begin(), bndr.begin() + (size_t)std::abs(rotation),
318 bndr.end());
319 }
320
321 /* Default position/uv for new vertices */
322 SPoint3 defaultPoint;
323 SPoint2 defaultParam;
324 size_t num = (size_t)-1;
325 for(MVertex *v : bnd) {
326 if(v->onWhat() == gf && v->getNum() < num) {
327 defaultPoint = v->point();
328 v->getParameter(0, defaultParam[0]);
329 v->getParameter(1, defaultParam[1]);
330 num = v->getNum();
331 }
332 }
333 if(num == (size_t)-1) { /* No bdr vertex inside face */
334 defaultPoint = bndr[0]->point();
335 reparamMeshVertexOnFace(bndr[0], gf, defaultParam);
336 }
337
338 size_t count = 1;
339 unordered_map<id, MVertex *> pv2mv;
340 for(size_t f = 0; f < quads.size(); ++f) {
341 std::array<MVertex *, 4> vert;
342 for(size_t lv = 0; lv < 4; ++lv) {
343 size_t pv = quads[f][lv];
344 if(pv < bndr.size()) { vert[lv] = bndr[pv]; }
345 else {
346 auto it = pv2mv.find(pv);
347 if(it == pv2mv.end()) {
348 SVector3 p = defaultPoint;
349 p.data()[0] += count * EPS_RANDOM;
350 p.data()[1] += count * EPS_RANDOM;
351 p.data()[2] += count * EPS_RANDOM;
352 double uv[2] = {defaultParam.x(), defaultParam.y()};
353 uv[0] = uv[0] + count * EPS_RANDOM;
354 uv[1] = uv[1] + count * EPS_RANDOM;
355 MVertex *mv =
356 new MFaceVertex(p.x(), p.y(), p.z(), gf, uv[0], uv[1]);
357 pv2mv[pv] = mv;
358 vert[lv] = mv;
359 newVertices.push_back(mv);
360 count += 1;
361 }
362 else {
363 vert[lv] = it->second;
364 }
365 }
366 }
367 if(rotation < 0) { /* revert quad to keep coherent orientation */
368 std::reverse(vert.begin(), vert.end());
369 }
370 MQuadrangle *q = new MQuadrangle(vert[0], vert[1], vert[2], vert[3]);
371 newElements.push_back(q);
372 }
373
374 /* Ensure coherent orientation between the new mesh and the boundary */
375 bool oko =
376 orientElementsAccordingToBoundarySegment(bnd[0], bnd[1], newElements);
377 if(!oko) {
378 for(MVertex *v : newVertices) delete v;
379 for(MElement *e : newElements) delete e;
380 newVertices.clear();
381 newElements.clear();
382 Msg::Error(
383 "getDiskQuadrangulationRemeshing: bdr quad edge not found, weird");
384 return false;
385 }
386
387 return true;
388 }
389
smallCavitySmoothing(GFaceMeshPatch & patch,SurfaceProjector * sp,bool invertNormalsForQuality,GeomOptimStats & stats)390 bool smallCavitySmoothing(GFaceMeshPatch &patch, SurfaceProjector *sp,
391 bool invertNormalsForQuality, GeomOptimStats &stats)
392 {
393 if(patch.intVertices.size() == 0) {
394 computeSICN(patch.elements, stats.sicnMinBefore, stats.sicnAvgBefore);
395 computeSICN(patch.elements, stats.sicnMinAfter, stats.sicnAvgAfter);
396 return true;
397 }
398
399 computeSICN(patch.elements, stats.sicnMinBefore, stats.sicnAvgBefore);
400
401 GeometryOptimizer optu;
402 optu.initialize(patch, sp);
403
404 /* initialize geometry with laplacian smoothing (without surface projection)
405 */
406 optu.smoothWithKernel(SmoothingKernel::Laplacian,
407 SmoothingKernel::Laplacian, 0.1, 10, false, false,
408 1.e-5, 1.e-5, false, false);
409
410 /* advanced untangling/smoothing on mean plane */
411 size_t iter = 5;
412 bool projectOnCad = true;
413 bool withBackup = false;
414 GeometryOptimizer::PlanarMethod me =
415 GeometryOptimizer::PlanarMethod::MeanPlane;
416 bool oku =
417 optu.smoothWithWinslowUntangler(me, iter, withBackup, projectOnCad);
418 computeSICN(patch.elements, stats.sicnMinAfter, stats.sicnAvgAfter);
419
420 return oku;
421
422 // bool cadInitOk = false;
423 // if (haveNiceParametrization(patch.gf)) {
424 // PatchGeometryBackup backup(patch);
425 // /* Try pure UV smoothing in parameter space */
426 // int s0 = patchOptimizeGeometryGlobal(patch, stats);
427 // if (stats.sicnMinAfter > 0.) {
428 // cadInitOk = true;
429 // } else {
430 // backup.restore();
431 // }
432 // }
433
434 // /* Kernel smoothing (in parameter space or in 3D space with proj.) */
435 // GeomOptimOptions opt;
436 // opt.invertCADNormals = invertNormalsForQuality;
437 // opt.localLocking = true;
438 // opt.dxLocalMax = 1.e-5;
439 // opt.outerLoopIterMax = 100;
440 // opt.timeMax = 0.25 * double(patch.intVertices.size());
441 // if (cadInitOk) {
442 // opt.force3DwithProjection = false;
443 // opt.useDmoIfSICNbelow = 0.5;
444 // } else {
445 // opt.sp = sp;
446 // opt.force3DwithProjection = true;
447 // }
448
449 // bool okk = patchOptimizeGeometryWithKernel(patch, opt, stats);
450 // return okk;
451 }
452
453 } // namespace QMT
454
455 using namespace QMT;
456
initDiskQuadrangulations()457 int initDiskQuadrangulations()
458 {
459 bool ok = load_disk_quadrangulations_from_raw_data();
460 if(!ok) return -1;
461 return 0;
462 }
463
remeshLocalWithDiskQuadrangulation(GFace * gf,const std::vector<MElement * > & elements,const std::vector<MVertex * > & intVertices,const std::vector<MVertex * > & bdrVertices,const std::vector<int> & bndIdealValence,const std::vector<std::pair<int,int>> & bndAllowedValenceRange,const std::vector<MElement * > & neighborsForGeometry,double minSICNrequired,bool invertNormalsForQuality,SurfaceProjector * sp,GFaceMeshDiff & diff)464 int remeshLocalWithDiskQuadrangulation(
465 GFace *gf, const std::vector<MElement *> &elements,
466 const std::vector<MVertex *> &intVertices,
467 const std::vector<MVertex *> &bdrVertices,
468 const std::vector<int> &bndIdealValence,
469 const std::vector<std::pair<int, int> > &bndAllowedValenceRange,
470 const std::vector<MElement *> &neighborsForGeometry, double minSICNrequired,
471 bool invertNormalsForQuality, SurfaceProjector *sp, GFaceMeshDiff &diff)
472 {
473 if(QMT::B_disk_quadrangulations.size() == 0) {
474 Msg::Error("Missing disk quadrangulation database, call "
475 "initDiskQuadrangulations() before");
476 return -1;
477 }
478 if(bdrVertices.size() == 0) {
479 Msg::Error("disk quadrangulation remeshing: no boundary vertices",
480 bdrVertices.size());
481 return -1;
482 }
483
484 /* Get disk quadrangulations with the same boundary size */
485 const vector<vector<id4> > *small_patterns = NULL;
486 if(bdrVertices.size() < B_disk_quadrangulations.size() &&
487 B_disk_quadrangulations[bdrVertices.size()].size() > 0) {
488 small_patterns = &(B_disk_quadrangulations[bdrVertices.size()]);
489 }
490 else {
491 // TODO: a simple remeshing by using parallel quads ?
492 Msg::Warning("disk quadrangulation remeshing: no pattern for input "
493 "boundary loop size (%li bdr vertices)",
494 bdrVertices.size());
495 return -1;
496 }
497 const vector<vector<id4> > &qmeshes = *small_patterns;
498
499 double irregInput =
500 computeInputIrregularity(elements, intVertices, bdrVertices,
501 bndIdealValence, bndAllowedValenceRange);
502
503 /* For each disk quadrangulation, compute vertex valence and
504 * check matching with input patch, considering all rotations.
505 * Store the compatible matchings with their irregularity score. */
506 vector<int> valence;
507 std::vector<std::pair<double, std::pair<size_t, int> > >
508 irregularity_pattern_rotation;
509 for(size_t i = 0; i < qmeshes.size(); ++i) {
510 const vector<id4> &quads = qmeshes[i];
511 computeQuadMeshValences(quads, valence);
512 double irregularity = DBL_MAX;
513 int rotation = 0;
514 bool found = computeBestMatchingConfiguration(
515 quads, valence, bndIdealValence, bndAllowedValenceRange, rotation,
516 irregularity);
517 if(found && irregularity < irregInput) {
518 irregularity_pattern_rotation.push_back({irregularity, {i, rotation}});
519 }
520 }
521 if(irregularity_pattern_rotation.size() == 0) {
522 Msg::Debug("disk quadrangulation remeshing: no pattern matching input "
523 "allowed valence range (%li bdr vertices)",
524 bdrVertices.size());
525 return -1; /* no pattern matching allowed valence range */
526 }
527 std::sort(irregularity_pattern_rotation.begin(),
528 irregularity_pattern_rotation.end());
529
530 /* Keep the best pattern for which it is possible to find a
531 * untangled and sufficient quality geometry */
532 size_t N = 5; /* maximum number of pattern tested */
533 if(irregularity_pattern_rotation.size() < N)
534 N = irregularity_pattern_rotation.size();
535
536 bool geometryOk = false;
537 for(size_t i = 0; i < N; ++i) {
538 size_t no = irregularity_pattern_rotation[i].second.first;
539 int rotation = irregularity_pattern_rotation[i].second.second;
540 const vector<id4> &quads = qmeshes[no];
541
542 /* New GFace mesh patch */
543 GFaceMeshPatch patch;
544 patch.gf = gf;
545 patch.bdrVertices = {bdrVertices};
546
547 bool ok = getDiskQuadrangulationRemeshing(
548 gf, bdrVertices, rotation, quads, patch.intVertices, patch.elements);
549 if(!ok) {
550 Msg::Debug(
551 "disk quandrangulation remeshing: failed to remesh small cavity");
552 continue;
553 }
554 Msg::Debug("disk quadrangulation remeshing: try %li/%li, dq=[%li,%i], %li "
555 "bdr, %i->%i int vertices, %i->%i quads",
556 i, N, no, rotation, patch.bdrVertices.front().size(),
557 intVertices.size(), patch.intVertices.size(), elements.size(),
558 patch.elements.size());
559
560 /* Try to only move the interior vertices (in general, it is not enough) */
561 {
562 GeomOptimStats stats;
563 bool oks =
564 smallCavitySmoothing(patch, sp, invertNormalsForQuality, stats);
565 if(oks && stats.sicnMinAfter > minSICNrequired) {
566 geometryOk = true;
567 diff.after = patch; /* set the diff output ! */
568 break;
569 }
570 }
571
572 /* Try to move the interior and bdr vertices (which are not on CAD
573 * curve/corner) */
574 {
575 std::vector<MElement *> largerElements = patch.elements;
576 append(largerElements, neighborsForGeometry);
577 GFaceMeshPatch largerPatch;
578 bool okp = patchFromElements(gf, largerElements, largerPatch);
579 if(!okp || largerPatch.intVertices.size() == 0) continue;
580
581 PatchGeometryBackup bdrBackup(largerPatch);
582
583 Msg::Debug("try smoothing the extended cavity (%li -> %li free vertices)",
584 patch.intVertices.size(), largerPatch.intVertices.size());
585
586 GeomOptimStats stats;
587 bool oks =
588 smallCavitySmoothing(largerPatch, sp, invertNormalsForQuality, stats);
589 if(oks && stats.sicnMinAfter > minSICNrequired) {
590 geometryOk = true;
591 diff.after =
592 patch; /* set the diff output (the patch, not the largerPatch) ! */
593 break;
594 }
595 else {
596 /* restore boundary positions */
597 bdrBackup.restore();
598 }
599 }
600 }
601
602 if(!geometryOk) {
603 Msg::Debug("disk quadrangulation remeshing: failed to find valid geometry "
604 "(%li bdr vertices)",
605 bdrVertices.size());
606 return -1;
607 }
608
609 /* set the diff input */
610 diff.gf = gf;
611 diff.before.gf = gf;
612 diff.before.elements = elements;
613 diff.before.intVertices = intVertices;
614 diff.before.bdrVertices = {bdrVertices};
615
616 return 0;
617 }
618