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) {
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 itP1 = std::next(itP0);
99 auto itP2 = std::next(itP1);
100 auto itP3 = std::next(itP2);
101 auto itP = std::next(itP2);
102
103 auto rEnd = std::ranges::end(points);
104
105 while (itP != rEnd && arePointsCoplanar(*itP0, *itP1, *itP2, *itP)) {
106 itP = std::next(itP);
107 }
108
109 if (itP == rEnd) {
110 throw std::runtime_error("All points are coplanar.");
111 }
112
113 std::iter_swap(itP, itP3);
114}
115
116template<FaceMeshConcept MeshType, Point3Concept PointType>
117MeshType makeTetrahedron(
118 const PointType& p0,
119 const PointType& p1,
120 const PointType& p2,
121 const PointType& p3)
122{
123 using FaceType = MeshType::FaceType;
124
125 MeshType result;
126
127 if (trianglePointVisibility(p0, p1, p2, p3)) {
128 result = createTetrahedron<MeshType>(p0, p2, p1, p3);
129 }
130 else {
131 result = createTetrahedron<MeshType>(p0, p1, p2, p3);
132 }
133
134 if constexpr (face::HasOptionalAdjacentFaces<FaceType>) {
135 result.enablePerFaceAdjacentFaces();
136 }
137
138 updatePerFaceAdjacentFaces(result);
139
140 return result;
141}
142
143template<FaceMeshConcept MeshType, Range R>
144auto initConflictGraph(const MeshType& mesh, R&& points)
145 requires Point3Concept<std::ranges::range_value_t<R>>
146{
147 using PointType = std::ranges::range_value_t<R>;
148 using MPointType = MeshType::VertexType::PositionType;
149 using FaceType = MeshType::FaceType;
150 using GraphType = BipartiteGraph<PointType, uint>;
151
152 // left nodes are points, right nodes are faces
153 // an arc (conflict) is added if a point is visible from a face
154 GraphType graph;
155
156 for (const auto& face : mesh.faces()) {
157 graph.addRightNode(face.index());
158 }
159
160 for (const auto& point : points) {
161 bool added = false;
162 for (const auto& face : mesh.faces()) {
163 if (facePointVisibility(face, point)) {
164 if (!added) {
165 graph.addLeftNode(point);
166 added = true;
167 }
168 graph.addArc(point, face.index());
169 }
170 }
171 }
172
173 return graph;
174}
175
189template<FaceConcept FaceType, VertexConcept VertexType>
190std::vector<std::pair<uint, uint>> horizonFaces(
191 const std::set<const FaceType*>& visibleFaces,
192 std::set<const VertexType*>& horizonVertices)
193{
194 std::vector<std::pair<uint, uint>> horizon;
195
196 const FaceType* firstFace = nullptr;
197 uint firstEdge = UINT_NULL;
198 const FaceType* currentFace = nullptr;
199 uint currentEdge = UINT_NULL;
200
201 // looking for the first visible face that lies on the border
202 bool found = false;
203 for (auto it = visibleFaces.begin(); it != visibleFaces.end() && !found;
204 ++it) {
205 uint i = 0;
206 for (const FaceType* adjFace : (*it)->adjFaces()) {
207 // if the adjacent face is not visible, then it is on the border
208 if (visibleFaces.find(adjFace) == visibleFaces.end()) {
209 firstFace = *it;
210 firstEdge = i;
211 currentFace = firstFace;
212 currentEdge = firstEdge;
213 found = true;
214 break;
215 }
216 i++;
217 }
218 }
219
220 do {
221 MeshPos<FaceType> pos(currentFace, currentEdge);
222 pos.flipVertex();
223 horizonVertices.insert(pos.vertex());
224
225 while (visibleFaces.find(pos.face()) != visibleFaces.end()) {
226 pos.nextEdgeAdjacentToV();
227 }
228 auto p = std::make_pair(pos.face()->index(), pos.edge());
229 horizon.push_back(p);
230
231 pos.flipFace();
232 currentFace = pos.face();
233 currentEdge = pos.edge();
234 } while (currentFace != firstFace || currentEdge != firstEdge);
235
236 return horizon;
237}
238
239template<Point3Concept PointType, FaceMeshConcept MeshType>
240auto potentialConflictPoints(
241 const BipartiteGraph<PointType, uint>& conflictGraph,
242 const PointType& currentPoint,
243 const MeshType& convexHull,
244 const auto& horizon)
245{
246 using FaceType = MeshType::FaceType;
247
248 std::vector<std::set<PointType>> potentialConflictPoints(horizon.size());
249
250 uint i = 0;
251 for (const auto& [faceIndex, edgeIndex] : horizon) {
252 for (const auto& p : conflictGraph.adjacentRightNodes(faceIndex)) {
253 potentialConflictPoints[i].insert(p);
254 }
255
256 const FaceType& face = convexHull.face(faceIndex);
257
258 uint adjFaceIndex = face.adjFace(edgeIndex)->index();
259
260 for (const auto& p : conflictGraph.adjacentRightNodes(adjFaceIndex)) {
261 potentialConflictPoints[i].insert(p);
262 }
263
264 potentialConflictPoints[i].erase(currentPoint);
265
266 i++;
267 }
268
269 return potentialConflictPoints;
270}
271
272template<
273 FaceMeshConcept MeshType,
274 FaceConcept FaceType,
275 VertexConcept VertexType>
276void deleteVisibleFaces(
277 MeshType& convexHull,
278 const std::set<const FaceType*>& visibleFaces,
279 const std::set<const VertexType*>& horizonVertices)
280{
281 std::set<const VertexType*> verticesToDelete;
282
283 for (const FaceType* face : visibleFaces) {
284 for (const VertexType* vertex : face->vertices()) {
285 if (horizonVertices.find(vertex) == horizonVertices.end()) {
286 verticesToDelete.insert(vertex);
287 }
288 }
289 convexHull.deleteFace(face->index());
290 }
291
292 for (const VertexType* vertex : verticesToDelete) {
293 convexHull.deleteVertex(vertex->index());
294 }
295}
296
297template<Point3Concept PointType, FaceMeshConcept MeshType>
298std::vector<uint> addNewFaces(
299 MeshType& convexHull,
300 const PointType& currentPoint,
301 const std::vector<std::pair<uint, uint>>& horizon)
302{
303 using VertexType = MeshType::VertexType;
304 using FaceType = MeshType::FaceType;
305
306 std::vector<uint> newFaces;
307
308 newFaces.reserve(horizon.size());
309
310 uint v = convexHull.addVertex(currentPoint);
311
312 uint firstFace = UINT_NULL;
313 uint lastFace = UINT_NULL;
314
315 for (const auto& [faceIndex, edgeIndex] : horizon) {
316 uint v1 = convexHull.face(faceIndex).vertexIndexMod(edgeIndex + 1);
317 uint v2 = convexHull.face(faceIndex).vertexIndex(edgeIndex);
318
319 uint f = convexHull.addFace(v, v1, v2);
320 newFaces.push_back(f);
321
322 if (firstFace == UINT_NULL) {
323 firstFace = f;
324 }
325
326 convexHull.face(f).setAdjFace(0, lastFace);
327 if (lastFace != UINT_NULL)
328 convexHull.face(lastFace).setAdjFace(2, f);
329
330 convexHull.face(f).setAdjFace(1, faceIndex);
331 convexHull.face(faceIndex).setAdjFace(edgeIndex, f);
332
333 lastFace = f;
334 }
335
336 convexHull.face(firstFace).setAdjFace(0, lastFace);
337 convexHull.face(lastFace).setAdjFace(2, firstFace);
338
339 return newFaces;
340}
341
342template<Point3Concept PointType, FaceMeshConcept MeshType>
343void updateNewConflicts(
344 BipartiteGraph<PointType, uint>& conflictGraph,
345 const MeshType& convexHull,
346 const std::vector<uint>& newFaces,
347 const std::vector<std::set<PointType>>& potentialConflictPoints)
348{
349 using FaceType = MeshType::FaceType;
350
351 for (uint i = 0; i < newFaces.size(); ++i) {
352 conflictGraph.addRightNode(newFaces[i]);
353
354 const FaceType& face = convexHull.face(newFaces[i]);
355
356 for (const auto& p : potentialConflictPoints[i]) {
357 if (facePointVisibility(face, p)) {
358 conflictGraph.addArc(p, newFaces[i]);
359 }
360 }
361 }
362}
363
364} // namespace detail
365
378template<FaceMeshConcept MeshType, Range R, LoggerConcept LogType = NullLogger>
379MeshType convexHull(
380 const R& points,
381 std::optional<uint> seed = std::nullopt,
382 LogType& log = nullLogger)
384{
385 using PointType = std::ranges::range_value_t<R>;
386 using VertexType = MeshType::VertexType;
387 using FaceType = MeshType::FaceType;
388
389 static_assert(
391 "MeshType must have per-face adjacent faces.");
392
393 log.log(0, "Shuffling points...");
394
395 std::vector<PointType> pointsCopy(
396 std::ranges::begin(points), std::ranges::end(points));
397
398 detail::shufflePoints(pointsCopy, seed);
399
400 log.log(0, "Making first tetrahedron...");
401
402 auto result = detail::makeTetrahedron<MeshType>(
404
405 auto remainingPoints = pointsCopy | std::views::drop(4);
406
407 auto conflictGraph = detail::initConflictGraph(result, remainingPoints);
408
409 log.log(0, "Computing convex hull...");
410
411 log.startProgress("Processing Points...", pointsCopy.size() - 4);
412 uint iteration = 0;
413 // for each point in the conflict graph (still not in the convex hull)
414 for (const auto& point : conflictGraph.leftNodes()) {
415 // if the point is visible from a face in the convex hull
416 if (conflictGraph.adjacentLeftNodeNumber(point) != 0) {
417 // store the faces that are visible from (conflict with) the point
418 // these faces will be deleted from the convex hull
419 std::set<const FaceType*> visibleFaces;
420 for (const auto& faceIndex :
421 conflictGraph.adjacentLeftNodes(point)) {
422 visibleFaces.insert(&result.face(faceIndex));
423 }
424
425 // compute the horizon of the visible faces
426 std::set<const VertexType*> horizonVertices;
427 auto horizon = detail::horizonFaces(visibleFaces, horizonVertices);
428
429 // for each edge in the horizon, compute the points that could
430 // conflict with the new face that will be added to the convex hull
431 // in that edge
432 auto potentialConflictPoints = detail::potentialConflictPoints(
433 conflictGraph, point, result, horizon);
434
435 // delete the point and faces from the conflict graph - no more
436 // conflicts
437 conflictGraph.deleteLeftNode(point);
438 for (const FaceType* face : visibleFaces) {
439 conflictGraph.deleteRightNode(face->index());
440 }
441
442 // delete the visible faces from the convex hull, and the vertices
443 // that are not on the horizon
444 detail::deleteVisibleFaces(result, visibleFaces, horizonVertices);
445
446 // add the new faces to the convex hull
447 auto newFaces = detail::addNewFaces(result, point, horizon);
448
449 // update the conflict graph with the new faces
450 detail::updateNewConflicts(
451 conflictGraph, result, newFaces, potentialConflictPoints);
452 }
453 else {
454 conflictGraph.deleteLeftNode(point);
455 }
456 iteration++;
457 log.progress(iteration);
458 }
459
460 log.endProgress();
461 log.log(100, "Convex hull computed.");
462
463 return result;
464}
465
476template<FaceMeshConcept MeshType, Range R, LoggerConcept LogType = NullLogger>
477MeshType convexHull(const R& points, LogType& log)
479{
480 return convexHull<MeshType>(points, std::nullopt, log);
481}
482
483} // namespace vcl
484
485#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:871
Definition face_components.h:72
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:89
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:111
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:70
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