GCC Code Coverage Report


Directory: ./
File: lib/geogram/delaunay/delaunay.cpp
Date: 2026-09-07 02:37:58
Exec Total Coverage
Lines: 65 203 32.0%
Functions: 9 25 36.0%
Branches: 54 284 19.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 <geogram/delaunay/delaunay.h>
41 #include <geogram/delaunay/delaunay_nn.h>
42 #include <geogram/delaunay/delaunay_3d.h>
43 #include <geogram/delaunay/delaunay_2d.h>
44
45 #ifdef GEOGRAM_WITH_PDEL
46 #include <geogram/delaunay/parallel_delaunay_3d.h>
47 #endif
48
49 #ifdef GEOGRAM_WITH_TETGEN
50 #include <geogram/delaunay/delaunay_tetgen.h>
51 #endif
52
53 #ifdef GEOGRAM_WITH_TRIANGLE
54 #include <geogram/delaunay/delaunay_triangle.h>
55 #endif
56
57 #include <geogram/basic/logger.h>
58 #include <geogram/basic/command_line.h>
59 #include <geogram/basic/process.h>
60 #include <geogram/basic/geometry_nd.h>
61 #include <geogram/basic/algorithm.h>
62
63 #include <fstream>
64 #include <sstream>
65
66 namespace {
67
68 using namespace GEO;
69
70 /**
71 * \brief Builds the invalid dimension error message
72 * \param[in] dimension the specified dimension
73 * \param[in] name the name of the Delaunay implementation
74 * \param[in] expected the expected dimension
75 * \return a string that contains the error message
76 */
77 std::string invalid_dimension_error(
78 coord_index_t dimension,
79 const char* name,
80 const char* expected
81 ) {
82 std::ostringstream out;
83 out << "Invalid dimension: dimension " << index_t(dimension)
84 << " is not supported by the " << name
85 << " algorithm. Supported dimension(s): " << expected;
86 return out.str();
87 }
88 }
89
90 namespace GEO {
91
92 Delaunay::InvalidDimension::InvalidDimension(
93 coord_index_t dimension,
94 const char* name,
95 const char* expected
96 ) :
97 std::logic_error(invalid_dimension_error(dimension, name, expected)) {
98 }
99
100 const char* Delaunay::InvalidDimension::what() const GEO_NOEXCEPT {
101 return std::logic_error::what();
102 }
103
104
105 Delaunay::InvalidInput::InvalidInput(int code) :
106 logic_error("Invalid input for Delaunay"),
107 error_code(code) {
108 }
109
110 Delaunay::InvalidInput::InvalidInput(
111 const InvalidInput& rhs
112 ) :
113 std::logic_error(rhs),
114 error_code(rhs.error_code),
115 invalid_facets(rhs.invalid_facets) {
116 }
117
118 Delaunay::InvalidInput::~InvalidInput() GEO_NOEXCEPT {
119 }
120
121 const char* Delaunay::InvalidInput::what() const GEO_NOEXCEPT {
122 return std::logic_error::what();
123 }
124
125 /************************************************************************/
126
127 251 void Delaunay::initialize() {
128
129 #ifdef GEOGRAM_WITH_TETGEN
130
4/10
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 251 times.
✗ Branch 10 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
753 geo_register_Delaunay_creator(DelaunayTetgen, "tetgen");
131 #endif
132
133 #ifdef GEOGRAM_WITH_TRIANGLE
134
4/10
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 251 times.
✗ Branch 10 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
753 geo_register_Delaunay_creator(DelaunayTriangle, "triangle");
135 #endif
136
137
4/10
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 251 times.
✗ Branch 10 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
753 geo_register_Delaunay_creator(Delaunay3d, "BDEL");
138
139 #ifdef GEOGRAM_WITH_PDEL
140
4/10
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 251 times.
✗ Branch 10 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
753 geo_register_Delaunay_creator(ParallelDelaunay3d, "PDEL");
141 #endif
142
4/10
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 251 times.
✗ Branch 10 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
753 geo_register_Delaunay_creator(RegularWeightedDelaunay3d, "BPOW");
143
144
4/10
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 251 times.
✗ Branch 10 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
753 geo_register_Delaunay_creator(Delaunay2d, "BDEL2d");
145
4/10
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 251 times.
✗ Branch 10 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
753 geo_register_Delaunay_creator(RegularWeightedDelaunay2d, "BPOW2d");
146
147 #ifndef GEOGRAM_PSM
148
4/10
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 251 times.
✗ Branch 10 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
753 geo_register_Delaunay_creator(Delaunay_NearestNeighbors, "NN");
149 #endif
150 251 }
151
152 76 Delaunay* Delaunay::create(coord_index_t dim, const std::string& name_in) {
153
154
1/2
✓ Branch 1 taken 76 times.
✗ Branch 2 not taken.
76 std::string name = name_in;
155
3/4
✓ Branch 1 taken 76 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 74 times.
✓ Branch 4 taken 2 times.
76 if(name == "default") {
156
2/4
✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 74 times.
✗ Branch 5 not taken.
148 name = CmdLine::get_arg("algo:delaunay");
157 }
158
159 try {
160
1/2
✓ Branch 1 taken 76 times.
✗ Branch 2 not taken.
76 Delaunay* d = DelaunayFactory::create_object(name, dim);
161
1/2
✓ Branch 0 taken 76 times.
✗ Branch 1 not taken.
76 if(d != nullptr) {
162 76 return d;
163 }
164
165 Logger::warn("Delaunay")
166 << "Could not create Delaunay triangulation: " << name
167 << std::endl;
168 }
169 catch(InvalidDimension& ex) {
170 Logger::warn("Delaunay") << ex.what() << std::endl;
171 }
172
173 #ifdef GEOGRAM_PSM
174 Logger::err("Delaunay")
175 << "Could not create Delaunay triangulation"
176 << std::endl;
177 return nullptr;
178 #else
179 Logger::warn("Delaunay")
180 << "Falling back to NN mode"
181 << std::endl;
182
183 return new Delaunay_NearestNeighbors(dim);
184 #endif
185 76 }
186
187
1/2
✓ Branch 4 taken 76 times.
✗ Branch 5 not taken.
76 Delaunay::Delaunay(coord_index_t dimension) {
188 76 set_dimension(dimension);
189 76 vertices_ = nullptr;
190 76 nb_vertices_ = 0;
191 76 nb_cells_ = 0;
192 76 cell_to_v_ = nullptr;
193 76 cell_to_cell_ = nullptr;
194 76 is_locked_ = false;
195 76 store_neighbors_ = false;
196 76 default_nb_neighbors_ = 30;
197 76 constraints_ = nullptr;
198 76 do_reorder_ = true;
199 76 refine_ = false;
200 76 quality_ = 2.0;
201 76 store_cicl_ = false;
202 76 keep_infinite_ = false;
203 76 nb_finite_cells_ = 0;
204 76 keep_regions_ = false;
205 76 }
206
207 152 Delaunay::~Delaunay() {
208 152 }
209
210 456 void Delaunay::set_vertices(index_t nb_vertices, const double* vertices) {
211 456 nb_vertices_ = nb_vertices;
212 456 vertices_ = vertices;
213 456 }
214
215 void Delaunay::set_BRIO_levels(const vector<index_t>& levels) {
216 geo_argused(levels);
217 // Default implementation does nothing
218 }
219
220 11 void Delaunay::set_arrays(
221 index_t nb_cells,
222 const index_t* cell_to_v, const index_t* cell_to_cell
223 ) {
224 11 nb_cells_ = nb_cells;
225 11 cell_to_v_ = cell_to_v;
226 11 cell_to_cell_ = cell_to_cell;
227
228
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 if(cell_to_cell != nullptr) {
229
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if(store_cicl_) {
230 update_v_to_cell();
231 update_cicl();
232 }
233
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if(store_neighbors_) {
234 update_neighbors();
235 }
236 }
237 11 }
238
239 bool Delaunay::supports_constraints() const {
240 return false;
241 }
242
243 index_t Delaunay::nearest_vertex(const double* p) const {
244 // Unefficient implementation (but at least it works).
245 // Derived classes are supposed to overload.
246 geo_assert(nb_vertices() > 0);
247 index_t result = 0;
248 double d = Geom::distance2(vertex_ptr(0), p, dimension());
249 for(index_t i = 1; i < nb_vertices(); i++) {
250 double cur_d = Geom::distance2(vertex_ptr(i), p, dimension());
251 if(cur_d < d) {
252 d = cur_d;
253 result = i;
254 }
255 }
256 return result;
257 }
258
259 445 void Delaunay::update_neighbors() {
260
2/2
✓ Branch 2 taken 64 times.
✓ Branch 3 taken 381 times.
445 if(nb_vertices() != neighbors_.nb_arrays()) {
261 64 neighbors_.init(
262 nb_vertices(),
263 default_nb_neighbors_
264 );
265
2/2
✓ Branch 1 taken 68860 times.
✓ Branch 2 taken 64 times.
68924 for(index_t i = 0; i < nb_vertices(); i++) {
266 68860 neighbors_.resize_array(i, default_nb_neighbors_, false);
267 }
268 }
269
1/2
✓ Branch 2 taken 445 times.
✗ Branch 3 not taken.
445 parallel_for(
270 0, nb_vertices(),
271 1255650 [this](index_t i) { store_neighbors_CB(i); },
272 1, true
273 );
274 445 }
275
276 void Delaunay::get_neighbors_internal(
277 index_t v, vector<index_t>& neighbors
278 ) const {
279 // Step 1: traverse the incident cells list, and insert
280 // all neighbors (may be duplicated)
281 neighbors.resize(0);
282 index_t vt = v_to_cell_[v];
283 if(vt != NO_INDEX) { // Happens when there are duplicated vertices.
284 index_t t = vt;
285 do {
286 index_t lvit = index(t, v);
287 // In the current cell, test all edges incident
288 // to current vertex 'it'
289 for(index_t lv = 0; lv < cell_size(); lv++) {
290 if(lvit != lv) {
291 index_t neigh = cell_vertex(t, lv);
292 geo_debug_assert(neigh != NO_INDEX);
293 neighbors.push_back(neigh);
294 }
295 }
296 t = next_around_vertex(t, index(t, v));
297 } while(t != vt);
298 }
299
300 // Step 2: Sort the neighbors and remove all duplicates
301 sort_unique(neighbors);
302 }
303
304 void Delaunay::store_neighbors_CB(index_t i) {
305 // TODO: this one is not multithread-friendly
306 // since it does dynamic memory allocation
307 // (but not really a problem, since the one
308 // that is used is in Delaunay_ANN).
309 vector<index_t> neighbors;
310 get_neighbors_internal(i, neighbors);
311 neighbors_.set_array(i, neighbors);
312 }
313
314 void Delaunay::update_v_to_cell() {
315 geo_assert(!is_locked_); // Not thread-safe
316 is_locked_ = true;
317
318 // Note: if keeps_infinite is set, then infinite vertex
319 // tet chaining is at t2v_[nb_vertices].
320
321 if(keeps_infinite()) {
322 v_to_cell_.assign(nb_vertices()+1, NO_INDEX);
323 for(index_t c = 0; c < nb_cells(); c++) {
324 for(index_t lv = 0; lv < cell_size(); lv++) {
325 index_t v = cell_vertex(c, lv);
326 if(v == NO_INDEX) {
327 v = nb_vertices();
328 }
329 v_to_cell_[v] = c;
330 }
331 }
332 } else {
333 v_to_cell_.assign(nb_vertices(), NO_INDEX);
334 for(index_t c = 0; c < nb_cells(); c++) {
335 for(index_t lv = 0; lv < cell_size(); lv++) {
336 v_to_cell_[cell_vertex(c, lv)] = c;
337 }
338 }
339 }
340 is_locked_ = false;
341 }
342
343 void Delaunay::update_cicl() {
344 geo_assert(!is_locked_); // Not thread-safe
345 is_locked_ = true;
346 cicl_.resize(cell_size() * nb_cells());
347
348 for(index_t v = 0; v < nb_vertices(); ++v) {
349 index_t t = v_to_cell_[v];
350 if(t != NO_INDEX) {
351 index_t lv = index(t, v);
352 set_next_around_vertex(t, lv, t);
353 }
354 }
355
356 if(keeps_infinite()) {
357
358 {
359 // Process the infinite vertex at index nb_vertices().
360 index_t t = v_to_cell_[nb_vertices()];
361 if(t != NO_INDEX) {
362 index_t lv = index(t, NO_INDEX);
363 set_next_around_vertex(t, lv, t);
364 }
365 }
366
367 for(index_t t = 0; t < nb_cells(); ++t) {
368 for(index_t lv = 0; lv < cell_size(); ++lv) {
369 index_t v = cell_vertex(t, lv);
370 index_t vv = (v == NO_INDEX) ? nb_vertices() : v;
371 if(v_to_cell_[vv] != t) {
372 index_t t1 = v_to_cell_[vv];
373 index_t lv1 = index(t1, v);
374 index_t t2 = next_around_vertex(t1, lv1);
375 set_next_around_vertex(t1, lv1, t);
376 set_next_around_vertex(t, lv, t2);
377 }
378 }
379 }
380
381
382 } else {
383 for(index_t t = 0; t < nb_cells(); ++t) {
384 for(index_t lv = 0; lv < cell_size(); ++lv) {
385 index_t v = cell_vertex(t, lv);
386 if(v_to_cell_[v] != t) {
387 index_t t1 = v_to_cell_[v];
388 index_t lv1 = index(t1, v);
389 index_t t2 = next_around_vertex(t1, lv1);
390 set_next_around_vertex(t1, lv1, t);
391 set_next_around_vertex(t, lv, t2);
392 }
393 }
394 }
395 }
396
397 is_locked_ = false;
398 }
399
400 void Delaunay::save_histogram(std::ostream& out) const {
401 vector<index_t> histogram;
402 for(index_t v = 0; v < nb_vertices(); v++) {
403 index_t N = neighbors_.array_size(v);
404 if(histogram.size() < N) {
405 histogram.resize(N + 1);
406 }
407 histogram[N]++;
408 }
409 for(index_t i = 0; i < histogram.size(); i++) {
410 out << i << " " << histogram[i] << std::endl;
411 }
412 }
413
414 1145 bool Delaunay::cell_is_infinite(index_t c) const {
415
1/6
✗ Branch 1 not taken.
✓ Branch 2 taken 1145 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
1145 geo_debug_assert(c < nb_cells());
416
2/2
✓ Branch 1 taken 4199 times.
✓ Branch 2 taken 779 times.
4978 for(index_t lv=0; lv < cell_size(); ++lv) {
417
2/2
✓ Branch 1 taken 366 times.
✓ Branch 2 taken 3833 times.
4199 if(cell_vertex(c,lv) == NO_INDEX) {
418 366 return true;
419 }
420 }
421 779 return false;
422 }
423
424 index_t Delaunay::region(index_t t) const {
425 geo_argused(t);
426 geo_debug_assert(t < nb_cells());
427 return NO_INDEX;
428 }
429 }
430