GCC Code Coverage Report


Directory: ./
File: lib/exploragram/hexdom/preprocessing.cpp
Date: 2026-09-07 02:28:19
Exec Total Coverage
Lines: 0 103 0.0%
Functions: 0 6 0.0%
Branches: 0 252 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/preprocessing.h>
41 #include <geogram/mesh/mesh_tetrahedralize.h>
42
43 #include <exploragram/hexdom/quadmesher.h> // for debug output
44
45 namespace GEO {
46
47 static vec3 triangle_normal(const Mesh& M, index_t f){
48 vec3 pt[3];
49 for (index_t v = 0; v < 3; v++) pt[v] = M.vertices.point(M.facets.vertex(f,v));
50 return cross(normalize(pt[1] - pt[0]), normalize(pt[2] - pt[0]));
51 }
52
53 static bool get_a_triangle_patch(
54 const Mesh& M,
55 index_t facet,
56 std::vector<bool>& tri_patch_flag,
57 index_t& patch_size,
58 double cos_angle,
59 index_t nb_tri_min_in_patch) {
60
61 /* Add facet in the patch */
62 tri_patch_flag[facet] = true;
63 patch_size += 1;
64
65 /* Check stop condition */
66 if (patch_size >= nb_tri_min_in_patch) {
67 return true;
68 }
69
70 /* Recursive call */
71 vec3 n = normalize(triangle_normal(M, facet));
72 for (index_t le = 0; le < 3; ++le) {
73 index_t a = M.facets.adjacent(facet, le);
74 if (a == GEO::NO_FACET) continue;
75 vec3 n_a = normalize(triangle_normal(M, a));
76 if (dot(n, n_a) > cos_angle) {
77 if (!tri_patch_flag[a])
78 get_a_triangle_patch(M, a, tri_patch_flag, patch_size, cos_angle, nb_tri_min_in_patch);
79 }
80 }
81 return patch_size >= nb_tri_min_in_patch;
82 }
83
84 static void generate_facet_is_in_patch_attribute(const Mesh& M) {
85 geo_assert(M.facets.nb() > 0);
86
87 /* The input constrained are only computed from "valid" triangles
88 * - triangles are considered valid if they are in a smooth patch of at
89 * least nb_tri_min_in_patch triangles
90 * - two adjacent triangles are in the same patch if the dot product of their
91 * normals is superior to cos_angle */
92
93 /* Parameters */
94 index_t nb_tri_min_in_patch = 8;
95 const double cos_angle = 0.975;
96
97 if (M.facets.nb() < 100) nb_tri_min_in_patch = 4; /* for small models */
98
99 Attribute<int> is_in_patch(M.facets.attributes(), "is_valid");
100 is_in_patch.fill(0);
101
102 std::vector<bool> is_flagged(M.facets.nb());
103 for(index_t f = 0; f < M.facets.nb(); ++f) {
104 if (is_in_patch[f]) continue;
105 index_t patch_size = 0;
106 std::fill(is_flagged.begin(), is_flagged.end(), false); /* reinitialize to zero */
107 bool ok = get_a_triangle_patch(M, f, is_flagged, patch_size, cos_angle, nb_tri_min_in_patch);
108 if (ok) { /* A patch starting at f has been found */
109 for (index_t i = 0; i < is_flagged.size(); ++i) {
110 /* triangles in the patch are keep for imposing constraints */
111 if (is_flagged[i]) is_in_patch[i] = 1;
112 }
113 }
114 }
115 }
116
117 static void compute_input_constraints(Mesh* m, bool relaxed = false) {
118
119 Attribute<mat3> B(m->vertices.attributes(), "B");
120 Attribute<vec3> lockB(m->vertices.attributes(), "lockB");// how many vectors are locked
121 Attribute<vec3> U(m->vertices.attributes(), "U");
122 Attribute<vec3> lockU(m->vertices.attributes(), "lockU");// how many dimensions are locked
123
124 // init all normal for each vertex
125 vector<vector<vec3> > normals(m->vertices.nb());
126 vector<vector<double> > weight(m->vertices.nb());
127
128 /* Compute the facet normals and store them at vertices */
129 if (!relaxed) {
130 FOR(c, m->cells.nb()) FOR(cf, 4) {
131 if ((m->cells.adjacent(c, cf) != NO_CELL)) continue;
132 vec3 n = tet_facet_cross(m, c, cf);
133 if (n.length2() > 1e-10) {
134 FOR(cfv, 3) {
135 normals[m->cells.facet_vertex(c, cf, cfv)].push_back(normalize(n));
136 weight[m->cells.facet_vertex(c, cf, cfv)].push_back(n.length());
137 }
138 }
139 }
140 } else {
141 /* The relaxation of constraints is achieved by storing only the normals of "valid"
142 * triangles.
143 * They are flagged via the facet attribute is_valid
144 * One possibility for flag them is to use generate_facet_is_in_patch_attribute()
145 * See the method for tweaking the parameters. */
146 m->cells.compute_borders();
147 generate_facet_is_in_patch_attribute(*m);
148 Attribute<int> is_valid(m->facets.attributes(), "is_valid");
149
150 for(index_t f = 0; f < m->facets.nb(); ++f) {
151 if (!is_valid[f]) continue;
152 vec3 n = facet_normal(m, f);
153 if (n.length2() > 1e-10) {
154 FOR(lv, 3) {
155 normals[m->facets.vertex(f, lv)].push_back(normalize(n));
156 weight[m->facets.vertex(f, lv)].push_back(n.length());
157 }
158 }
159 }
160 }
161 /* Build the constraints */
162 FOR(v, m->vertices.nb()) {
163 vector<vec3>& n = normals[v];
164 vector<double>& w = weight[v];
165
166 B[v].load_identity();
167 lockB[v] = vec3(0, 0, 0);
168 lockU[v] = vec3(0, 0, 0);
169 if (n.size() > 0) {
170 B[v] = Frame::representative_frame(n, w);// rot_to_B(representative_frame(n, w));
171 AxisPermutation ap;
172 ap.make_col2_equal_to_z(B[v], n[0]);
173 B[v] = Frame(B[v]).apply_permutation(ap);
174 FOR(i, n.size()) FOR(a, 3)
175 if (std::abs(dot(col(B[v], a), n[i])) > .7) {
176 lockU[v][a] = 1;
177 lockB[v][a] = 1;
178 }
179 if (lockB[v].length2() == 1) lockB[v] = vec3(0, 0, 1); // it may not always be true (happened once)
180 if (lockB[v].length2() > 1) lockB[v] = vec3(1, 1, 1);
181 U[v] = vec3(0, 0, 0);
182 }
183
184 //FOR(i,3) FOR(j,3) B[v](i,j) *= 5.;
185 }
186
187 if (relaxed) {
188 // m->facets.clear(false); // Keep the surface part for debugging
189 }
190 }
191
192 static void reorder_vertices_according_to_constraints(Mesh* m,bool hibert_sort) {
193 Attribute<vec3> lockB(m->vertices.attributes(), "lockB");// how many vectors are locked
194
195 GEO::vector<index_t > ind_map(m->vertices.nb());
196 index_t n = 0;
197 index_t num_l_v = 0;
198 index_t num_ln_v = 0;
199 FOR(v, m->vertices.nb()) if (lockB[v][0] == 1) { geo_assert(lockB[v][1] == 1); geo_assert(lockB[v][2] == 1); ind_map[n] = v; n++; }
200 num_l_v = n;
201 FOR(v, m->vertices.nb()) if (lockB[v][0] == 0 && lockB[v][2] == 1) { geo_assert(lockB[v][1] == 0); ind_map[n] = v; n++; }
202 num_ln_v = n;
203 FOR(v, m->vertices.nb()) if (lockB[v][2] == 0) { ind_map[n] = v; n++; }
204
205 for (index_t i = 0; i < num_l_v; i++) geo_assert(lockB[ind_map[i]][0] == 1);
206 for (index_t i = num_l_v; i < num_ln_v; i++) geo_assert(lockB[ind_map[i]][2] == 1);
207 for (index_t i = num_ln_v; i < m->vertices.nb(); i++) geo_assert(lockB[ind_map[i]][2] == 0);
208
209 geo_assert(num_l_v <= num_ln_v && num_ln_v <= m->vertices.nb());
210
211 plop(hibert_sort);
212 if (hibert_sort) {
213 compute_Hilbert_order(m->vertices.nb(), m->vertices.point_ptr(0), ind_map, 0, num_l_v,3);
214 compute_Hilbert_order(m->vertices.nb(), m->vertices.point_ptr(0), ind_map, num_l_v, num_ln_v, 3);
215 compute_Hilbert_order(m->vertices.nb(), m->vertices.point_ptr(0), ind_map, num_ln_v, m->vertices.nb(), 3);
216 }
217 m->vertices.permute_elements(ind_map); // note: it also updates the cell_corners.vertex... and invert ind_map :(
218 }
219
220
221 void produce_hexdom_input(Mesh* m,std::string& error_msg,bool hilbert_sort, bool relaxed) {
222
223 m->edges.clear();
224 m->vertices.remove_isolated();
225
226 if (m->cells.nb() == 0) {
227 if (m->facets.nb() == 0) throw ("mesh have no cells and no facets");
228 mesh_tetrahedralize(*m, true, true, .8);
229 }
230
231 if (have_negative_tet_volume(m)) {
232 throw ("contains tets with negative volume");
233 }
234
235 if (!m->cells.are_simplices()) {
236 throw ("cells contains non tet elements");
237 }
238
239 if (!volume_boundary_is_manifold(m, error_msg)) {
240 throw (error_msg.c_str());
241 }
242
243 if (!volume_is_tetgenifiable(m)) {
244 throw (" tetgen is not able to remesh the volume from its boundary");
245 }
246
247
248
249 // add some attributes
250 compute_input_constraints(m, relaxed);
251
252
253 // compute scale
254 double wanted_edge_length = get_cell_average_edge_size(m);
255
256 Attribute<mat3> B(m->vertices.attributes(), "B");
257 FOR(v, m->vertices.nb()) FOR(ij, 9) B[v].data()[ij] *= wanted_edge_length;
258 reorder_vertices_according_to_constraints(m,hilbert_sort );
259
260 }
261
262
263 }
264