GCC Code Coverage Report


Directory: ./
File: lib/exploragram/hexdom/quad_dominant.cpp
Date: 2026-09-07 02:37:58
Exec Total Coverage
Lines: 0 562 0.0%
Functions: 0 12 0.0%
Branches: 0 1922 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2022 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/fr/bruno-levy
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #include <exploragram/hexdom/quad_dominant.h>
41 #include <exploragram/hexdom/meshcomesh.h>
42 #include <exploragram/hexdom/PGP.h>
43 #include <exploragram/hexdom/basic.h>
44 #include <exploragram/hexdom/extra_connectivity.h>
45 #include <exploragram/hexdom/mesh_inspector.h>
46 #include <exploragram/hexdom/intersect_tools.h>
47 #include <exploragram/hexdom/polygon.h>
48
49 #include <geogram/numerics/matrix_util.h>
50 #include <geogram/basic/permutation.h>
51 #include <geogram/mesh/triangle_intersection.h>
52 #include <geogram/mesh/mesh_tetrahedralize.h>
53 #include <geogram/delaunay/delaunay.h>
54 #include <geogram/points/nn_search.h>
55 #include <geogram/points/colocate.h>
56 #include <geogram/mesh/mesh_repair.h>
57 #include <geogram/mesh/mesh_fill_holes.h>
58 #include <geogram/mesh/mesh_geometry.h>
59
60
61 #include <algorithm>
62 #include <queue>
63 #include <stack>
64 #include <map>
65
66 namespace GEO {
67
68 void export_boundary_with_uv(Mesh* m, Mesh* hex, const char* uv_name, const char* singular_name) {
69 Attribute<GEO::vec2> uv(hex->facet_corners.attributes(), uv_name);
70 Attribute<index_t> singtri(hex->facets.attributes(), singular_name);
71
72 Attribute<bool> has_param(m->cell_facets.attributes(), "has_param");
73 Attribute<vec3> UC(m->cell_corners.attributes(), "U");
74
75 // STEP 1: compute number of boundary vertices AND create a lookup table tet mesh vertex -> boundary surface vertex
76 vector<index_t> tetV_to_facetV(m->vertices.nb(), NOT_AN_ID);
77 index_t nb_boundary_V = 0;
78 FOR(c, m->cells.nb()) FOR(cf, m->cells.nb_facets(c)) {
79 if (NO_CELL != m->cells.adjacent(c, cf)) continue;
80 FOR(cfv, m->cells.facet_nb_vertices(c, cf)) {
81 index_t v = m->cells.facet_vertex(c, cf, cfv);
82 if (NOT_AN_ID == tetV_to_facetV[v]) {
83 tetV_to_facetV[v] = nb_boundary_V++;
84 }
85 }
86 }
87
88 // STEP 2: copy boundary vertices to the new mesh
89 hex->vertices.create_vertices(nb_boundary_V);
90 FOR(v, m->vertices.nb())
91 if (NOT_AN_ID != tetV_to_facetV[v])
92 hex->vertices.point(tetV_to_facetV[v]) = m->vertices.point(v);
93
94 // STEP 3: extract triangles and their parameterization (when possible)
95 FOR(c, m->cells.nb()) FOR(cf, m->cells.nb_facets(c)) {
96 if (m->cells.adjacent(c, cf) != NO_CELL) continue;
97 index_t tet_verts[3];
98 FOR(cfv, 3) {
99 tet_verts[cfv] = m->cells.facet_vertex(c, cf, cfv);
100 }
101
102 index_t tet_corners[3];
103 FOR(cfv, 3) FOR(test, 4) if (m->cell_corners.vertex(m->cells.corner(c, test)) == tet_verts[cfv])
104 tet_corners[cfv] = m->cells.corner(c, test);
105
106
107 vec3 lX[3], lU[3];
108 FOR(cfv, 3) {
109 lX[cfv] = m->vertices.point(tet_verts[cfv]);
110 lU[cfv] = UC[tet_corners[cfv]];
111 }
112
113 // STEP 2.2: non singular boundary triangles are easy to extract
114 index_t fid = hex->facets.create_triangle(
115 tetV_to_facetV[tet_verts[0]],
116 tetV_to_facetV[tet_verts[1]],
117 tetV_to_facetV[tet_verts[2]]
118 );
119
120 bool has_valid_2d_param = false;
121 if (has_param[m->cells.facet(c, cf)]) {
122 FOR(dim, 3) { // we are looking for the param dimension that goes inside the volume
123 if (lU[0][dim] != lU[1][dim] || lU[0][dim] != lU[2][dim]) continue;
124 has_valid_2d_param = true;
125 FOR(lv, 3) {
126 uv[hex->facets.corner(fid, lv)] = vec2(lU[lv][(dim + 1) % 3], lU[lv][(dim + 2) % 3]);
127 }
128 }
129 #if 0
130 // mirror the copy when necessary
131 if (det(uv[hex->facets.corner(fid, 1)] - uv[hex->facets.corner(fid, 0)],
132 uv[hex->facets.corner(fid, 2)] - uv[hex->facets.corner(fid, 0)]) < 0)
133 FOR(lv, 3) uv[hex->facets.corner(fid, lv)][0] *= -1.;
134 #endif
135 }
136
137 if (has_valid_2d_param) { // check param quality and invalidate it, if necessary
138 TrglGradient grd(lX[0], lX[1], lX[2]);
139 vec3 grduv[2];
140 FOR(d, 2) grduv[d] = grd.gradient_3d(uv[hex->facets.corner(fid, 0)][d], uv[hex->facets.corner(fid, 1)][d], uv[hex->facets.corner(fid, 2)][d]);
141 FOR(d, 2) FOR(dd, 3) if (Numeric::is_nan(grduv[d][dd])) has_valid_2d_param = false;
142 #if 0
143 if (grduv[0].length() > 10. * grduv[1].length()) has_valid_2d_param = false;
144 if (grduv[1].length() > 10. * grduv[0].length()) has_valid_2d_param = false;
145 if (std::abs(dot(normalize(grduv[0]), normalize(grduv[1]))) > cos(M_PI / 4.)) has_valid_2d_param = false;
146 #endif
147 }
148
149 singtri[fid] = !has_valid_2d_param;
150 }
151 }
152
153 static bool triangulate_surface_preserve_attributes(Mesh* m) {
154 bool result = true;
155 plop("triangulating surface after embedding isoUV");
156 vector<index_t> to_kill(m->facets.nb(), false);
157 FOR(f, m->facets.nb()) {
158 geo_assert(m->facets.nb_corners(f) >= 3);
159 if (m->facets.nb_corners(f) == 3) continue;
160
161 vector<vec3> pts;
162 FOR(lc, m->facets.nb_corners(f)) {
163 pts.push_back(X(m)[m->facets.vertex(f, lc)]);
164 }
165
166 vector<index_t> triangles;
167 bool success = Poly3d(pts).try_triangulate_minweight(triangles);
168 result = result && success;
169
170 geo_assert(0 == triangles.size() % 3);
171
172 FOR(new_face, triangles.size() / 3) {
173 index_t new_f = m->facets.create_triangle(
174 m->facets.vertex(f, triangles[3 * new_face + 0]),
175 m->facets.vertex(f, triangles[3 * new_face + 1]),
176 m->facets.vertex(f, triangles[3 * new_face + 2]));
177
178 to_kill.push_back(false);
179 m->facets.attributes().copy_item(new_f, f);
180 FOR(nfc,3) {
181 m->facet_corners.attributes().copy_item(m->facets.corner(new_f, nfc), m->facets.corner(f, triangles[3 * new_face + nfc]));
182 }
183 }
184 to_kill[f] = true;
185 }
186 m->facets.delete_elements(to_kill);
187 return result;
188 }
189
190
191 /**
192 * INPUT: facets with xyz geometry and uv coordinates s.t. no edge is of size 0
193 singular bool per triangle
194 * integer values of uv --- interpolated along edge 'e' --- matches on both side of 'e'
195 on presuppose que les U ont ete pre-snappes sur la grille entiere
196 * OUTPUT: facets with uv coordinates s.t.
197 * a new vertex is inserted (with interpolated xyz and uv) at every integer values of uv along edges
198 * nothing prevents some vertices around a facet to share the same uv's
199 ATTENTION: it modifies the parameterization when snaps grid corners on edges!
200 */
201
202 void split_edges_by_iso_uvs(Mesh* m, const char *uv_name, const char *singular_name) {
203 Attribute<GEO::vec2> uv(m->facet_corners.attributes(), uv_name);
204 Attribute<index_t> singular(m->facets.attributes(), singular_name);
205
206 index_t nb_init_facets = m->facets.nb();
207 Attribute<bool> added_vertices(m->vertices.attributes(), "added_vertices");
208 Attribute<index_t> orig_tri_fid(m->facets.attributes(), "orig_tri_fid");
209 Attribute<index_t> resp_facet(m->vertices.attributes(), "resp_facet");
210
211 typedef std::pair<index_t /*v_id*/, vec2 /*uv*/> NewCorner;
212 vector<vector<NewCorner> > new_corners(m->facet_corners.nb());
213
214 reach("create vertices and init arrays of NewCorner to insert on edges");
215 {
216 FacetsExtraConnectivity fec(m);
217 FOR(h, m->facet_corners.nb()) {
218 index_t opp = fec.opposite(h);
219
220 if (singular[fec.facet(h)]) continue; // the face is responsible for the edge if opp<h or if its opposite is singular
221 if (NOT_AN_ID != opp && !singular[fec.facet(opp)] && opp > h) continue;
222
223 vec2 lU[2] = { uv[h], uv[fec.next(h)] };
224
225 #if 0
226 bool trans_func_found = false;
227 index_t R;
228 vec2 T;
229 if (NOT_AN_ID != opp && !singular[fec.facet(opp)]) {
230 vec2 A = lU[1]-lU[0], B = uv[opp]-uv[fec.next(opp)], C = lU[0];
231 if (std::abs(A.length()-B.length())<1e-3) {
232 double mindist = std::numeric_limits<double>::max();
233 FOR(r,4) {
234 if ((A-B).length()<mindist) {
235 R = r;
236 mindist = (A-B).length();
237 }
238 A = vec2(-A[1], A[0]);
239 }
240 FOR(r, R) {
241 A = vec2(-A[1], A[0]);
242 C = vec2(-C[1], C[0]);
243 }
244 plop(mindist);
245 vec2 Ttmp = uv[fec.next(opp)] - C;
246 T = vec2(round(Ttmp[0]), round(Ttmp[1]));
247 trans_func_found = (mindist<1e-3 && (T-Ttmp).length()<1e-3);
248 } else {
249 plop("GNA?!");
250 }
251 if (!trans_func_found && NOT_AN_ID!=opp && !singular[fec.facet(opp)]) {
252 plop("ATTENTION, grids do not match");
253 singular[fec.facet(h)] = true;
254 singular[fec.facet(opp)] = true;
255 continue;
256 }
257 }
258 #endif
259 vector<double> coeff;
260 FOR(coord, 2) { // find barycentric coordinates of both u and v integer isos
261 double v[2] = { lU[0][coord], lU[1][coord] };
262 double from = floor(std::min(v[0], v[1])) + 1;
263 double to = std::max(v[0], v[1]);
264 if (to-from > 1000) continue;
265 for (double iso = from; iso < to; iso += 1.) {
266 double c = (iso - v[0]) / (v[1] - v[0]); // v[0] is far from v[1] (U was pre-snapped to integers with .05 tolerance)
267 if (!Numeric::is_nan(c) && c > 0 && c < 1) {
268 // vec2 u = lU[0] + c*(lU[1] - lU[0]);
269 // u[coord] = iso;
270 coeff.push_back(c);
271 }
272 }
273 }
274 std::sort(coeff.begin(), coeff.end(), std::less<double>()); // we need to sort in order to merge close values
275
276 vector<vec3> pts;
277 vector<vec2> lu;
278 vector<vec2> luopp;
279
280 FOR(i, coeff.size()) {
281 // a + c*(b-a) returns exactly a when a==b and no NaNs involved
282 // no need to worry about the cases when c==0 and c==1, because of the presnapping of the parameterization
283 vec3 pt = X(m)[fec.org(h)] + coeff[i] * (X(m)[fec.dest(h)] - X(m)[fec.org(h)]);
284 vec2 u = lU[0] + coeff[i] * (lU[1] - lU[0]);
285 vec2 uopp;
286 if (NOT_AN_ID != opp) uopp = uv[fec.next(opp)] + coeff[i] * (uv[opp] - uv[fec.next(opp)]);
287
288 FOR(d, 2) { // we must guarantee that new vertices have (at least) one integer component
289 if (std::abs(u[d] - round(u[d])) < 1e-10) {
290 u[d] = round(u[d]);
291 }
292
293 if (NOT_AN_ID != opp && std::abs(uopp[d] - round(uopp[d])) < 1e-10) {
294 uopp[d] = round(uopp[d]);
295 }
296 }
297
298 pts.push_back(pt);
299 lu.push_back(u);
300 if (NOT_AN_ID != opp) luopp.push_back(uopp);
301 }
302
303 // create vertices
304 index_t off = m->vertices.create_vertices(pts.size());
305 FOR(i, pts.size()) {
306 added_vertices[i+off] = true;
307 resp_facet[i+off] = orig_tri_fid[fec.facet(h)];
308 }
309 FOR(i, pts.size()) {
310 m->vertices.point(off + i) = pts[i];
311 new_corners[h].push_back(NewCorner(off + i, lu[i]));
312 if (NOT_AN_ID != opp)
313 new_corners[opp].push_back(NewCorner(off + i, luopp[i]));
314 }
315 if (NOT_AN_ID != opp)
316 std::reverse(new_corners[opp].begin(), new_corners[opp].end());
317 }
318 }
319
320 // now we have all the corners to insert, we create new facets for all the surface, old facets are to delete
321
322 reach("split edges");
323 FOR(f, nb_init_facets) {
324 vector<index_t> polyV;
325 vector<vec2> poly_uv;
326 FOR(fc, m->facets.nb_corners(f)) {
327 polyV.push_back(m->facets.vertex(f, fc));
328 index_t c = m->facets.corner(f, fc);
329 poly_uv.push_back(uv[c]);
330 FOR(i, new_corners[c].size()) {
331 polyV.push_back(new_corners[c][i].first);
332 poly_uv.push_back(new_corners[c][i].second);
333 }
334 }
335 index_t nf = m->facets.create_polygon(polyV);
336 m->facets.attributes().copy_item(nf ,f);
337 FOR(fc, m->facets.nb_corners(nf))
338 uv[m->facets.corner(nf, fc)] = poly_uv[fc];
339 }
340
341 reach("kill facets");
342 vector<index_t> to_kill(nb_init_facets, true); // kill old (pre-split) facets
343 to_kill.resize(m->facets.nb(), false);
344 m->facets.delete_elements(to_kill);
345 }
346
347
348 /**
349 * INPUT: facets with uv coordinates, where many vertices have integer values in uv
350 * OUTPUT: facets with uv coordinates, inclusing edges that have one coordinate of uv that is constant and integer valued
351 *
352 * remark: some polylines of these new edges are likely to be pre-images of edges of the regular grid...
353 */
354
355
356 void find_degenerate_facets(Mesh* m, vector<index_t> &degenerate) {
357 degenerate.clear();
358 FOR(f, m->facets.nb()) {
359 index_t nbv = m->facets.nb_corners(f);
360 // geo_assert(3 == m->facets.nb_corners(f));
361 FOR (c1, nbv) {
362 FOR (c2, nbv) {
363 if (c1 == c2) continue;
364 index_t v1 = m->facets.vertex(f, c1);
365 index_t v2 = m->facets.vertex(f, c2);
366
367 geo_assert( v1!= v2 );
368 if (X(m)[v1][0] == X(m)[v2][0] && X(m)[v1][1] == X(m)[v2][1] && X(m)[v1][2] == X(m)[v2][2]) {
369 degenerate.push_back(f);
370 }
371 }
372 }
373 }
374 }
375
376 void imprint(Mesh* m, const char *uv_name, const char *singular_name) {
377 {
378 Attribute<index_t> singular(m->facets.attributes(), singular_name);
379 Attribute<index_t> resp_facet(m->vertices.attributes(), "resp_facet");
380 Attribute<index_t> orig_tri_fid(m->facets.attributes(), "orig_tri_fid");
381 FOR(i, m->facets.nb() ) orig_tri_fid[i] = i;
382 FOR(v, m->vertices.nb()) resp_facet[v] = NOT_AN_ID;
383
384 check_no_intersecting_faces(m);
385 }
386
387
388
389 Mesh m_bak;
390 m_bak.copy(*m);
391
392 for(;;) {
393 split_edges_by_iso_uvs(m, uv_name, singular_name);
394 facets_split(m, uv_name, singular_name);
395
396 triangulate_surface_preserve_attributes(m);
397
398 vector<index_t> fails;
399 find_degenerate_facets(m, fails);
400
401 vector<index_t> intersections;
402 find_self_intersections(m, intersections);
403 fails.insert(fails.end(), intersections.begin(), intersections.end()); // degenerate triangles or intersecting ones, all the same, I do not want them
404 if (!fails.size()) break;
405
406 plop("imprint fail, need to undo");
407 {
408 Attribute<index_t> singular(m_bak.facets.attributes(), singular_name);
409 Attribute<index_t> resp_facet(m->vertices.attributes(), "resp_facet");
410
411 FOR(i, fails.size()) {
412 FOR(j, 3) {
413 index_t f = resp_facet[m->facets.vertex(fails[i], j)];
414 if (NOT_AN_ID!=f) singular[f] = true;
415 }
416 }
417 }
418 m->copy(m_bak);
419 }
420 }
421
422 void facets_split(Mesh* m, const char *uv_name, const char *singular_name) {
423 Attribute<bool> added_vertices(m->vertices.attributes(), "added_vertices");
424 Attribute<GEO::vec2> uv(m->facet_corners.attributes(), uv_name);
425 Attribute<index_t> singular(m->facets.attributes(), singular_name);
426
427 Attribute<index_t> orig_tri_fid(m->facets.attributes(), "orig_tri_fid");
428 Attribute<index_t> resp_facet(m->vertices.attributes(), "resp_facet");
429
430 vector<index_t> to_kill(m->facets.nb(), 0);
431 FOR(f, m->facets.nb()) {
432 if (singular[f]) continue;
433 index_t nbc = m->facets.nb_corners(f);
434
435
436 // STEP 1: find a couple (coordinate, iso value) to split the facet
437 index_t coord = index_t(-1);
438 double iso = 0;
439 FOR(test_coord, 2) {
440 double min_v = 1e20;
441 double max_v = -1e20;
442 FOR(fc, nbc) {
443 min_v = std::min(min_v, uv[m->facets.corner(f, fc)][test_coord]);
444 max_v = std::max(max_v, uv[m->facets.corner(f, fc)][test_coord]);
445 }
446 if (floor(min_v)+1 < max_v) { // floor(min_v)+1 is the first integer value strictly superior to min_v
447 coord = test_coord;
448 iso = floor(min_v)+1;
449 break;
450 }
451 }
452 if (coord == index_t(-1)) continue;
453
454 // STEP 2: if STEP 1 succedeed, compute the extremities of the new edge
455 index_t cut[2] = { NOT_AN_ID, NOT_AN_ID };
456
457 FOR(fc, nbc) {
458 if (uv[m->facets.corner(f, fc)][coord] != iso) continue;
459 if (cut[0] == NOT_AN_ID) {
460 cut[0] = fc;
461 } else if (fc != next_mod(cut[0], nbc) && next_mod(fc, nbc) != cut[0]) { // no biangles
462 cut[1] = fc;
463 }
464 }
465 if (cut[1] == NOT_AN_ID) continue;
466
467 { // let us check that the cut separates the facet
468 bool inf1=true, sup1=true, inf2=true, sup2=true;
469 for (index_t fc = cut[0]+1; fc<cut[1]; fc++) {
470 double tex = uv[m->facets.corner(f, fc)][coord];
471 inf1 = inf1 && (tex<iso);
472 sup1 = sup1 && (tex>iso);
473 }
474 for (index_t fc = cut[1]+1; fc<cut[0]+nbc; fc++) {
475 double tex = uv[m->facets.corner(f, fc%nbc)][coord];
476 inf2 = inf2 && (tex<iso);
477 sup2 = sup2 && (tex>iso);
478 }
479 if (!( (inf1 && sup2) || (sup1 && inf2) )) {
480 plop("WARNING: facet cannot be properly cut by the iso");
481 continue;
482 }
483 }
484
485
486 to_kill[f] = true;
487
488 // STEP 3: compute new vertices (iso-integer value of uv) on the new edge
489 vector<vec3> nv_pts;
490 vector<vec2> nv_uv;
491 {
492 vec3 lX[2];
493 FOR(i, 2) lX[i] = m->vertices.point(m->facets.vertex(f, cut[i]));
494 vec2 lU[2];
495 FOR(i, 2) lU[i] = uv[m->facets.corner(f, cut[i])];
496 vector<double> coeff;
497 double v[2] = { lU[0][(coord+1)%2], lU[1][(coord+1)%2] }; // recall that coord is the cutting dimension
498
499 for (double cur_iso = ceil(std::min(v[0], v[1])); cur_iso < std::max(v[0], v[1]); cur_iso += 1.0) {
500 double c = (cur_iso - v[0]) / (v[1] - v[0]);
501 if (!Numeric::is_nan(c) && c > 0 && c < 1)
502 coeff.push_back(c);
503 }
504
505 std::sort(coeff.begin(), coeff.end(), std::less<double>());
506 FOR(i, coeff.size()) {
507 vec3 x = lX[0] + coeff[i] * (lX[1] - lX[0]); // it guarantees x==lX[0] when lX[0]==lX[1]
508 vec2 u = lU[0] + coeff[i] * (lU[1] - lU[0]); // no need to worry about coeff[i]==0 and coeff[i]==1 because of the parameterization pre-snapping
509 nv_pts.push_back(x);
510 nv_uv.push_back(u);
511 }
512 // new vertices must have only integer values of uv --- remove possible numerical imprecision
513 FOR(i, nv_pts.size()) {
514 FOR(d, 2) {
515 nv_uv[i][d] = round(nv_uv[i][d]);
516 }
517 }
518 }
519
520 // STEP 4: create new vertices and new faces
521 index_t off = m->vertices.create_vertices(nv_pts.size());
522 FOR(i, nv_pts.size()) {
523 resp_facet[off+i] = orig_tri_fid[f];
524 added_vertices[off+i] = true;
525 X(m)[off + i] = nv_pts[i];
526 }
527 FOR(half, 2) {
528 vector <index_t> lv;
529 vector <vec2> luv;
530
531 // add original vertices
532 index_t cir = cut[half];
533 do {
534 lv.push_back(m->facets.vertex(f, cir));
535 luv.push_back(uv[m->facets.corner(f, cir)]);
536 cir = next_mod(cir, nbc);
537 } while (cir != cut[(half + 1) % 2]);
538 lv.push_back(m->facets.vertex(f, cir));
539 luv.push_back(uv[m->facets.corner(f, cir)]);
540
541 // add new vertices
542 FOR(i, nv_pts.size()) {
543 index_t ind = i;
544 if (half == 0) ind = nv_pts.size() - 1 - i;
545 lv.push_back(off + ind);
546 luv.push_back(nv_uv[ind]);
547 }
548
549 index_t fid = m->facets.create_polygon(lv);
550 m->facets.attributes().copy_item(fid, f);
551 FOR(fc, m->facets.nb_corners(fid)) {
552 uv[m->facets.corner(fid, fc)] = luv[fc];
553 }
554 to_kill.push_back(false);
555 }
556 }
557 m->facets.delete_elements(to_kill);
558 }
559
560 /**
561 * INPUT: facets with uv coordinates
562 * OUTPUT: chart facet attribute s.t. the chart frontier is included in edges that are iso-integer value of 'uv'
563 */
564
565 inline index_t indir_root(index_t i, vector<index_t>& indir) {
566 while (i != indir[i]) i = indir[i];
567 return i;
568 }
569
570 // merges charts for adjacent facets under two conditions:
571 // 1) both facets are not singular
572 // 2) the shared edge is not integer iso in both facets
573 void mark_charts(Mesh* m, const char *uv_name, const char *chart_name, const char *singular_name) { // 2-manifold surface is supposed
574 Attribute<bool> isovalue(m->facet_corners.attributes(), "isovalue");
575 Attribute<bool> quadelement(m->facets.attributes(), "quadelement");
576 Attribute<bool> quadcorners(m->vertices.attributes(), "quadcorners");
577 Attribute<bool> added_vertices(m->vertices.attributes(), "added_vertices");
578
579 Attribute<GEO::vec2> uv(m->facet_corners.attributes(), uv_name);
580 Attribute<index_t> singular(m->facets.attributes(), singular_name);
581 Attribute<index_t> chart(m->facets.attributes(), chart_name);
582
583 FacetsExtraConnectivity fec(m);
584
585 vector<index_t> indir(m->facets.nb());
586 { // fill isovalue edge attribute and merge quad candidate charts
587 FOR(f, m->facets.nb()) {
588 indir[f] = f;
589 }
590
591 FOR(h, m->facet_corners.nb()) {
592 index_t hopp = fec.opposite(h);
593 index_t f = fec.facet(h);
594 index_t fopp = NOT_AN_ID==hopp ? NOT_AN_ID : fec.facet(hopp);
595
596 if (singular[f] || (NOT_AN_ID!=hopp && hopp>h)) continue;
597
598 bool cut = false;
599 index_t test[2] = { h, hopp };
600 FOR(lh, 2) {
601 if (lh && (NOT_AN_ID==hopp || singular[fopp])) break;
602 FOR(coord, 2) {
603 cut = cut || (uv[test[lh]][coord] == uv[fec.next(test[lh])][coord] && uv[test[lh]][coord] == round(uv[test[lh]][coord]));
604 }
605 }
606
607 if (cut) {
608 isovalue[h] = true;
609 if (NOT_AN_ID!=hopp) isovalue[hopp] = true;
610 }
611
612 if (!cut && NOT_AN_ID!=fopp && !singular[fopp]) {
613 indir[indir_root(fopp, indir)] = indir_root(f, indir);
614 }
615 }
616
617 FOR(f, m->facets.nb()) {
618 chart[f] = indir_root(f, indir);
619 }
620 }
621
622
623 { // determine quad corner vertices: fill quadcorners attribute
624 FOR (h, m->facet_corners.nb()) {
625 int cnt = 0;
626 index_t cir = h;
627 do {
628 if (NOT_AN_ID==cir) break;
629 cnt += int(isovalue[cir]);
630 cir = fec.next_around_vertex(cir);
631 } while (cir != h);
632
633 if ((NOT_AN_ID==cir && cnt>=2) || cnt>2) {
634 quadcorners[fec.org(h)] = true;
635 }
636 }
637 }
638
639
640 { // fill quadelement attribute
641
642 // this code verifies only if chart boundaries are marked as isovalues + if it has 4 quadcorners
643 // normally it is also necessary to check if the "quad" is a 2d disk (one boundary + Euler characterisic (imagine a torus with a hole))
644
645 vector<bool> seen(m->facets.nb(), false);
646 FOR(fseed, m->facets.nb()) {
647 if (fseed != chart[fseed]) continue;
648 int nb_quad_corners = 0;
649 std::deque<index_t> Q;
650 Q.push_back(fseed);
651 seen[fseed] = true;
652 bool iso_only_at_boundaries = true;
653 std::vector<index_t> C;
654 while (Q.size()) {
655 index_t f = Q.front();
656 if (singular[f]) {
657 iso_only_at_boundaries = false;
658 break;
659 }
660 C.push_back(f);
661 Q.pop_front();
662 FOR(fc, m->facets.nb_corners(f)) {
663 index_t h = m->facets.corner(f, fc);
664 index_t hopp = fec.opposite(h);
665 index_t fopp = NOT_AN_ID==hopp ? NOT_AN_ID : fec.facet(hopp);
666 if (fopp==NOT_AN_ID) {
667 if (isovalue[h] && quadcorners[fec.org(h)]) nb_quad_corners++;
668 } else {
669 if (chart[fopp] != chart[fseed]) {
670 if (isovalue[h]) {
671 if (quadcorners[fec.org(h)])
672 nb_quad_corners++;
673 continue;
674 }
675 Q.clear();
676 iso_only_at_boundaries = false;
677 break;
678 }
679 }
680 if (NOT_AN_ID!=fopp && !seen[fopp]) {
681 Q.push_back(fopp);
682 seen[fopp] = true;
683 }
684 }
685 }
686 if (iso_only_at_boundaries && nb_quad_corners == 4) {
687 FOR(i, C.size()) {
688 quadelement[C[i]] = true;
689 }
690 }
691 }
692 }
693
694 { // charts = orig triangles in non-quad regions
695 FOR(f, m->facets.nb()) {
696 if (!quadelement[f]) indir[f] = f;
697 }
698
699 Attribute<index_t> orig_tri_fid(m->facets.attributes(), "orig_tri_fid");
700 FOR(h, m->facet_corners.nb()) {
701 index_t hopp = fec.opposite(h);
702 index_t f = fec.facet(h);
703 index_t fopp = NOT_AN_ID==hopp ? NOT_AN_ID : fec.facet(hopp);
704 if (NOT_AN_ID == fopp || quadelement[f] || quadelement[fopp] || orig_tri_fid[f]!=orig_tri_fid[fopp]) continue;
705 indir[indir_root(fopp, indir)] = indir_root(f, indir);
706 }
707
708 FOR(f, m->facets.nb()) {
709 chart[f] = indir_root(f, indir);
710 }
711 }
712
713 vector<vector<index_t> > v2f = generate_v2f(m);
714 { // mark the boundary between triangles and quads
715 FOR(v, m->vertices.nb()) {
716 TrFan fan = TrFan(v, m, v2f, chart);
717 bool touches_a_quad = false;
718 FOR(i, fan.nb_fan_triangles()) {
719 touches_a_quad = touches_a_quad || quadelement[fan[i].f];
720 }
721 if (quadcorners[v] || !touches_a_quad || fan.ncharts()<=2) continue;
722
723 index_t first_triangle = NOT_AN_ID;
724 FOR(i, fan.nb_fan_triangles()) {
725 if (quadelement[fan[i].f]) continue;
726 if (NOT_AN_ID==first_triangle) {
727 first_triangle = fan[i].f;
728 }
729 indir[indir_root(first_triangle, indir)] = indir_root(fan[i].f, indir);
730 }
731 }
732 FOR(f, m->facets.nb()) {
733 chart[f] = indir_root(f, indir);
734 }
735 }
736
737 { // fill verts to remove attribute
738 Attribute<bool> verts_to_remove(m->vertices.attributes(), "verts_to_remove");
739 FOR(v, m->vertices.nb()) {
740 TrFan fan = TrFan(v, m, v2f, chart);
741 bool touches_a_quad = false;
742 FOR(i, fan.nb_fan_triangles()) {
743 touches_a_quad = touches_a_quad || quadelement[fan[i].f];
744 }
745 if (quadcorners[v]) continue;
746 if ( fan.incomplete_ && fan.ncharts()!=1) continue;
747 if (!fan.incomplete_ && fan.ncharts() >2) continue;
748 if (!touches_a_quad && !added_vertices[v]) continue;
749 verts_to_remove[v] = true;
750 }
751 }
752 }
753
754 /****************************************************************************************************/
755 static bool try_export_quadtri_from_charts(Mesh* m, vector<BBox>& locked_regions) {
756 bool modified = false;
757 Attribute<index_t> chart(m->facets.attributes(), "chart"); // TODO document this
758 Attribute<bool> quadelement(m->facets.attributes(), "quadelement");
759
760 vector<index_t> to_kill(m->facets.nb(), false);
761
762 index_t max_chart_no = 0;
763 FOR(f, m->facets.nb()) {
764 max_chart_no = std::max(max_chart_no, chart[f] + 1);
765 }
766 vector<int> nbtri_in_chart(max_chart_no, 0);
767 FOR(f, m->facets.nb()) {
768 nbtri_in_chart[chart[f]]++;
769 }
770
771 FacetsExtraConnectivity fec(m);
772 index_t nbf = m->facets.nb();
773 FOR(f, nbf) {
774 geo_assert(3 == m->facets.nb_corners(f));
775
776 if (nbtri_in_chart[chart[f]] != 2 || !quadelement[f]) continue;
777 FOR(ih, 3) {
778 index_t h = m->facets.corner(f, ih);
779 index_t hopp = fec.opposite(h);
780 if (NOT_AN_ID==hopp) continue;
781 index_t fopp = fec.facet(hopp);
782 geo_assert(NOT_AN_ID != fopp);
783 if (chart[fopp] != chart[f]) continue;
784 if (f < fopp) break;
785
786 vector<index_t> pts;
787 pts.push_back(fec.dest(h));
788 pts.push_back(fec.dest(fec.next(h)));
789 pts.push_back(fec.org(h));
790 pts.push_back(fec.dest(fec.next(hopp)));
791
792 bool intersect_locked_region = false;
793 BBox b;
794 FOR(v, 4) {
795 b.add(X(m)[pts[v]]);
796 }
797 FOR(i, locked_regions.size()) {
798 intersect_locked_region = intersect_locked_region || locked_regions[i].intersect(b);
799 }
800
801 if (!intersect_locked_region) {
802 index_t nf = m->facets.create_polygon(pts);
803 modified = true;
804 m->facets.attributes().copy_item(nf ,f);
805 to_kill.push_back(false);
806 to_kill[f] = true;
807 to_kill[fopp] = true;
808 }
809 }
810 }
811 m->facets.delete_elements(to_kill, false);
812 return modified;
813 }
814
815
816
817
818 static void sample_triangle(vec3 *ABC, double eps, vector<vec3>& samples) {
819 double max_edge_length = 0;
820 FOR(p, 3) max_equal(max_edge_length, (ABC[(p + 1) % 3] - ABC[p]).length());
821 index_t nb_steps = 10;
822 if (eps>0) min_equal(nb_steps, index_t(max_edge_length / eps + 2));
823
824 FOR(i, nb_steps)FOR(j, nb_steps - i) {
825 double u = double(i) / double(nb_steps - 1);
826 double v = double(j) / double(nb_steps - 1);
827 samples.push_back(ABC[0] + u*(ABC[1] - ABC[0]) + v*(ABC[2] - ABC[0]));
828 }
829 }
830 //double upper_bound_min_dist2_to_triangles(vec3 P, vector<vec3>& triangles) {
831 // vec3 closest_point;
832 // double l0, l1, l2;
833 // double min_dist2 = 1e20;
834 // FOR(t, triangles.size() / 3)
835 // min_equal(min_dist2,
836 // Geom::point_triangle_squared_distance<vec3>(P,
837 // triangles[t * 3], triangles[t * 3 + 1], triangles[t * 3 + 2], closest_point, l0, l1, l2)
838 // );
839 // return min_dist2;
840 //}
841
842 static vector<index_t> facets_having_a_point_further_than_eps(Mesh* m,Mesh* ref,double epsilon) {
843
844 vector<index_t> res;
845
846 vector<BBox> inboxes = facets_bbox(ref);
847 DynamicHBoxes hb; hb.init(inboxes);
848
849 FOR(f, m->facets.nb()) {
850 bool fail = false;
851 vector<vec3> samples;
852 vec3 ABC[3];
853 FOR(lv,3) ABC[lv] = X(m)[m->facets.vertex(f,lv)];
854 sample_triangle(ABC, epsilon / 2., samples);
855 if (m->facets.nb_vertices(f) == 4) {
856 FOR(lv, 3) ABC[lv] = X(m)[m->facets.vertex(f, (lv+2)%3)];
857 sample_triangle(ABC, epsilon / 2., samples);
858 }
859
860
861 FOR(p, samples.size()) {
862 vec3 P = samples[p];
863
864 double min_dist2 = 1e20;
865
866 BBox bbox; bbox.add(P); bbox.dilate(epsilon/2.);
867 vector<index_t> prim;
868 hb.intersect(bbox, prim);
869 FOR(fid, prim.size()) {
870 index_t other_f = prim[fid];
871 vec3 closest_point;
872 double l0, l1, l2;
873 min_equal(min_dist2, Geom::point_triangle_squared_distance<vec3>(P,
874 X(ref)[ref->facets.vertex(other_f, 0)],
875 X(ref)[ref->facets.vertex(other_f, 1)],
876 X(ref)[ref->facets.vertex(other_f, 2)],
877 closest_point, l0, l1, l2));
878
879 if (ref->facets.nb_vertices(other_f) == 4) {
880 min_equal(min_dist2, Geom::point_triangle_squared_distance<vec3>(P,
881 X(ref)[ref->facets.vertex(other_f, 0)],
882 X(ref)[ref->facets.vertex(other_f, 2)],
883 X(ref)[ref->facets.vertex(other_f, 3)],
884 closest_point, l0, l1, l2));
885 }
886 }
887
888 if (::sqrt(min_dist2) > epsilon / 2.) {
889 fail = true;
890 break;
891 }
892 }
893 if (fail) res.push_back(f);
894 }
895 return res;
896 }
897
898 // Attention, sub-functions of this function need to access to attributes "chart" and "singular"
899 void simplify_quad_charts(Mesh* m) {
900 std::string msg;
901 if (!surface_is_manifold(m, msg)) plop(msg);
902
903 Mesh m_bak;
904 m_bak.copy(*m);
905 double epsilon = 0;// .4*get_facet_average_edge_size(&m_bak);
906 //epsilon = 0;
907 vector<BBox> locked_regions;
908 // int cnt = 0;
909 for(;;) {
910 vector<index_t> invalid_m ;
911 vector<index_t> invalid_bak ;
912 vector<index_t> intersections;
913 {
914 Attribute<index_t> chart(m->facets.attributes(), "chart");
915 Attribute<index_t> undo(m->facets.attributes(), "undo");
916 FOR(fid, m->facets.nb()) {
917 undo[fid] = NOT_AN_ID;
918 }
919 Attribute<bool> verts_to_remove(m->vertices.attributes(), "verts_to_remove");
920 plop("try_simplify(m, chart, verts_to_remove, undo)");
921 try_simplify(m, chart, verts_to_remove, undo);
922 plop("try_export_quadtri_from_charts(m, locked_regions)");
923 try_export_quadtri_from_charts(m, locked_regions);
924
925 plop("check for intersections");
926 plop(m->facets.nb());
927 find_self_intersections(m, intersections);
928 FOR(f, intersections.size()) {
929 if (3 == m->facets.nb_vertices(f)) {
930 if (undo[intersections[f]] != NOT_AN_ID) verts_to_remove[undo[intersections[f]]] = false;
931 // GEO::Logger::out("HexDom") << intersections[f] << " " << undo[intersections[f]] << std::endl;
932 } else {
933 geo_assert(4 == m->facets.nb_vertices(f));
934 BBox inbox;
935 FOR(fv, 4) {
936 inbox.add(X(m)[m->facets.vertex(intersections[f], fv)]);
937 }
938 locked_regions.push_back(inbox);
939 }
940 // Attribute<bool> gna(m->facets.attributes(), "gna");
941 // gna[intersections[f]] = true;
942 }
943 //intersections.clear();// HACK (simulation de quadhex)
944
945 if (epsilon > 0) {
946 plop("check for Hausdorff distance --- dist to init mesh");
947 invalid_m = facets_having_a_point_further_than_eps(m, &m_bak, epsilon);
948 plop(invalid_m.size());
949 FOR(i, invalid_m.size()) {
950 if (3 == m->facets.nb_vertices(invalid_m[i])) {
951 if (undo[invalid_m[i]] != NOT_AN_ID) verts_to_remove[undo[invalid_m[i]]] = false;
952 }
953 else {
954 BBox inbox;
955 FOR(fv, 4) {
956 inbox.add(X(m)[m->facets.vertex(invalid_m[i], fv)]);
957 }
958 locked_regions.push_back(inbox);
959 }
960 }
961
962 plop("check for Hausdorff distance --- dist to new mesh");
963 invalid_bak = facets_having_a_point_further_than_eps(&m_bak, m, epsilon);
964 plop(invalid_bak.size());
965
966 FOR(i, invalid_bak.size()) FOR(lv, 3)
967 verts_to_remove[m_bak.facets.vertex(invalid_bak[i], lv)] = false;
968 }
969 }
970
971
972 if (intersections.empty() && invalid_m.empty() && invalid_bak.empty()) break;
973
974 plop("conflict detected");
975 {
976 Attribute<bool> m_verts_to_remove(m->vertices.attributes(), "verts_to_remove");
977 Attribute<bool> m_bak_verts_to_remove(m_bak.vertices.attributes(), "verts_to_remove");
978 geo_assert(m->vertices.nb() == m_bak.vertices.nb());
979 FOR(v, m->vertices.nb()) m_bak_verts_to_remove[v] = m_verts_to_remove[v];
980 }
981
982 // char filename[1024], filename2[1024];
983 // sprintf(filename, "/home/ssloy/tmp/hexdom_nightly/geogram/zdebug%i_bak.geogram", cnt);
984 // sprintf(filename2, "/home/ssloy/tmp/hexdom_nightly/geogram/zdebug%i_simp.geogram", cnt);
985 // cnt++;
986 // mesh_save(m_bak, filename);
987 // mesh_save(*m, filename2);
988
989 m->copy(m_bak);
990 }
991 kill_isolated_vertices(m);
992 }
993
994
995
996
997 }
998