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