GCC Code Coverage Report


Directory: ./
File: voronoi/CVT.cpp
Date: 2026-09-27 03:10:11
Exec Total Coverage
Lines: 78 177 44.1%
Functions: 7 16 43.8%
Branches: 59 244 24.2%

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/voronoi/CVT.h>
41 #include <geogram/voronoi/RVD.h>
42 #include <geogram/mesh/mesh_repair.h>
43 #include <geogram/mesh/mesh_geometry.h>
44 #include <geogram/numerics/optimizer.h>
45 #include <geogram/basic/progress.h>
46 #include <geogram/basic/argused.h>
47 #include <geogram/bibliography/bibliography.h>
48
49 /****************************************************************************/
50
51 namespace GEO {
52
53 CentroidalVoronoiTesselation*
54 CentroidalVoronoiTesselation::instance_ = nullptr;
55
56 12 CentroidalVoronoiTesselation::CentroidalVoronoiTesselation(
57 Mesh* mesh, coord_index_t dim, const std::string& delaunay
58
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 5 times.
12 ) {
59 12 use_RVC_centroids_ = true;
60 12 show_iterations_ = false;
61 12 constrained_cvt_ = false;
62 12 dimension_ =
63
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 5 times.
19 (dim != 0) ? dim : coord_index_t(mesh->vertices.dimension());
64
2/10
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
24 geo_assert(index_t(dimension_) <= mesh->vertices.dimension());
65 12 is_projection_ = true;
66
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 delaunay_ = Delaunay::create(dimension_, delaunay);
67
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 RVD_ = RestrictedVoronoiDiagram::create(delaunay_, mesh);
68 12 mesh_ = mesh;
69
1/10
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
12 geo_assert(instance_ == nullptr);
70 12 instance_ = this;
71 12 progress_ = nullptr;
72
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 geo_cite("Lloyd82leastsquares");
73
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 geo_cite("Du:1999:CVT:340312.340319");
74
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 geo_cite("DBLP:journals/tog/LiuWLSYLY09");
75 12 }
76
77 ✗ CentroidalVoronoiTesselation::CentroidalVoronoiTesselation(
78 Mesh* mesh, const vector<vec3>& R3_embedding, coord_index_t dim,
79 const std::string& delaunay
80 ✗ ) {
81 ✗ use_RVC_centroids_ = true;
82 ✗ show_iterations_ = false;
83 ✗ constrained_cvt_ = false;
84 ✗ dimension_ =
85 ✗ (dim != 0) ? dim : coord_index_t(mesh->vertices.dimension());
86 ✗ geo_assert(index_t(dimension_) <= mesh->vertices.dimension());
87 ✗ is_projection_ = (R3_embedding.size() == 0);
88 ✗ delaunay_ = Delaunay::create(dimension_, delaunay);
89 ✗ if(is_projection_) {
90 ✗ RVD_ = RestrictedVoronoiDiagram::create(delaunay_, mesh);
91 } else {
92 RVD_ = RestrictedVoronoiDiagram::create(
93 delaunay_, mesh, R3_embedding
94 ✗ );
95 }
96 ✗ mesh_ = mesh;
97 ✗ geo_assert(instance_ == nullptr);
98 ✗ instance_ = this;
99 ✗ progress_ = nullptr;
100 ✗ geo_cite("Lloyd82leastsquares");
101 ✗ geo_cite("Du:1999:CVT:340312.340319");
102 ✗ geo_cite("DBLP:journals/tog/LiuWLSYLY09");
103 ✗ }
104
105 24 CentroidalVoronoiTesselation::~CentroidalVoronoiTesselation() {
106
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
24 instance_ = nullptr;
107 24 }
108
109 12 bool CentroidalVoronoiTesselation::compute_initial_sampling(
110 index_t nb_samples, bool verbose
111 ) {
112 12 points_.resize(dimension_ * nb_samples);
113 12 return RVD_->compute_initial_sampling(
114 points_.data(), nb_samples, verbose
115 12 );
116 }
117
118 ✗ void CentroidalVoronoiTesselation::set_points(
119 index_t nb_points, const double* points
120 ) {
121 ✗ points_.resize(dimension_ * nb_points);
122 ✗ for(index_t i = 0; i < points_.size(); i++) {
123 ✗ points_[i] = points[i];
124 }
125 ✗ }
126
127 ✗ void CentroidalVoronoiTesselation::resize_points(
128 index_t nb_points
129 ) {
130 ✗ points_.resize(dimension_ * nb_points);
131 ✗ }
132
133
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
17 void CentroidalVoronoiTesselation::Lloyd_iterations(index_t nb_iter) {
134
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
17 index_t nb_points = index_t(points_.size() / dimension_);
135
136 vector<double> mg;
137 vector<double> m;
138
139
2/4
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
17 RVD_->set_check_SR(false);
140
141
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 2 times.
17 if(progress_ != nullptr) {
142
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 progress_->reset(nb_iter);
143 }
144
145 17 cur_iter_ = 0;
146 17 nb_iter_ = nb_iter;
147
148
2/2
✓ Branch 0 taken 455 times.
✓ Branch 1 taken 17 times.
472 for(index_t i = 0; i < nb_iter; i++) {
149
1/2
✓ Branch 1 taken 455 times.
✗ Branch 2 not taken.
455 mg.assign(nb_points * dimension_, 0.0);
150
1/4
✓ Branch 1 taken 455 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
455 m.assign(nb_points, 0.0);
151
2/4
✓ Branch 1 taken 455 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 455 times.
✗ Branch 5 not taken.
455 delaunay_->set_vertices(nb_points, points_.data());
152
1/2
✓ Branch 1 taken 455 times.
✗ Branch 2 not taken.
455 RVD_->compute_centroids(mg.data(), m.data());
153 index_t cur = 0;
154
2/2
✓ Branch 0 taken 1598000 times.
✓ Branch 1 taken 455 times.
1598455 for(index_t j = 0; j < nb_points; j++) {
155
2/2
✓ Branch 0 taken 1597691 times.
✓ Branch 1 taken 309 times.
1598000 if(m[j] > 1e-30 && !point_is_locked(j)) {
156 1597691 double s = 1.0 / m[j];
157
2/2
✓ Branch 0 taken 7900146 times.
✓ Branch 1 taken 1597691 times.
9497837 for(index_t coord = 0; coord < dimension_; coord++) {
158 7900146 points_[cur + coord] = s * mg[cur + coord];
159 }
160 }
161 1598000 cur += dimension_;
162 }
163
1/2
✓ Branch 1 taken 455 times.
✗ Branch 2 not taken.
455 newiteration();
164 }
165
166
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 4 times.
17 progress_ = nullptr;
167 17 }
168
169
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 void CentroidalVoronoiTesselation::compute_surface(
170 Mesh* mesh, bool multinerve
171 ) {
172 7 index_t nb_points = index_t(points_.size() / dimension_);
173 7 delaunay_->set_vertices(nb_points, points_.data());
174
175 vector<index_t> triangles;
176 vector<double> vertices;
177 vector<double> vertices_R3;
178
179 RestrictedVoronoiDiagram::RDTMode mode =
180 RestrictedVoronoiDiagram::RDTMode(0);
181
182
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if(multinerve) {
183 mode = RestrictedVoronoiDiagram::RDTMode(
184 mode | RestrictedVoronoiDiagram::RDT_MULTINERVE
185 );
186 }
187
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
7 if(use_RVC_centroids_) {
188 mode = RestrictedVoronoiDiagram::RDTMode(
189 mode | RestrictedVoronoiDiagram::RDT_RVC_CENTROIDS
190 | RestrictedVoronoiDiagram::RDT_PREFER_SEEDS
191 );
192 }
193
194
2/4
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
7 RVD_->set_check_SR(true);
195
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 RVD_->compute_RDT(
196
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 triangles, vertices, mode, point_is_locked_
197 );
198
199 // TODO: projection is not good when the embedding is not one-to-one,
200 // for instance with Gauss map. We should use barycentric coordinates
201 // instead.
202 7 index_t nb_vertices = index_t(vertices.size() / dimension_);
203
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 vertices_R3.resize(nb_vertices * 3);
204
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if(is_projection_) {
205 double* cur = vertices.data();
206
2/2
✓ Branch 0 taken 34998 times.
✓ Branch 1 taken 7 times.
35005 for(index_t v = 0; v < nb_vertices; v++) {
207 34998 vertices_R3[3 * v] = cur[0];
208 34998 vertices_R3[3 * v + 1] = cur[1];
209 34998 vertices_R3[3 * v + 2] = cur[2];
210 34998 cur += dimension_;
211 }
212 } else {
213 ✗ RVD_->project_points_on_surface(
214 nb_vertices, vertices.data(), vertices_R3
215 );
216 }
217
218
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 mesh->clear();
219
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 mesh->facets.assign_triangle_mesh(3, vertices_R3, triangles, true);
220
221
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if(multinerve) {
222
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 mesh_postprocess_RDT(*mesh);
223 } else {
224 // The 'repair' phase is needed to reconstruct the
225 // facet-facet links, that are not initialized by
226 // Mesh::assign_triangle_mesh()
227 ✗ double radius = bbox_diagonal(*mesh);
228 ✗ mesh_repair(*mesh, MESH_REPAIR_DEFAULT, 1e-6 * radius);
229 // TODO: check: is it really good to have some tolerance here,
230 // not sure, may cause some Moebius configs sometimes.
231 }
232 7 }
233
234 ✗ void CentroidalVoronoiTesselation::compute_volume(
235 Mesh* mesh
236 ) {
237 ✗ geo_assert(volumetric());
238 ✗ index_t nb_points = index_t(points_.size() / dimension_);
239 ✗ delaunay_->set_vertices(nb_points, points_.data());
240
241 vector<index_t> tets;
242 vector<double> vertices;
243 vector<double> vertices_R3;
244
245 ✗ RVD_->set_check_SR(true);
246 ✗ RVD_->compute_RDT(
247 tets, vertices
248 );
249
250 // TODO: projection is not good when the embedding is not one-to-one,
251 // for instance with Gauss map. We should use barycentric coordinates
252 // instead.
253 ✗ index_t nb_vertices = index_t(vertices.size() / dimension_);
254 ✗ vertices_R3.resize(nb_vertices * 3);
255 ✗ if(is_projection_) {
256 double* cur = vertices.data();
257 ✗ for(index_t v = 0; v < nb_vertices; v++) {
258 ✗ vertices_R3[3 * v] = cur[0];
259 ✗ vertices_R3[3 * v + 1] = cur[1];
260 ✗ vertices_R3[3 * v + 2] = cur[2];
261 ✗ cur += dimension_;
262 }
263 } else {
264 // TODO: map vertices from embedding space to 3D space
265 // when we are not in projection mode
266 ✗ geo_assert_not_reached;
267 }
268 ✗ mesh->clear();
269 ✗ mesh->cells.assign_tet_mesh(3, vertices_R3, tets, true);
270 ✗ }
271
272 5 void CentroidalVoronoiTesselation::Newton_iterations(
273 index_t nb_iter, index_t m
274 ) {
275
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 Optimizer_var optimizer = Optimizer::create("HLBFGS");
276
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if(optimizer.is_null()) {
277
2/6
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
5 Logger::warn("CVT") << "This geogram was not compiled with HLBFGS"
278 << " (falling back to Lloyd iterations)"
279 << std::endl;
280
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 Lloyd_iterations(nb_iter);
281 return;
282 }
283
284 index_t n = index_t(points_.size());
285
286 ✗ RVD_->set_check_SR(true);
287
288 ✗ if(progress_ != nullptr) {
289 ✗ progress_->reset(nb_iter);
290 }
291
292 ✗ cur_iter_ = 0;
293 ✗ nb_iter_ = nb_iter;
294
295 ✗ optimizer->set_epsg(0.0);
296 ✗ optimizer->set_epsf(0.0);
297 ✗ optimizer->set_epsx(0.0);
298 ✗ optimizer->set_newiteration_callback(newiteration_CB);
299 ✗ optimizer->set_funcgrad_callback(funcgrad_CB);
300 ✗ optimizer->set_N(n);
301 ✗ optimizer->set_M(m);
302 ✗ optimizer->set_max_iter(nb_iter);
303 ✗ optimizer->optimize(points_.data());
304
305 simplex_func_.reset();
306 ✗ progress_ = nullptr;
307 }
308
309 ✗ void CentroidalVoronoiTesselation::constrain_points(double* g) const {
310 ✗ if(point_is_locked_.size() != 0) {
311 double* cur_g = g;
312 ✗ for(index_t i = 0; i < nb_points(); ++i) {
313 ✗ if(point_is_locked_[i]) {
314 ✗ for(index_t c = 0; c < dimension_; c++) {
315 ✗ cur_g[c] = 0.0;
316 }
317 }
318 ✗ cur_g += dimension_;
319 }
320 }
321 ✗ }
322
323 ✗ void CentroidalVoronoiTesselation::funcgrad(
324 index_t n, double* x, double& f, double* g
325 ) {
326 ✗ index_t nb_points = n / dimension_;
327 ✗ delaunay_->set_vertices(nb_points, x);
328 ✗ Memory::clear(g, n * sizeof(double));
329 ✗ f = 0.0;
330 ✗ if(!simplex_func_.is_null()) {
331 ✗ RVD_->compute_integration_simplex_func_grad(
332 f,g,simplex_func_
333 );
334 } else {
335 ✗ RVD_->compute_CVT_func_grad(f, g);
336 }
337 ✗ constrain_points(g);
338 ✗ }
339
340 455 void CentroidalVoronoiTesselation::newiteration() {
341
2/2
✓ Branch 0 taken 440 times.
✓ Branch 1 taken 15 times.
455 if(progress_ != nullptr) {
342 440 progress_->next();
343 }
344 455 cur_iter_++;
345 455 }
346
347 ✗ void CentroidalVoronoiTesselation::funcgrad_CB(
348 index_t n, double* x, double& f, double* g
349 ) {
350 ✗ instance_->funcgrad(n, x, f, g);
351 ✗ }
352
353 ✗ void CentroidalVoronoiTesselation::newiteration_CB(
354 index_t n, const double* x, double f, const double* g, double gnorm
355 ) {
356 geo_argused(n);
357 geo_argused(x);
358 geo_argused(f);
359 geo_argused(g);
360 geo_argused(gnorm);
361 ✗ instance_->newiteration();
362 ✗ }
363
364 ✗ void CentroidalVoronoiTesselation::compute_R3_embedding() {
365 ✗ index_t nb_points = index_t(points_.size() / dimension_);
366 ✗ points_R3_.resize(nb_points);
367 ✗ if(is_projection_ && !constrained_cvt_) {
368 double* cur = points_.data();
369 ✗ for(index_t p = 0; p < nb_points; p++) {
370 ✗ points_R3_[p] = vec3(cur[0], cur[1], cur[2]);
371 ✗ cur += dimension_;
372 }
373 } else {
374 ✗ RVD_->project_points_on_surface(
375 ✗ nb_points, points_.data(), points_R3_, constrained_cvt_
376 );
377 }
378 ✗ }
379 }
380