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