version 3.11-dev
Loading...
Searching...
No Matches
triangulation.hh
Go to the documentation of this file.
1// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2// vi: set et ts=4 sw=4 sts=4:
3//
4// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
5// SPDX-License-Identifier: GPL-3.0-or-later
6//
14#ifndef DUMUX_GEOMETRY_TRIANGULATION_HH
15#define DUMUX_GEOMETRY_TRIANGULATION_HH
16
17#include <vector>
18#include <array>
19#include <algorithm>
20#include <type_traits>
21#include <tuple>
22
23#include <dune/common/exceptions.hh>
24#include <dune/common/fvector.hh>
25
26#include <dumux/common/math.hh>
29
30namespace Dumux {
32
36
40
43
44#ifndef DOXYGEN
45namespace Detail {
46using DefaultDimPolicies = std::tuple<DelaunayPolicy, MidPointPolicy, ConvexHullPolicy>;
47} // end namespace Detail
48#endif
49
51template<int dim, int dimWorld>
52using DefaultPolicy = std::tuple_element_t<dim-1, Detail::DefaultDimPolicies>;
53
54} // end namespace TriangulationPolicy
55
63template<int dim, int dimWorld, class ctype>
64using Triangulation = std::vector< std::array<Dune::FieldVector<ctype, dimWorld>, dim+1> >;
65
76template< int dim, int dimWorld, class Policy = TriangulationPolicy::DefaultPolicy<dim, dimWorld>,
77 class RandomAccessContainer,
78 std::enable_if_t< std::is_same_v<Policy, TriangulationPolicy::DelaunayPolicy>
79 && dim == 1, int> = 0 >
81triangulate(const RandomAccessContainer& points)
82{
83 using ctype = typename RandomAccessContainer::value_type::value_type;
84 using Point = Dune::FieldVector<ctype, dimWorld>;
85
86 static_assert(std::is_same_v<typename RandomAccessContainer::value_type, Point>,
87 "Triangulation expects Dune::FieldVector as point type");
88
89 if (points.size() == 2)
90 return Triangulation<dim, dimWorld, ctype>({ {points[0], points[1]} });
91
93 assert(points.size() > 1);
94 DUNE_THROW(Dune::NotImplemented, "1d triangulation for point cloud size > 2");
95}
96
109template< int dim, int dimWorld, class Policy = TriangulationPolicy::DefaultPolicy<dim, dimWorld>,
110 class RandomAccessContainer,
111 std::enable_if_t< std::is_same_v<Policy, TriangulationPolicy::MidPointPolicy>
112 && dim == 2, int> = 0 >
114triangulate(const RandomAccessContainer& convexHullPoints)
115{
116 using ctype = typename RandomAccessContainer::value_type::value_type;
117 using Point = Dune::FieldVector<ctype, dimWorld>;
118 using Triangle = std::array<Point, 3>;
119
120 static_assert(std::is_same_v<typename RandomAccessContainer::value_type, Point>,
121 "Triangulation expects Dune::FieldVector as point type");
122
123 if (convexHullPoints.size() < 3)
124 DUNE_THROW(Dune::InvalidStateException, "Try to triangulate point cloud with less than 3 points!");
125
126 if (convexHullPoints.size() == 3)
127 return std::vector<Triangle>(1, {convexHullPoints[0], convexHullPoints[1], convexHullPoints[2]});
128
129 Point midPoint(0.0);
130 for (const auto& p : convexHullPoints)
131 midPoint += p;
132 midPoint /= convexHullPoints.size();
133
134 std::vector<Triangle> triangulation;
135 triangulation.reserve(convexHullPoints.size());
136
137 for (std::size_t i = 0; i < convexHullPoints.size()-1; ++i)
138 triangulation.emplace_back(Triangle{midPoint, convexHullPoints[i], convexHullPoints[i+1]});
139
140 triangulation.emplace_back(Triangle{midPoint, convexHullPoints[convexHullPoints.size()-1], convexHullPoints[0]});
141
142 return triangulation;
143}
144
157template< int dim, int dimWorld, class Policy = TriangulationPolicy::DefaultPolicy<dim, dimWorld>,
158 class RandomAccessContainer,
159 std::enable_if_t< std::is_same_v<Policy, TriangulationPolicy::ConvexHullPolicy>
160 && dim == 2, int> = 0 >
162triangulate(const RandomAccessContainer& points)
163{
164 const auto convexHullPoints = grahamConvexHull<2>(points);
166}
167
178template< int dim, int dimWorld, class Policy = TriangulationPolicy::DefaultPolicy<dim, dimWorld>,
179 class RandomAccessContainer,
180 std::enable_if_t< std::is_same_v<Policy, TriangulationPolicy::ConvexHullPolicy>
181 && dim == 3, int> = 0 >
183triangulate(const RandomAccessContainer& points)
184{
185 using ctype = typename RandomAccessContainer::value_type::value_type;
186 using Point = Dune::FieldVector<ctype, dimWorld>;
187 using Tetrahedron = std::array<Point, 4>;
188
189 static_assert(std::is_same_v<typename RandomAccessContainer::value_type, Point>,
190 "Triangulation expects Dune::FieldVector as point type");
191
192 const auto numPoints = points.size();
193 if (numPoints < 4)
194 DUNE_THROW(Dune::InvalidStateException, "Trying to create 3D triangulation of point cloud with less than 4 points!");
195
196 if (numPoints == 4)
197 return std::vector<Tetrahedron>(1, {points[0], points[1], points[2], points[3]});
198
199 // compute the mid point of the point cloud (not the midpoint of the convex hull but this
200 // should not matter too much for the applications we have in mind here)
201 Point midPoint(0.0);
202 Point lowerLeft(1e100);
203 Point upperRight(-1e100);
204 for (const auto& p : points)
205 {
206 midPoint += p;
207 for (int i = 0; i < dimWorld; ++i)
208 {
209 using std::max; using std::min;
210 lowerLeft[i] = min(p[i], lowerLeft[i]);
211 upperRight[i] = max(p[i], upperRight[i]);
212 }
213 }
214 midPoint /= numPoints;
215
216 auto magnitude = 0.0;
217 using std::max;
218 for (int i = 0; i < dimWorld; ++i)
219 magnitude = max(upperRight[i] - lowerLeft[i], magnitude);
220 const auto eps = 1e-7*magnitude;
221 const auto eps2 = eps*eps;
222 const auto epsDist = 1e-8*magnitude;
223
224 // reserve memory conservatively to avoid reallocation
225 std::vector<Tetrahedron> triangulation;
226 triangulation.reserve(numPoints);
227
228 // make a buffer for storing coplanar points and indices
229 std::vector<Point> coplanarPointBuffer;
230 coplanarPointBuffer.reserve(std::min<std::size_t>(12, numPoints-1));
231
232 // remember coplanar cluster planes
233 // coplanar clusters are uniquely identified by their plane (outward unit normal and offset)
234 // we only want to add each cluster once (when handling the first triangle in the cluster)
235 std::vector<std::pair<Point, ctype>> coplanarClusters;
236 coplanarClusters.reserve(numPoints/3);
237
238 // brute force algorithm: Try all possible triangles and check
239 // if they are triangles of the convex hull. This is achieved by checking if all
240 // other points are in the same half-space (side)
241 // the algorithm is O(n^4), so only do this for very small point clouds
242 for (int i = 0; i < numPoints; ++i)
243 {
244 for (int j = i+1; j < numPoints; ++j)
245 {
246 for (int k = j+1; k < numPoints; ++k)
247 {
248 const auto pointI = points[i];
249 const auto ab = points[j] - pointI;
250 const auto ac = points[k] - pointI;
251 const auto normal = crossProduct(ab, ac);
252
253 // clear list of coplanar points w.r.t to triangle ijk
254 coplanarPointBuffer.clear();
255
256 // check if this triangle ijk is admissible which means
257 // it is on the convex hull (all other points in the cloud are in the same half-space/side)
258 const bool isAdmissible = [&]()
259 {
260 // check if points are colinear and we can't form a triangle
261 // if so, skip this triangle
262 if (normal.two_norm2() < eps2*eps2)
263 return false;
264
265 int marker = 0; // 0 means undecided side (or coplanar)
266 for (int m = 0; m < numPoints; ++m)
267 {
268 if (m != i && m != j && m != k)
269 {
270 // check scalar product with surface normal to decide side
271 const auto ad = points[m] - pointI;
272 const auto sp = normal*ad;
273
274 // if the sign changes wrt the previous sign, the triangle is not part of the convex hull
275 // sp is a triple product, so divide by the normal to obtain a distance
276 using std::abs; using std::signbit;
277 const bool coplanar = abs(sp) < epsDist*normal.two_norm();
278 int newMarker = coplanar ? 0 : signbit(sp) ? -1 : 1;
279
280 // make decision for a side as soon as the next marker is != 0
281 // keep track of the previous marker
282 if (marker == 0 && newMarker != 0)
283 marker = newMarker;
284
285 // if marker flips, not all points are on one side
286 // zero marker (undecided side / coplanar point) shouldn't abort the process
287 if (newMarker != 0 && marker != newMarker)
288 return false;
289
290 // handle possible coplanar points
291 if (coplanar)
292 coplanarPointBuffer.push_back(points[m]);
293 }
294 }
295
296 // if there are coplanar points complete the cluster with i, j, and k
297 // and store the cluster information for future lookup
298 if (!coplanarPointBuffer.empty())
299 {
300 auto unitNormal = normal;
301 unitNormal /= unitNormal.two_norm();
302 if (unitNormal*(pointI - midPoint) < 0.0)
303 unitNormal *= -1.0;
304 const auto offset = unitNormal*pointI;
305
306 using std::abs;
307 if (std::find_if(
308 coplanarClusters.begin(), coplanarClusters.end(),
309 [&](const auto& c){ return unitNormal*c.first > 1.0 - 1e-6 && abs(offset - c.second) < epsDist; }
310 ) != coplanarClusters.end())
311 {
312 // this cluster has already been handled
313 coplanarPointBuffer.clear();
314 return false;
315 }
316
317 coplanarPointBuffer.insert(coplanarPointBuffer.end(), { points[i], points[j], points[k] });
318 coplanarClusters.emplace_back(std::make_pair(unitNormal, offset));
319 }
320
321 // we require that not all points are coplanar, so
322 // there will be always at least one non-coplanar point
323 // to check on which side the other points are.
324 // Hence, once we get here, the triangle or coplanar point cluster is part of the convex hull
325 return true;
326 }();
327
328 if (isAdmissible)
329 {
330 // check if we have a cluster of coplanar points forming on of the
331 // faces of the convex hull, if yes, compute (2d) convex hull first and triangulate
332 if (!coplanarPointBuffer.empty())
333 {
334 const auto triangles = triangulate<2, 3, TriangulationPolicy::ConvexHullPolicy>(coplanarPointBuffer);
335 for (const auto& triangle : triangles)
336 {
337 const auto ab = triangle[1] - triangle[0];
338 const auto ac = triangle[2] - triangle[0];
339 const auto normal = crossProduct(ab, ac);
340 const auto am = midPoint - triangle[0];
341 const auto sp = normal*am;
342 using std::signbit;
343 const bool isBelow = signbit(sp);
344 if (isBelow)
345 triangulation.emplace_back(Tetrahedron{
346 triangle[0], triangle[2], triangle[1], midPoint
347 });
348 else
349 triangulation.emplace_back(Tetrahedron{
350 triangle[0], triangle[1], triangle[2], midPoint
351 });
352 }
353 }
354 else
355 {
356 const auto am = midPoint - pointI;
357 const auto sp = normal*am;
358 using std::signbit;
359 const bool isBelow = signbit(sp);
360 if (isBelow)
361 triangulation.emplace_back(Tetrahedron{
362 pointI, points[k], points[j], midPoint
363 });
364 else
365 triangulation.emplace_back(Tetrahedron{
366 pointI, points[j], points[k], midPoint
367 });
368 }
369 }
370 }
371 }
372 }
373
374 // sanity check: if points are not coplanar, then using the mid point policy, we get at least 4 tetrahedrons
375 if (triangulation.size() < 4)
376 DUNE_THROW(Dune::InvalidStateException, "Something went wrong with the triangulation!");
377
378 return triangulation;
379}
380
381} // end namespace Dumux
382
383#endif
A function to compute the convex hull of a point cloud.
Dune::FieldVector< Scalar, 3 > crossProduct(const Dune::FieldVector< Scalar, 3 > &vec1, const Dune::FieldVector< Scalar, 3 > &vec2)
Cross product of two vectors in three-dimensional Euclidean space.
Definition math.hh:671
Triangulation< dim, dimWorld, typename RandomAccessContainer::value_type::value_type > triangulate(const RandomAccessContainer &points)
Triangulate area given points of a convex hull (1d).
Definition triangulation.hh:81
Vector normal(const Vector &v)
Create a vector normal to the given one (v is expected to be non-zero).
Definition normal.hh:26
std::vector< Dune::FieldVector< ctype, dimWorld > > grahamConvexHull(std::vector< Dune::FieldVector< ctype, dimWorld > > &points)
Compute the points making up the convex hull around the given set of unordered points.
Definition grahamconvexhull.hh:186
Vector unitNormal(const Vector &v)
Create a vector normal to the given one (v is expected to be non-zero).
Definition normal.hh:58
std::vector< std::array< Dune::FieldVector< ctype, dimWorld >, dim+1 > > Triangulation
The default data type to store triangulations.
Definition triangulation.hh:64
Detect if a point intersects a simplex (including boundary).
Define some often used mathematical functions.
Definition cvfelocalresidual.hh:25
Definition triangulation.hh:31
std::tuple_element_t< dim-1, Detail::DefaultDimPolicies > DefaultPolicy
Default policy for a given dimension.
Definition triangulation.hh:52
Definition adapt.hh:17
Definition triangulation.hh:39
Delaunay-type triangulations.
Definition triangulation.hh:42
Definition triangulation.hh:35