Visual Computing Library
Loading...
Searching...
No Matches
convex_hull.h
1/*****************************************************************************
2 * VCLib *
3 * Visual Computing Library *
4 * *
5 * Copyright(C) 2021-2025 *
6 * Visual Computing Lab *
7 * ISTI - Italian National Research Council *
8 * *
9 * All rights reserved. *
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the Mozilla Public License Version 2.0 as published *
13 * by the Mozilla Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 * This program is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 * Mozilla Public License Version 2.0 *
20 * (https://www.mozilla.org/en-US/MPL/2.0/) for more details. *
21 ****************************************************************************/
22
23#ifndef VCL_ALGORITHMS_MESH_CONVEX_HULL_H
24#define VCL_ALGORITHMS_MESH_CONVEX_HULL_H
25
26#include <vclib/algorithms/core/bounding_box.h>
27#include <vclib/algorithms/core/box/box3.h>
28#include <vclib/algorithms/core/visibility.h>
29#include <vclib/algorithms/mesh/create/tetrahedron.h>
30#include <vclib/algorithms/mesh/update/topology.h>
31#include <vclib/concepts/mesh.h>
32#include <vclib/mesh/requirements.h>
33#include <vclib/misc/logger.h>
34#include <vclib/misc/parallel.h>
35#include <vclib/misc/shuffle.h>
36#include <vclib/space/complex/graph/bipartite_graph.h>
37#include <vclib/space/complex/mesh_pos.h>
38
39namespace vcl {
40
41namespace detail {
42
51template<Range R>
52void firstTetOptimization(R&& points)
53{
54 using PointType = std::ranges::range_value_t<R>;
55 using Iterator = std::ranges::iterator_t<R>;
56
57 Box<PointType> box = boundingBox(points);
58
59 std::array<std::pair<double, Iterator>, 6> distances;
60
61 // save in the array the closest point to each of the 6 sides of the box
62 for (uint i = 0; i < 6; ++i) {
63 auto it = std::ranges::begin(points);
64 const auto& boxCorner = i < 3 ? box.min() : box.max();
65 distances[i] =
66 std::make_pair(std::abs(boxCorner(i % 3) - (*it)(i % 3)), it);
67 for (++it; it != std::ranges::end(points); ++it) {
68 double d = std::abs(boxCorner(i % 3) - (*it)(i % 3));
69 if (d < distances[i].first) {
70 distances[i] = std::make_pair(d, it);
71 }
72 }
73 }
74
75 // sort the array by distance
76 std::sort(distances.begin(), distances.end(), [](auto& a, auto& b) {
77 return a.first < b.first;
78 });
79
80 // swap the first points with the closest points to the box corners
81 for (uint i = 0; i < 6; ++i) {
82 std::iter_swap(std::ranges::begin(points) + i, distances[i].second);
83 }
84}
85
94template<Range R>
95void shufflePoints(R&& points, bool deterministic = false)
96 requires Point3Concept<std::ranges::range_value_t<R>>
97{
98 shuffle(points, deterministic);
99
100 firstTetOptimization(points);
101
102 auto itP0 = std::ranges::begin(points);
103 auto itP1 = std::next(itP0);
104 auto itP2 = std::next(itP1);
105 auto itP3 = std::next(itP2);
106 auto itP = std::next(itP2);
107
108 auto rEnd = std::ranges::end(points);
109
110 while (itP != rEnd && arePointsCoplanar(*itP0, *itP1, *itP2, *itP)) {
111 itP = std::next(itP);
112 }
113
114 if (itP == rEnd) {
115 throw std::runtime_error("All points are coplanar.");
116 }
117
118 std::iter_swap(itP, itP3);
119}
120
121template<FaceMeshConcept MeshType, Point3Concept PointType>
122MeshType makeTetrahedron(
123 const PointType& p0,
124 const PointType& p1,
125 const PointType& p2,
126 const PointType& p3)
127{
128 using FaceType = MeshType::FaceType;
129
130 MeshType result;
131
132 if (trianglePointVisibility(p0, p1, p2, p3)) {
133 result = createTetrahedron<MeshType>(p0, p2, p1, p3);
134 }
135 else {
136 result = createTetrahedron<MeshType>(p0, p1, p2, p3);
137 }
138
139 if constexpr (face::HasOptionalAdjacentFaces<FaceType>) {
140 result.enablePerFaceAdjacentFaces();
141 }
142
143 updatePerFaceAdjacentFaces(result);
144
145 return result;
146}
147
148template<FaceMeshConcept MeshType, Range R>
149auto initConflictGraph(const MeshType& mesh, R&& points)
150 requires Point3Concept<std::ranges::range_value_t<R>>
151{
152 using PointType = std::ranges::range_value_t<R>;
153 using MPointType = MeshType::VertexType::CoordType;
154 using FaceType = MeshType::FaceType;
155 using GraphType = BipartiteGraph<PointType, uint>;
156
157 // left nodes are points, right nodes are faces
158 // an arc (conflict) is added if a point is visible from a face
159 GraphType graph;
160
161 for (const auto& face : mesh.faces()) {
162 graph.addRightNode(face.index());
163 }
164
165 for (const auto& point : points) {
166 bool added = false;
167 for (const auto& face : mesh.faces()) {
168 if (facePointVisibility(face, point)) {
169 if (!added) {
170 graph.addLeftNode(point);
171 added = true;
172 }
173 graph.addArc(point, face.index());
174 }
175 }
176 }
177
178 return graph;
179}
180
194template<FaceConcept FaceType, VertexConcept VertexType>
195std::vector<std::pair<uint, uint>> horizonFaces(
196 const std::set<const FaceType*>& visibleFaces,
197 std::set<const VertexType*>& horizonVertices)
198{
199 std::vector<std::pair<uint, uint>> horizon;
200
201 const FaceType* firstFace = nullptr;
202 uint firstEdge = UINT_NULL;
203 const FaceType* currentFace = nullptr;
204 uint currentEdge = UINT_NULL;
205
206 // looking for the first visible face that lies on the border
207 bool found = false;
208 for (auto it = visibleFaces.begin(); it != visibleFaces.end() && !found;
209 ++it) {
210 uint i = 0;
211 for (const FaceType* adjFace : (*it)->adjFaces()) {
212 // if the adjacent face is not visible, then it is on the border
213 if (visibleFaces.find(adjFace) == visibleFaces.end()) {
214 firstFace = *it;
215 firstEdge = i;
216 currentFace = firstFace;
217 currentEdge = firstEdge;
218 found = true;
219 break;
220 }
221 i++;
222 }
223 }
224
225 do {
226 MeshPos<FaceType> pos(currentFace, currentEdge);
227 pos.flipVertex();
228 horizonVertices.insert(pos.vertex());
229
230 while (visibleFaces.find(pos.face()) != visibleFaces.end()) {
231 pos.nextEdgeAdjacentToV();
232 }
233 auto p = std::make_pair(pos.face()->index(), pos.edge());
234 horizon.push_back(p);
235
236 pos.flipFace();
237 currentFace = pos.face();
238 currentEdge = pos.edge();
239 } while (currentFace != firstFace || currentEdge != firstEdge);
240
241 return horizon;
242}
243
244template<Point3Concept PointType, FaceMeshConcept MeshType>
245auto potentialConflictPoints(
246 const BipartiteGraph<PointType, uint>& conflictGraph,
247 const PointType& currentPoint,
248 const MeshType& convexHull,
249 const auto& horizon)
250{
251 using FaceType = MeshType::FaceType;
252
253 std::vector<std::set<PointType>> potentialConflictPoints(horizon.size());
254
255 uint i = 0;
256 for (const auto& [faceIndex, edgeIndex] : horizon) {
257 for (const auto& p : conflictGraph.adjacentRightNodes(faceIndex)) {
258 potentialConflictPoints[i].insert(p);
259 }
260
261 const FaceType& face = convexHull.face(faceIndex);
262
263 uint adjFaceIndex = face.adjFace(edgeIndex)->index();
264
265 for (const auto& p : conflictGraph.adjacentRightNodes(adjFaceIndex)) {
266 potentialConflictPoints[i].insert(p);
267 }
268
269 potentialConflictPoints[i].erase(currentPoint);
270
271 i++;
272 }
273
274 return potentialConflictPoints;
275}
276
277template<
278 FaceMeshConcept MeshType,
279 FaceConcept FaceType,
280 VertexConcept VertexType>
281void deleteVisibleFaces(
282 MeshType& convexHull,
283 const std::set<const FaceType*>& visibleFaces,
284 const std::set<const VertexType*>& horizonVertices)
285{
286 std::set<const VertexType*> verticesToDelete;
287
288 for (const FaceType* face : visibleFaces) {
289 for (const VertexType* vertex : face->vertices()) {
290 if (horizonVertices.find(vertex) == horizonVertices.end()) {
291 verticesToDelete.insert(vertex);
292 }
293 }
294 convexHull.deleteFace(face->index());
295 }
296
297 for (const VertexType* vertex : verticesToDelete) {
298 convexHull.deleteVertex(vertex->index());
299 }
300}
301
302template<Point3Concept PointType, FaceMeshConcept MeshType>
303std::vector<uint> addNewFaces(
304 MeshType& convexHull,
305 const PointType& currentPoint,
306 const std::vector<std::pair<uint, uint>>& horizon)
307{
308 using VertexType = MeshType::VertexType;
309 using FaceType = MeshType::FaceType;
310
311 std::vector<uint> newFaces;
312
313 newFaces.reserve(horizon.size());
314
315 uint v = convexHull.addVertex(currentPoint);
316
317 uint firstFace = UINT_NULL;
318 uint lastFace = UINT_NULL;
319
320 for (const auto& [faceIndex, edgeIndex] : horizon) {
321 uint v1 = convexHull.face(faceIndex).vertexIndexMod(edgeIndex + 1);
322 uint v2 = convexHull.face(faceIndex).vertexIndex(edgeIndex);
323
324 uint f = convexHull.addFace(v, v1, v2);
325 newFaces.push_back(f);
326
327 if (firstFace == UINT_NULL) {
328 firstFace = f;
329 }
330
331 convexHull.face(f).setAdjFace(0, lastFace);
332 if (lastFace != UINT_NULL)
333 convexHull.face(lastFace).setAdjFace(2, f);
334
335 convexHull.face(f).setAdjFace(1, faceIndex);
336 convexHull.face(faceIndex).setAdjFace(edgeIndex, f);
337
338 lastFace = f;
339 }
340
341 convexHull.face(firstFace).setAdjFace(0, lastFace);
342 convexHull.face(lastFace).setAdjFace(2, firstFace);
343
344 return newFaces;
345}
346
347template<Point3Concept PointType, FaceMeshConcept MeshType>
348void updateNewConflicts(
349 BipartiteGraph<PointType, uint>& conflictGraph,
350 const MeshType& convexHull,
351 const std::vector<uint>& newFaces,
352 const std::vector<std::set<PointType>>& potentialConflictPoints)
353{
354 using FaceType = MeshType::FaceType;
355
356 for (uint i = 0; i < newFaces.size(); ++i) {
357 conflictGraph.addRightNode(newFaces[i]);
358
359 const FaceType& face = convexHull.face(newFaces[i]);
360
361 for (const auto& p : potentialConflictPoints[i]) {
362 if (facePointVisibility(face, p)) {
363 conflictGraph.addArc(p, newFaces[i]);
364 }
365 }
366 }
367}
368
369} // namespace detail
370
382template<FaceMeshConcept MeshType, Range R, LoggerConcept LogType = NullLogger>
383MeshType convexHull(
384 const R& points,
385 bool deterministic = false,
386 LogType& log = nullLogger)
388{
389 using PointType = std::ranges::range_value_t<R>;
390 using VertexType = MeshType::VertexType;
391 using FaceType = MeshType::FaceType;
392
393 static_assert(
395 "MeshType must have per-face adjacent faces.");
396
397 log.log(0, "Shuffling points...");
398
399 std::vector<PointType> pointsCopy(
400 std::ranges::begin(points), std::ranges::end(points));
401
402 detail::shufflePoints(pointsCopy, deterministic);
403
404 log.log(0, "Making first tetrahedron...");
405
406 auto result = detail::makeTetrahedron<MeshType>(
408
409 auto remainingPoints = pointsCopy | std::views::drop(4);
410
411 auto conflictGraph = detail::initConflictGraph(result, remainingPoints);
412
413 log.log(0, "Computing convex hull...");
414
415 log.startProgress("Processing Points...", pointsCopy.size() - 4);
416 uint iteration = 0;
417 // for each point in the conflict graph (still not in the convex hull)
418 for (const auto& point : conflictGraph.leftNodes()) {
419 // if the point is visible from a face in the convex hull
420 if (conflictGraph.adjacentLeftNodeNumber(point) != 0) {
421 // store the faces that are visible from (conflict with) the point
422 // these faces will be deleted from the convex hull
423 std::set<const FaceType*> visibleFaces;
424 for (const auto& faceIndex :
425 conflictGraph.adjacentLeftNodes(point)) {
426 visibleFaces.insert(&result.face(faceIndex));
427 }
428
429 // compute the horizon of the visible faces
430 std::set<const VertexType*> horizonVertices;
431 auto horizon = detail::horizonFaces(visibleFaces, horizonVertices);
432
433 // for each edge in the horizon, compute the points that could
434 // conflict with the new face that will be added to the convex hull
435 // in that edge
436 auto potentialConflictPoints = detail::potentialConflictPoints(
437 conflictGraph, point, result, horizon);
438
439 // delete the point and faces from the conflict graph - no more
440 // conflicts
441 conflictGraph.deleteLeftNode(point);
442 for (const FaceType* face : visibleFaces) {
443 conflictGraph.deleteRightNode(face->index());
444 }
445
446 // delete the visible faces from the convex hull, and the vertices
447 // that are not on the horizon
448 detail::deleteVisibleFaces(result, visibleFaces, horizonVertices);
449
450 // add the new faces to the convex hull
451 auto newFaces = detail::addNewFaces(result, point, horizon);
452
453 // update the conflict graph with the new faces
454 detail::updateNewConflicts(
455 conflictGraph, result, newFaces, potentialConflictPoints);
456 }
457 else {
458 conflictGraph.deleteLeftNode(point);
459 }
460 iteration++;
461 log.progress(iteration);
462 }
463
464 log.endProgress();
465 log.log(100, "Convex hull computed.");
466
467 return result;
468}
469
480template<FaceMeshConcept MeshType, Range R, LoggerConcept LogType = NullLogger>
481MeshType convexHull(const R& points, LogType& log)
483{
484 return convexHull<MeshType>(points, false, log);
485}
486
487} // namespace vcl
488
489#endif // VCL_ALGORITHMS_MESH_CONVEX_HULL_H
A class representing a line segment in n-dimensional space. The class is parameterized by a PointConc...
Definition segment.h:43
Concept for points in three-dimensional space.
Definition point.h:130
Definition face.h:52
bool facePointVisibility(const FaceType &face, const PointType &point)
Checks if a point is visible from a face, i.e., if the point is in the half-space defined by the face...
Definition visibility.h:185
bool trianglePointVisibility(const TriangleType &triangle, const PointType &point)
Checks if a point is visible from a triangle, i.e., if the point is in the half-space defined by the ...
Definition visibility.h:142
bool arePointsCoplanar(const PointType &p1, const PointType &p2, const PointType &p3, const PointType &p4)
Checks if 4 points are coplanar.
Definition visibility.h:120
auto boundingBox(const PointType &p)
Compute the bounding box of a single point.
Definition bounding_box.h:65
auto convexHull(InputIterator first, InputIterator end)
Get the 2D convex hull using Graham scan algorithm on a set of points.
Definition convex_hull.h:137
NullLogger nullLogger
The nullLogger object is an object of type NullLogger that is used as default argument in the functio...
Definition null_logger.h:125
void shuffle(R &&range, bool deterministic=false)
Shuffle the elements of a range.
Definition shuffle.h:43
constexpr uint UINT_NULL
The UINT_NULL value represent a null value of uint that is the maximum value that can be represented ...
Definition base.h:48
constexpr detail::FacesView faces
A view that allows to iterate overt the Face elements of an object.
Definition face.h:52
constexpr detail::AdjFacesView adjFaces
The adjFaces view allows to obtain a view that access to the adjacent faces of the object that has be...
Definition adj_faces.h:65
constexpr detail::VerticesView vertices
A view that allows to iterate over the Vertex elements of an object.
Definition vertex.h:60