Visual Computing Library  devel
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/mesh/create/tetrahedron.h>
27#include <vclib/algorithms/mesh/update/topology.h>
28
29#include <vclib/algorithms/core.h>
30#include <vclib/mesh.h>
31#include <vclib/space/complex.h>
32
33namespace vcl {
34
35namespace detail {
36
45template<Range R>
46void firstTetOptimization(R&& points)
47{
48 using PointType = std::ranges::range_value_t<R>;
49 using Iterator = std::ranges::iterator_t<R>;
50
51 Box<PointType> box = boundingBox(points);
52
53 std::array<std::pair<double, Iterator>, 6> distances;
54
55 // save in the array the closest point to each of the 6 sides of the box
56 for (uint i = 0; i < 6; ++i) {
57 auto it = std::ranges::begin(points);
58 const auto& boxCorner = i < 3 ? box.min() : box.max();
59 distances[i] =
60 std::make_pair(std::abs(boxCorner(i % 3) - (*it)(i % 3)), it);
61 for (++it; it != std::ranges::end(points); ++it) {
62 double d = std::abs(boxCorner(i % 3) - (*it)(i % 3));
63 if (d < distances[i].first) {
64 distances[i] = std::make_pair(d, it);
65 }
66 }
67 }
68
69 // sort the array by distance
70 std::sort(distances.begin(), distances.end(), [](auto& a, auto& b) {
71 return a.first < b.first;
72 });
73
74 // swap the first points with the closest points to the box corners
75 for (uint i = 0; i < 6 && i < points.size(); ++i) {
76 std::iter_swap(std::ranges::begin(points) + i, distances[i].second);
77 }
78}
79
89template<Range R>
90void shufflePoints(R&& points, std::optional<uint> seed = std::nullopt)
91 requires Point3Concept<std::ranges::range_value_t<R>>
92{
93 shuffle(points, seed);
94
95 firstTetOptimization(points);
96
97 auto itP0 = std::ranges::begin(points);
98 auto itP = std::next(itP0);
99
100 // make sure that the first two points are not coincident
101 while (itP != std::ranges::end(points) && *itP0 == *itP) {
102 itP = std::next(itP);
103 }
104 if (itP == std::ranges::end(points)) {
105 throw std::runtime_error("All points are coincident.");
106 }
107 auto itP1 = std::next(itP0);
108 std::iter_swap(itP, itP1);
109
110 itP = std::next(itP);
111
112 // make sure that the first three points are not collinear
113 while (itP != std::ranges::end(points) &&
114 arePointsCollinear(*itP0, *itP1, *itP)) {
115 itP = std::next(itP);
116 }
117 if (itP == std::ranges::end(points)) {
118 throw std::runtime_error("All points are collinear.");
119 }
120 auto itP2 = std::next(itP1);
121 std::iter_swap(itP, itP2);
122
123 itP = std::next(itP);
124
125 auto rEnd = std::ranges::end(points);
126
127 while (itP != rEnd && arePointsCoplanar(*itP0, *itP1, *itP2, *itP)) {
128 itP = std::next(itP);
129 }
130 if (itP == rEnd) {
131 throw std::runtime_error("All points are coplanar.");
132 }
133 auto itP3 = std::next(itP2);
134 std::iter_swap(itP, itP3);
135}
136
137template<FaceMeshConcept MeshType, Point3Concept PointType>
138MeshType makeTetrahedron(
139 const PointType& p0,
140 const PointType& p1,
141 const PointType& p2,
142 const PointType& p3)
143{
144 using FaceType = MeshType::FaceType;
145
146 MeshType result;
147
148 if (trianglePointVisibility(p0, p1, p2, p3)) {
149 result = createTetrahedron<MeshType>(p0, p2, p1, p3);
150 }
151 else {
152 result = createTetrahedron<MeshType>(p0, p1, p2, p3);
153 }
154
155 if constexpr (face::HasOptionalAdjacentFaces<FaceType>) {
156 result.enablePerFaceAdjacentFaces();
157 }
158
159 updatePerFaceAdjacentFaces(result);
160
161 return result;
162}
163
164template<FaceMeshConcept MeshType, Range R>
165auto initConflictGraph(const MeshType& mesh, R&& points)
166 requires Point3Concept<std::ranges::range_value_t<R>>
167{
168 using PointType = std::ranges::range_value_t<R>;
169 using MPointType = MeshType::VertexType::PositionType;
170 using FaceType = MeshType::FaceType;
171 using GraphType = BipartiteGraph<PointType, uint>;
172
173 // left nodes are points, right nodes are faces
174 // an arc (conflict) is added if a point is visible from a face
175 GraphType graph;
176
177 for (const auto& face : mesh.faces()) {
178 graph.addRightNode(face.index());
179 }
180
181 for (const auto& point : points) {
182 bool added = false;
183 for (const auto& face : mesh.faces()) {
184 if (facePointVisibility(face, point)) {
185 if (!added) {
186 graph.addLeftNode(point);
187 added = true;
188 }
189 graph.addArc(point, face.index());
190 }
191 }
192 }
193
194 return graph;
195}
196
210template<FaceConcept FaceType, VertexConcept VertexType>
211std::vector<std::pair<uint, uint>> horizonFaces(
212 const std::set<const FaceType*>& visibleFaces,
213 std::set<const VertexType*>& horizonVertices)
214{
215 std::vector<std::pair<uint, uint>> horizon;
216
217 const FaceType* firstFace = nullptr;
218 uint firstEdge = UINT_NULL;
219 const FaceType* currentFace = nullptr;
220 uint currentEdge = UINT_NULL;
221
222 // looking for the first visible face that lies on the border
223 bool found = false;
224 for (auto it = visibleFaces.begin(); it != visibleFaces.end() && !found;
225 ++it) {
226 uint i = 0;
227 for (const FaceType* adjFace : (*it)->adjFaces()) {
228 // if the adjacent face is not visible, then it is on the border
229 if (visibleFaces.find(adjFace) == visibleFaces.end()) {
230 firstFace = *it;
231 firstEdge = i;
232 currentFace = firstFace;
233 currentEdge = firstEdge;
234 found = true;
235 break;
236 }
237 i++;
238 }
239 }
240
241 do {
242 MeshPos<FaceType> pos(currentFace, currentEdge);
243 pos.flipVertex();
244 horizonVertices.insert(pos.vertex());
245
246 while (visibleFaces.find(pos.face()) != visibleFaces.end()) {
247 pos.nextEdgeAdjacentToV();
248 }
249 auto p = std::make_pair(pos.face()->index(), pos.edge());
250 horizon.push_back(p);
251
252 pos.flipFace();
253 currentFace = pos.face();
254 currentEdge = pos.edge();
255 } while (currentFace != firstFace || currentEdge != firstEdge);
256
257 return horizon;
258}
259
260template<Point3Concept PointType, FaceMeshConcept MeshType>
261auto potentialConflictPoints(
262 const BipartiteGraph<PointType, uint>& conflictGraph,
263 const PointType& currentPoint,
264 const MeshType& convexHull,
265 const auto& horizon)
266{
267 using FaceType = MeshType::FaceType;
268
269 std::vector<std::set<PointType>> potentialConflictPoints(horizon.size());
270
271 uint i = 0;
272 for (const auto& [faceIndex, edgeIndex] : horizon) {
273 for (const auto& p : conflictGraph.adjacentRightNodes(faceIndex)) {
274 potentialConflictPoints[i].insert(p);
275 }
276
277 const FaceType& face = convexHull.face(faceIndex);
278
279 uint adjFaceIndex = face.adjFace(edgeIndex)->index();
280
281 for (const auto& p : conflictGraph.adjacentRightNodes(adjFaceIndex)) {
282 potentialConflictPoints[i].insert(p);
283 }
284
285 potentialConflictPoints[i].erase(currentPoint);
286
287 i++;
288 }
289
290 return potentialConflictPoints;
291}
292
293template<
294 FaceMeshConcept MeshType,
295 FaceConcept FaceType,
296 VertexConcept VertexType>
297void deleteVisibleFaces(
298 MeshType& convexHull,
299 const std::set<const FaceType*>& visibleFaces,
300 const std::set<const VertexType*>& horizonVertices)
301{
302 std::set<const VertexType*> verticesToDelete;
303
304 for (const FaceType* face : visibleFaces) {
305 for (const VertexType* vertex : face->vertices()) {
306 if (horizonVertices.find(vertex) == horizonVertices.end()) {
307 verticesToDelete.insert(vertex);
308 }
309 }
310 convexHull.deleteFace(face->index());
311 }
312
313 for (const VertexType* vertex : verticesToDelete) {
314 convexHull.deleteVertex(vertex->index());
315 }
316}
317
318template<Point3Concept PointType, FaceMeshConcept MeshType>
319std::vector<uint> addNewFaces(
320 MeshType& convexHull,
321 const PointType& currentPoint,
322 const std::vector<std::pair<uint, uint>>& horizon)
323{
324 using VertexType = MeshType::VertexType;
325 using FaceType = MeshType::FaceType;
326
327 std::vector<uint> newFaces;
328
329 newFaces.reserve(horizon.size());
330
331 uint v = convexHull.addVertex(currentPoint);
332
333 uint firstFace = UINT_NULL;
334 uint lastFace = UINT_NULL;
335
336 for (const auto& [faceIndex, edgeIndex] : horizon) {
337 uint v1 = convexHull.face(faceIndex).vertexIndexMod(edgeIndex + 1);
338 uint v2 = convexHull.face(faceIndex).vertexIndex(edgeIndex);
339
340 uint f = convexHull.addFace(v, v1, v2);
341 newFaces.push_back(f);
342
343 if (firstFace == UINT_NULL) {
344 firstFace = f;
345 }
346
347 convexHull.face(f).setAdjFace(0u, lastFace);
348 if (lastFace != UINT_NULL)
349 convexHull.face(lastFace).setAdjFace(2u, f);
350
351 convexHull.face(f).setAdjFace(1u, faceIndex);
352 convexHull.face(faceIndex).setAdjFace(edgeIndex, f);
353
354 lastFace = f;
355 }
356
357 convexHull.face(firstFace).setAdjFace(0u, lastFace);
358 convexHull.face(lastFace).setAdjFace(2u, firstFace);
359
360 return newFaces;
361}
362
363template<Point3Concept PointType, FaceMeshConcept MeshType>
364void updateNewConflicts(
365 BipartiteGraph<PointType, uint>& conflictGraph,
366 const MeshType& convexHull,
367 const std::vector<uint>& newFaces,
368 const std::vector<std::set<PointType>>& potentialConflictPoints)
369{
370 using FaceType = MeshType::FaceType;
371
372 for (uint i = 0; i < newFaces.size(); ++i) {
373 conflictGraph.addRightNode(newFaces[i]);
374
375 const FaceType& face = convexHull.face(newFaces[i]);
376
377 for (const auto& p : potentialConflictPoints[i]) {
378 if (facePointVisibility(face, p)) {
379 conflictGraph.addArc(p, newFaces[i]);
380 }
381 }
382 }
383}
384
385} // namespace detail
386
399template<FaceMeshConcept MeshType, Range R, LoggerConcept LogType = NullLogger>
400MeshType convexHull(
401 const R& points,
402 std::optional<uint> seed = std::nullopt,
403 LogType& log = nullLogger)
405{
406 using PointType = std::ranges::range_value_t<R>;
407 using VertexType = MeshType::VertexType;
408 using FaceType = MeshType::FaceType;
409
410 static_assert(
412 "MeshType must have per-face adjacent faces.");
413
414 log.log(0, "Shuffling points...");
415
416 std::vector<PointType> pointsCopy(
417 std::ranges::begin(points), std::ranges::end(points));
418
419 detail::shufflePoints(pointsCopy, seed);
420
421 log.log(0, "Making first tetrahedron...");
422
423 auto result = detail::makeTetrahedron<MeshType>(
425
426 auto remainingPoints = pointsCopy | std::views::drop(4);
427
428 auto conflictGraph = detail::initConflictGraph(result, remainingPoints);
429
430 log.log(0, "Computing convex hull...");
431
432 log.startProgress("Processing Points...", pointsCopy.size() - 4);
433 uint iteration = 0;
434 // for each point in the conflict graph (still not in the convex hull)
435 for (const auto& point : conflictGraph.leftNodes()) {
436 // if the point is visible from a face in the convex hull
437 if (conflictGraph.adjacentLeftNodeNumber(point) != 0) {
438 // store the faces that are visible from (conflict with) the point
439 // these faces will be deleted from the convex hull
440 std::set<const FaceType*> visibleFaces;
441 for (const auto& faceIndex :
442 conflictGraph.adjacentLeftNodes(point)) {
443 visibleFaces.insert(&result.face(faceIndex));
444 }
445
446 // compute the horizon of the visible faces
447 std::set<const VertexType*> horizonVertices;
448 auto horizon = detail::horizonFaces(visibleFaces, horizonVertices);
449
450 // for each edge in the horizon, compute the points that could
451 // conflict with the new face that will be added to the convex hull
452 // in that edge
453 auto potentialConflictPoints = detail::potentialConflictPoints(
454 conflictGraph, point, result, horizon);
455
456 // delete the point and faces from the conflict graph - no more
457 // conflicts
458 conflictGraph.deleteLeftNode(point);
459 for (const FaceType* face : visibleFaces) {
460 conflictGraph.deleteRightNode(face->index());
461 }
462
463 // delete the visible faces from the convex hull, and the vertices
464 // that are not on the horizon
465 detail::deleteVisibleFaces(result, visibleFaces, horizonVertices);
466
467 // add the new faces to the convex hull
468 auto newFaces = detail::addNewFaces(result, point, horizon);
469
470 // update the conflict graph with the new faces
471 detail::updateNewConflicts(
472 conflictGraph, result, newFaces, potentialConflictPoints);
473 }
474 else {
475 conflictGraph.deleteLeftNode(point);
476 }
477 iteration++;
478 log.progress(iteration);
479 }
480
481 log.endProgress();
482 log.log(100, "Convex hull computed.");
483
484 return result;
485}
486
497template<FaceMeshConcept MeshType, Range R, LoggerConcept LogType = NullLogger>
498MeshType convexHull(const R& points, LogType& log)
500{
501 return convexHull<MeshType>(points, std::nullopt, log);
502}
503
504} // namespace vcl
505
506#endif // VCL_ALGORITHMS_MESH_CONVEX_HULL_H
A class representing a box in N-dimensional space.
Definition box.h:46
PointT size() const
Computes the size of the box.
Definition box.h:267
A concept representing a 3D Point.
Definition point.h:872
Definition face_components.h:73
bool arePointsCollinear(const PointType &p0, const PointType &p1, const PointType &p2)
Checks if 3 points are collinear.
Definition visibility.h:88
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:85
bool arePointsCoplanar(const PointType &p0, const PointType &p1, const PointType &p2, const PointType &p3)
Checks if 4 points are coplanar.
Definition visibility.h:110
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:132
NullLogger nullLogger
The nullLogger object is an object of type NullLogger that is used as default argument in the functio...
Definition null_logger.h:123
void shuffle(R &&range, std::optional< uint > seed=std::nullopt)
Shuffle the elements of a range.
Definition random.h:71
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
auto boundingBox(const PointType &p)
Compute the bounding box of a single point.
Definition bounding_box.h:59
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
constexpr detail::FacesView faces
A view that allows to iterate overt the Face elements of an object.
Definition face.h:84
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:92