ViennaLS
Loading...
Searching...
No Matches
lsConvexHull.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <algorithm>
6#include <list>
7#include <unordered_map>
8
9#include <hrleVectorType.hpp>
10
11#include <lsGeometries.hpp>
12#include <lsMesh.hpp>
13
14#include <vcLogger.hpp>
15#include <vcSmartPointer.hpp>
16#include <vcVectorUtil.hpp>
17
18namespace viennals {
19
20using namespace viennacore;
21
25template <class T, int D> class ConvexHull {
26 typedef hrleVectorType<unsigned, D - 1> EdgeType;
27
28 SmartPointer<Mesh<T>> mesh = nullptr;
29 SmartPointer<PointCloud<T, D>> pointCloud = nullptr;
30 std::vector<EdgeType> visitedEdges;
31 std::vector<hrleVectorType<unsigned, D>> hullElements;
32 std::list<EdgeType> remainingEdges;
33
34 // go through all points in the set and find the correct element
35 // to do this find the normal vector and dot product to see which side
36 // a point is on
37 unsigned pivotEdge(EdgeType currentEdge) const {
38 auto &points = pointCloud->points;
39 unsigned nextIndex = 0;
40
41 // start from index which is neither one of the edges
42 while (nextIndex == currentEdge[0] ||
43 (D == 3 && nextIndex == currentEdge[1])) {
44 ++nextIndex;
45 }
46
47 for (unsigned i = 0; i < points.size(); ++i) {
48 if (i == currentEdge[0] || i == nextIndex)
49 continue;
50 if (D == 3 && i == currentEdge[1])
51 continue;
52
53 // surface element normal and distance to point i
54 hrleVectorType<T, D> normal;
55 hrleVectorType<T, D> distance = points[i] - points[currentEdge[0]];
56 // create surface element and check if all points are to its right
57 if constexpr (D == 2) {
58 // in 2D normal is a 90 degree rotation
59 normal = points[nextIndex] - points[currentEdge[0]];
60 if (D == 2) {
61 std::swap(normal[0], normal[1]);
62 normal[0] = -normal[0];
63 }
64 } else if constexpr (D == 3) {
65 auto v1 = points[currentEdge[1]] - points[currentEdge[0]];
66 auto v2 = points[nextIndex] - points[currentEdge[0]];
67 normal = calculateNormal(v1, v2);
68 }
69
70 auto product = DotProduct(distance, normal);
71 // if dot product is very small, point is very close to plane
72 // we need to check if we already have the correct point
73 // or if it is the next correct one
74 if (std::abs(product) < 1e-9) {
75 // check if suggested triangle intersects with any other
76 // in the same plane
77 hrleVectorType<unsigned, D> triangle;
78 triangle[0] = currentEdge[0];
79 triangle[1] = currentEdge[1];
80 triangle[2] = i;
81 if (doesTriangleClip(triangle)) {
82 continue;
83 }
84
85 EdgeType edges[2];
86 edges[0][0] = currentEdge[0];
87 edges[0][1] = i;
88 edges[1][0] = currentEdge[1];
89 edges[1][1] = i;
90
91 if (!wasEdgeVisited(edges[0]) && !wasEdgeVisited(edges[1])) {
92 nextIndex = i;
93 }
94 }
95 // check if point is to the right of current element
96 else if (product > 0) {
97 nextIndex = i;
98 }
99 }
100 return nextIndex;
101 }
102
103 // check if edge was already visited
104 bool wasEdgeVisited(const EdgeType &edge) const {
105 for (unsigned i = 0; i < visitedEdges.size(); ++i) {
106 // in 2D one match is enough, in 3D both points must be the same
107 if (edge[0] == visitedEdges[i][0]) {
108 if constexpr (D == 3) {
109 if (edge[1] == visitedEdges[i][1]) {
110 return true;
111 }
112 } else {
113 return true;
114 }
115 }
116
117 // in 3D, an edge is also the same if they contain the same nodes
118 if constexpr (D == 3) {
119 if (edge[0] == visitedEdges[i][1] && edge[1] == visitedEdges[i][0]) {
120 return true;
121 }
122 }
123 }
124 return false;
125 }
126
127 // find whether an edge has already been used for a triangle once
128 typename std::list<EdgeType>::iterator findEdge(EdgeType edge) {
129 for (typename std::list<EdgeType>::iterator it = remainingEdges.begin();
130 it != remainingEdges.end(); ++it) {
131 bool containsElem[2] = {false, false};
132 for (unsigned i = 0; i < D - 1; ++i) {
133 // check all possible permutations of elements
134 for (unsigned k = 0; k < D - 1; ++k) {
135 if ((*it)[i] == edge[k]) {
136 containsElem[i] = true;
137 }
138 }
139 }
140 if (containsElem[0] && containsElem[1]) {
141 return it;
142 }
143 }
144 return remainingEdges.end();
145 }
146
147 // return whether two triangles with shared nodes intersect
148 bool intersectSharedNode(const hrleVectorType<unsigned, D> &triangle1,
149 const hrleVectorType<unsigned, D> &triangle2) const {
150 // find which nodes are shared and which nodes are others
151 std::vector<unsigned> sharedNodes;
152 // otherNodes2 contains the nodes of triangle1 which are not shared
153 std::vector<unsigned> otherNodes;
154
155 for (unsigned i = 0; i < D; ++i) {
156 unsigned shared = sharedNodes.size();
157 for (unsigned j = 0; j < D; ++j) {
158 if (triangle1[i] == triangle2[j]) {
159 // node numbers of t2
160 sharedNodes.push_back(j);
161 }
162 }
163 // if triangle1[i] was not shared, push to other nodes
164 if (shared == sharedNodes.size()) {
165 otherNodes.push_back(i);
166 }
167 }
168
169 assert(sharedNodes.size() > 0);
170
171 auto &points = pointCloud->points;
172
173 // for each shared node, calculate the edges of t2 leading away from it.
174 // then generate the average vector and its angle to the edges.
175 // then calculate the dot product of each other node of t1 to the average
176 // vector. If it is smaller than the dot product between the average vector
177 // and an edge, the node must be outside of triangle, It it is inside, one
178 // triangle must clip the other.
179 for (unsigned i = 0; i < sharedNodes.size(); ++i) {
180 hrleVectorType<T, D> averageVector;
181 // save the dot product between average and one edge
182 double centerEdgeDot;
183 {
184 auto shared = sharedNodes[i];
185 hrleVectorType<T, D> edge1 = Normalize(
186 points[triangle2[(shared + 1) % D]] - points[triangle2[shared]]);
187 hrleVectorType<T, D> edge2 = Normalize(
188 points[triangle2[(shared + 2) % D]] - points[triangle2[shared]]);
189 averageVector = Normalize((edge1 + edge2) / 2);
190 centerEdgeDot = DotProduct(averageVector, edge2);
191 }
192
193 // go over other nodes of triangle1
194 for (unsigned j = 0; j < otherNodes.size(); ++j) {
195 hrleVectorType<T, D> vector =
196 Normalize(points[triangle1[otherNodes[j]]] -
197 points[triangle2[sharedNodes[i]]]);
198
199 if (DotProduct(vector, averageVector) > centerEdgeDot + 1e-9) {
200 return true;
201 }
202 }
203 }
204 // if no other node was ever within two edges, the triangles do not clip
205 return false;
206 }
207
208 // check if triangle defined by two edges clips any other triangle
209 bool doesTriangleClip(const hrleVectorType<unsigned, D> &triangle) const {
210 auto &points = pointCloud->points;
211
212 auto triangleNormal =
213 Normalize(calculateNormal(points[triangle[1]] - points[triangle[0]],
214 points[triangle[2]] - points[triangle[0]]));
215
216 // unsigned shareEdges = 0;
217 for (unsigned i = 0; i < hullElements.size(); ++i) {
218 unsigned inOneTriangle = 0;
219 for (unsigned j = 0; j < D; ++j) {
220 // check all possible permutations of elements
221 for (unsigned k = 0; k < D; ++k) {
222 if (hullElements[i][j] == triangle[k]) {
223 ++inOneTriangle;
224 }
225 }
226 }
227
228 // if they share at least one node, they might clip, so check
229 if (inOneTriangle > 0) {
230 // check if they are in the same plane
231 auto normal2 = Normalize(calculateNormal(
232 points[hullElements[i][1]] - points[hullElements[i][0]],
233 points[hullElements[i][2]] - points[hullElements[i][0]]));
234
235 bool skip = false;
236 for (unsigned d = 0; d < D; ++d) {
237 double diff =
238 std::abs(std::abs(triangleNormal[d]) - std::abs(normal2[d]));
239 if (diff > 1e-6)
240 skip = true;
241 }
242
243 if (skip)
244 continue;
245
246 if (intersectSharedNode(triangle, hullElements[i]))
247 return true;
248 if (intersectSharedNode(hullElements[i], triangle))
249 return true;
250 }
251 }
252 return false;
253 }
254
255 // calculate the normal vector of two vectors
256 hrleVectorType<T, D> calculateNormal(const hrleVectorType<T, D> &v1,
257 const hrleVectorType<T, D> &v2) const {
258 hrleVectorType<T, D> newNormal;
259 newNormal[0] = v1[1] * v2[2] - v1[2] * v2[1];
260 newNormal[1] = v1[2] * v2[0] - v1[0] * v2[2];
261 newNormal[2] = v1[0] * v2[1] - v1[1] * v2[0];
262 return newNormal;
263 }
264
265 // calculate Area of triangle defined by 2 vectors
266 T calculateArea(const hrleVectorType<T, D> &v1,
267 const hrleVectorType<T, D> &v2) const {
268 hrleVectorType<T, D> newNormal = calculateNormal(v1, v2);
269 return std::sqrt(DotProduct(newNormal, newNormal));
270 }
271
272public:
274
275 ConvexHull(SmartPointer<Mesh<T>> passedMesh,
276 SmartPointer<PointCloud<T, D>> passedPointCloud)
277 : mesh(passedMesh), pointCloud(passedPointCloud) {}
278
279 void setMesh(SmartPointer<Mesh<T>> passedMesh) { mesh = passedMesh; }
280
281 void setPointCloud(SmartPointer<PointCloud<T, D>> passedPointCloud) {
282 pointCloud = passedPointCloud;
283 }
284
285 void apply() {
286 if (mesh == nullptr) {
287 Logger::getInstance()
288 .addWarning("No mesh was passed to ConvexHull.")
289 .print();
290 return;
291 }
292 if (pointCloud == nullptr) {
293 Logger::getInstance()
294 .addWarning("No point cloud was passed to ConvexHull.")
295 .print();
296 return;
297 }
298
299 mesh->clear();
300 auto &points = pointCloud->points;
301 if (points.size() == 0)
302 return;
303
304 {
305 // find first hull point
306 auto it = std::min_element(points.begin(), points.end());
307 unsigned currentIndex = std::distance(points.begin(), it);
308
309 if (D == 2) {
310 remainingEdges.push_back(EdgeType(currentIndex));
311 } else {
312 // need to check if there is second point at same z coord
313 int currentPointIndex = -1;
314 double hullMetric = std::numeric_limits<double>::max();
315 for (unsigned i = 0; i < points.size(); ++i) {
316 if (i == currentIndex)
317 continue;
318 if (std::abs(points[i][2] - points[currentIndex][2]) < 1e-7) {
319 auto diff = points[currentIndex] - points[i];
320 // choose closest point if points are in z plane
321 double currentHullMetric = DotProduct(diff, diff);
322 if (currentHullMetric < hullMetric) {
323 hullMetric = currentHullMetric;
324 currentPointIndex = i;
325 }
326 }
327 }
328
329 EdgeType edge;
330 edge[0] = currentIndex;
331 // if there was no point at same z, find through pivot
332 if (currentPointIndex == -1) {
333 hrleVectorType<T, D> newPoint;
334 newPoint = points[currentIndex];
335 newPoint[0] += 1.0;
336 // take newPoint for fake edge to find correct first point
337 points.push_back(newPoint);
338 edge[1] = points.size() - 1;
339 edge[1] = pivotEdge(edge);
340 points.pop_back();
341 } else {
342 edge[1] = currentPointIndex;
343 }
344
345 remainingEdges.push_back(edge);
346 }
347 }
348
349 // until there are no more edges to check
350 while (remainingEdges.size() != 0) {
351 // for all points in the cloud
352 EdgeType currentEdge = remainingEdges.back();
353 remainingEdges.pop_back();
354
355 if (wasEdgeVisited(currentEdge))
356 continue;
357
358 // find next point in hull
359 unsigned nextIndex = pivotEdge(currentEdge);
360
361 // set new hull element and save
362 hrleVectorType<unsigned, D> element;
363 for (unsigned i = 0; i < D - 1; ++i) {
364 element[i] = currentEdge[i];
365 }
366 element[D - 1] = nextIndex;
367 hullElements.push_back(element);
368
369 // mark edge as visited
370 visitedEdges.push_back(currentEdge);
371 if (D == 2) {
372 remainingEdges.push_back(EdgeType(nextIndex));
373 } else {
374 EdgeType edge;
375 edge[0] = nextIndex;
376 edge[1] = currentEdge[1];
377 // if edge is already part of another element
378 // and we just added it, we need to remove it
379 // from the list
380 auto it = findEdge(edge);
381 if (it != remainingEdges.end()) {
382 remainingEdges.erase(it);
383 } else {
384 remainingEdges.push_back(edge);
385 }
386
387 edge[0] = currentEdge[0];
388 edge[1] = nextIndex;
389 it = findEdge(edge);
390 if (it != remainingEdges.end()) {
391 remainingEdges.erase(it);
392 } else {
393 remainingEdges.push_back(edge);
394 }
395 }
396 }
397
398 // now make the mesh
399 auto &elements = mesh->template getElements<D>();
400 std::unordered_map<unsigned, unsigned> oldToNewNodes;
401 for (unsigned i = 0; i < hullElements.size(); ++i) {
402 // here add translation of old index to new index for new points
403 std::array<unsigned, D> newElement;
404 for (unsigned j = 0; j < D; ++j) {
405 auto insertion = oldToNewNodes.insert(
406 std::make_pair(hullElements[i][j], oldToNewNodes.size()));
407
408 // if insertion took place, add the point to the nodes
409 if (insertion.second) {
410 Vec3D<T> node{points[hullElements[i][j]][0],
411 points[hullElements[i][j]][1],
412 (D == 2) ? T(0.) : points[hullElements[i][j]][2]};
413 mesh->nodes.push_back(node);
414 }
415
416 newElement[j] = insertion.first->second;
417 }
418
419 elements.push_back(newElement);
420 }
421 }
422};
423
424// add all template specialisations for this class
426
427} // namespace viennals
This algorithm creates a convex hull mesh from a point cloud. This is done using the gift wrapping ap...
Definition lsConvexHull.hpp:25
ConvexHull()
Definition lsConvexHull.hpp:273
void apply()
Definition lsConvexHull.hpp:285
ConvexHull(SmartPointer< Mesh< T > > passedMesh, SmartPointer< PointCloud< T, D > > passedPointCloud)
Definition lsConvexHull.hpp:275
void setMesh(SmartPointer< Mesh< T > > passedMesh)
Definition lsConvexHull.hpp:279
void setPointCloud(SmartPointer< PointCloud< T, D > > passedPointCloud)
Definition lsConvexHull.hpp:281
This class holds an explicit mesh, which is always given in 3 dimensions. If it describes a 2D mesh,...
Definition lsMesh.hpp:21
Class describing a point cloud, which can be used to create geometries from its convex hull mesh.
Definition lsGeometries.hpp:121
#define PRECOMPILE_PRECISION_DIMENSION(className)
Definition lsPreCompileMacros.hpp:24
Definition lsAdvect.hpp:46
constexpr int D
Definition pyWrap.cpp:65
double T
Definition pyWrap.cpp:63