EBGeometry
Compact, header-only C++ library for fast evaluation of signed distance functions
Loading...
Searching...
No Matches
EBGeometry_TriangleDistance.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Robert Marskar <robert.marskar@sintef.no>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
15#ifndef EBGEOMETRY_TRIANGLEDISTANCE_HPP
16#define EBGEOMETRY_TRIANGLEDISTANCE_HPP
17
18// Std includes
19#include <type_traits>
20
21// Our includes
22#include "EBGeometry_GPU.hpp"
23#include "EBGeometry_SIMD.hpp"
24
25namespace EBGeometry {
26
30namespace TriangleDistance {
31
37template <class V>
38struct Vec3V
39{
43 V x;
44
48 V y;
49
53 V z;
54};
55
57template <class V>
59inline Vec3V<V>
60operator-(const Vec3V<V>& a_u, const Vec3V<V>& a_v) noexcept
61{
62 return Vec3V<V>{a_u.x - a_v.x, a_u.y - a_v.y, a_u.z - a_v.z};
63}
64
65template <class V>
67inline V
68dot(const Vec3V<V>& a_u, const Vec3V<V>& a_v) noexcept
69{
70 return a_u.x * a_v.x + a_u.y * a_v.y + a_u.z * a_v.z;
71}
72
73template <class V>
75inline Vec3V<V>
76cross(const Vec3V<V>& a_u, const Vec3V<V>& a_v) noexcept
77{
78 return Vec3V<V>{a_u.y * a_v.z - a_u.z * a_v.y, a_u.z * a_v.x - a_u.x * a_v.z, a_u.x * a_v.y - a_u.y * a_v.x};
79}
80
81template <class V>
83inline Vec3V<V>
84scaled(const Vec3V<V>& a_u, const V a_s) noexcept
85{
86 return Vec3V<V>{a_s * a_u.x, a_s * a_u.y, a_s * a_u.z};
87}
88
89template <class V>
91inline V
92constant(const int a_value) noexcept
93{
94 using T = typename SIMD::Scalar<V>::type;
95
96 return V(T(a_value));
97}
98
99// +1 where a_x > 0, else -1 (zero and NaN included).
100template <class V>
102inline V
103sign(const V a_x) noexcept
104{
105 using SIMD::select;
106
107 return select(a_x > constant<V>(0), constant<V>(1), constant<V>(-1));
108}
109
110// Whether a projection parameter lies strictly inside its edge, 0 < t < 1.
111template <class V>
113inline auto
114onEdge(const V a_t) noexcept
115{
116 using SIMD::both;
117
118 return both(a_t > constant<V>(0), a_t < constant<V>(1));
119}
121
148template <class V, class Source>
149[[nodiscard]] EBGEOMETRY_HOST_DEVICE
151inline V
152signedDistance(const Source& a_triangle, const Vec3V<V>& a_point) noexcept
153{
154 using SIMD::abs;
155 using SIMD::both;
156 using SIMD::select;
157 using SIMD::sqrt;
158
159 const Vec3V<V> v0 = a_triangle.vertex(0);
160 const Vec3V<V> v1 = a_triangle.vertex(1);
161 const Vec3V<V> v2 = a_triangle.vertex(2);
162 const Vec3V<V> n = a_triangle.normal();
163
164 const Vec3V<V> v21 = v1 - v0;
165 const Vec3V<V> v32 = v2 - v1;
166 const Vec3V<V> v13 = v0 - v2;
167
168 const Vec3V<V> p1 = a_point - v0;
169 const Vec3V<V> p2 = a_point - v1;
170 const Vec3V<V> p3 = a_point - v2;
171
172 // With outward normals the three signs are all -1 for a point projecting inside (sum -3), with
173 // inward normals all +1 (sum +3).
174 const V s0 = sign(dot(cross(v21, n), p1));
175 const V s1 = sign(dot(cross(v32, n), p2));
176 const V s2 = sign(dot(cross(v13, n), p3));
177
178 const auto inside = abs(s0 + s1 + s2) >= constant<V>(2);
179 const V faceD = dot(n, p1);
180
181 if constexpr (std::is_floating_point_v<V>) {
182 // One lane can branch where a SIMD lane cannot. A point projecting inside the face needs no
183 // vertex or edge, and only the closest candidate needs its sign: on a device this skips most of
184 // the double-precision work, which bounds the kernel there.
185 if (inside) {
186 return faceD;
187 }
188
189 // The closest candidate so far: its squared distance, the vector from it to the point, and
190 // which pseudonormal signs it (0-2 vertices, 3-5 edges). An edge's closest point is computed only
191 // where the point projects onto the edge (0 < t < 1).
192 V bestD2 = dot(p1, p1);
193 Vec3V<V> bestVec = p1;
194 int best = 0;
195
196 const V p2d2 = dot(p2, p2);
197
198 if (p2d2 < bestD2) {
199 bestD2 = p2d2;
200 bestVec = p2;
201 best = 1;
202 }
203
204 const V p3d2 = dot(p3, p3);
205
206 if (p3d2 < bestD2) {
207 bestD2 = p3d2;
208 bestVec = p3;
209 best = 2;
210 }
211
212 const V t1 = dot(p1, v21) / dot(v21, v21);
213
214 if (onEdge(t1)) {
215 const Vec3V<V> y1 = p1 - scaled(v21, t1);
216 const V y1d2 = dot(y1, y1);
217
218 if (y1d2 < bestD2) {
219 bestD2 = y1d2;
220 bestVec = y1;
221 best = 3;
222 }
223 }
224
225 const V t2 = dot(p2, v32) / dot(v32, v32);
226
227 if (onEdge(t2)) {
228 const Vec3V<V> y2 = p2 - scaled(v32, t2);
229 const V y2d2 = dot(y2, y2);
230
231 if (y2d2 < bestD2) {
232 bestD2 = y2d2;
233 bestVec = y2;
234 best = 4;
235 }
236 }
237
238 const V t3 = dot(p3, v13) / dot(v13, v13);
239
240 if (onEdge(t3)) {
241 const Vec3V<V> y3 = p3 - scaled(v13, t3);
242 const V y3d2 = dot(y3, y3);
243
244 if (y3d2 < bestD2) {
245 bestD2 = y3d2;
246 bestVec = y3;
247 best = 5;
248 }
249 }
250
251 const auto k = static_cast<size_t>(best);
252 const Vec3V<V> pseudonormal = k < 3 ? a_triangle.vertexNormal(k) : a_triangle.edgeNormal(k - 3);
253
254 return sign(dot(pseudonormal, bestVec)) * sqrt(bestD2);
255 }
256
257 const V t1 = dot(p1, v21) / dot(v21, v21);
258 const V t2 = dot(p2, v32) / dot(v32, v32);
259 const V t3 = dot(p3, v13) / dot(v13, v13);
260
261 const Vec3V<V> y1 = p1 - scaled(v21, t1);
262 const Vec3V<V> y2 = p2 - scaled(v32, t2);
263 const Vec3V<V> y3 = p3 - scaled(v13, t3);
264
265 // Vertices, then edges; a candidate replaces the best only when strictly closer, and an edge counts
266 // only where the point projects inside it (0 < t < 1).
267 V bestD2 = dot(p1, p1);
268 V bestSign = sign(dot(a_triangle.vertexNormal(0), p1));
269
270 const V p2d2 = dot(p2, p2);
271 const auto c2 = p2d2 < bestD2;
272 bestSign = select(c2, sign(dot(a_triangle.vertexNormal(1), p2)), bestSign);
273 bestD2 = select(c2, p2d2, bestD2);
274
275 const V p3d2 = dot(p3, p3);
276 const auto c3 = p3d2 < bestD2;
277 bestSign = select(c3, sign(dot(a_triangle.vertexNormal(2), p3)), bestSign);
278 bestD2 = select(c3, p3d2, bestD2);
279
280 const V y1d2 = dot(y1, y1);
281 const auto e1 = both(onEdge(t1), y1d2 < bestD2);
282 bestSign = select(e1, sign(dot(a_triangle.edgeNormal(0), y1)), bestSign);
283 bestD2 = select(e1, y1d2, bestD2);
284
285 const V y2d2 = dot(y2, y2);
286 const auto e2 = both(onEdge(t2), y2d2 < bestD2);
287 bestSign = select(e2, sign(dot(a_triangle.edgeNormal(1), y2)), bestSign);
288 bestD2 = select(e2, y2d2, bestD2);
289
290 const V y3d2 = dot(y3, y3);
291 const auto e3 = both(onEdge(t3), y3d2 < bestD2);
292 bestSign = select(e3, sign(dot(a_triangle.edgeNormal(2), y3)), bestSign);
293 bestD2 = select(e3, y3d2, bestD2);
294
295 return select(inside, faceD, bestSign * sqrt(bestD2));
296}
297
312template <class V, class Source>
313[[nodiscard]] EBGEOMETRY_HOST_DEVICE
315inline V
316unsignedDistance2(const Source& a_triangle, const Vec3V<V>& a_point) noexcept
317{
318 using SIMD::abs;
319 using SIMD::both;
320 using SIMD::select;
321
322 const Vec3V<V> v0 = a_triangle.vertex(0);
323 const Vec3V<V> v1 = a_triangle.vertex(1);
324 const Vec3V<V> v2 = a_triangle.vertex(2);
325 const Vec3V<V> n = a_triangle.normal();
326
327 const Vec3V<V> v21 = v1 - v0;
328 const Vec3V<V> v32 = v2 - v1;
329 const Vec3V<V> v13 = v0 - v2;
330
331 const Vec3V<V> p1 = a_point - v0;
332 const Vec3V<V> p2 = a_point - v1;
333 const Vec3V<V> p3 = a_point - v2;
334
335 const V s0 = sign(dot(cross(v21, n), p1));
336 const V s1 = sign(dot(cross(v32, n), p2));
337 const V s2 = sign(dot(cross(v13, n), p3));
338
339 const auto inside = abs(s0 + s1 + s2) >= constant<V>(2);
340 const V faceD = dot(n, p1);
341
342 if constexpr (std::is_floating_point_v<V>) {
343 if (inside) {
344 return faceD * faceD;
345 }
346
347 V bestD2 = dot(p1, p1);
348
349 const V p2d2 = dot(p2, p2);
350 const V p3d2 = dot(p3, p3);
351
352 bestD2 = p2d2 < bestD2 ? p2d2 : bestD2;
353 bestD2 = p3d2 < bestD2 ? p3d2 : bestD2;
354
355 const V t1 = dot(p1, v21) / dot(v21, v21);
356
357 if (onEdge(t1)) {
358 const Vec3V<V> y1 = p1 - scaled(v21, t1);
359 const V y1d2 = dot(y1, y1);
360
361 bestD2 = y1d2 < bestD2 ? y1d2 : bestD2;
362 }
363
364 const V t2 = dot(p2, v32) / dot(v32, v32);
365
366 if (onEdge(t2)) {
367 const Vec3V<V> y2 = p2 - scaled(v32, t2);
368 const V y2d2 = dot(y2, y2);
369
370 bestD2 = y2d2 < bestD2 ? y2d2 : bestD2;
371 }
372
373 const V t3 = dot(p3, v13) / dot(v13, v13);
374
375 if (onEdge(t3)) {
376 const Vec3V<V> y3 = p3 - scaled(v13, t3);
377 const V y3d2 = dot(y3, y3);
378
379 bestD2 = y3d2 < bestD2 ? y3d2 : bestD2;
380 }
381
382 return bestD2;
383 }
384
385 const V t1 = dot(p1, v21) / dot(v21, v21);
386 const V t2 = dot(p2, v32) / dot(v32, v32);
387 const V t3 = dot(p3, v13) / dot(v13, v13);
388
389 const Vec3V<V> y1 = p1 - scaled(v21, t1);
390 const Vec3V<V> y2 = p2 - scaled(v32, t2);
391 const Vec3V<V> y3 = p3 - scaled(v13, t3);
392
393 V bestD2 = dot(p1, p1);
394
395 const V p2d2 = dot(p2, p2);
396 bestD2 = select(p2d2 < bestD2, p2d2, bestD2);
397
398 const V p3d2 = dot(p3, p3);
399 bestD2 = select(p3d2 < bestD2, p3d2, bestD2);
400
401 const V y1d2 = dot(y1, y1);
402 bestD2 = select(both(onEdge(t1), y1d2 < bestD2), y1d2, bestD2);
403
404 const V y2d2 = dot(y2, y2);
405 bestD2 = select(both(onEdge(t2), y2d2 < bestD2), y2d2, bestD2);
406
407 const V y3d2 = dot(y3, y3);
408 bestD2 = select(both(onEdge(t3), y3d2 < bestD2), y3d2, bestD2);
409
410 return select(inside, faceD * faceD, bestD2);
411}
412
413} // namespace TriangleDistance
414
415} // namespace EBGeometry
416
417#endif
GPU portability layer: host/device decoration macros and backend detection.
#define EBGEOMETRY_HOST_ALWAYS_INLINE
Always inline a function in a host pass (GCC and Clang); nothing in a device pass.
Definition EBGeometry_GPU.hpp:123
#define EBGEOMETRY_HOST_DEVICE
Mark a function as callable from both host and device code (no-op on host compilers).
Definition EBGeometry_GPU.hpp:80
Thin wrappers over SSE4.1, AVX and AVX-512 registers, so that one kernel template serves every SIMD w...
EBGEOMETRY_HOST_ALWAYS_INLINE Tagged< T > select(const bool a_second, const Tagged< T > &a_first, const Tagged< T > &a_other) noexcept
One of two tagged values, chosen field by field, so that the compiler selects without a branch: a who...
Definition EBGeometry_CSGOps.hpp:273
bool both(const bool a_lhs, const bool a_rhs) noexcept
Whether both conditions hold (scalar form).
Definition EBGeometry_SIMD.hpp:62
T select(const bool a_mask, const T a_ifTrue, const T a_ifFalse) noexcept
Pick a_ifTrue where a_mask holds, else a_ifFalse (scalar form).
Definition EBGeometry_SIMD.hpp:49
float sqrt(const float a_x) noexcept
Square root (scalar form).
Definition EBGeometry_SIMD.hpp:74
float abs(const float a_x) noexcept
Absolute value (scalar form).
Definition EBGeometry_SIMD.hpp:98
EBGEOMETRY_HOST_ALWAYS_INLINE V unsignedDistance2(const Source &a_triangle, const Vec3V< V > &a_point) noexcept
The squared distance from a point to a triangle, without its sign: the candidates of signedDistance()...
Definition EBGeometry_TriangleDistance.hpp:316
EBGEOMETRY_HOST_ALWAYS_INLINE V signedDistance(const Source &a_triangle, const Vec3V< V > &a_point) noexcept
Signed distance from a_point to a triangle, or to one triangle per lane.
Definition EBGeometry_TriangleDistance.hpp:152
Namespace containing all of EBGeometry's functionality.
Definition EBGeometry_Affine.hpp:26
constexpr Vec3T< T > cross(const Vec3T< T > &u, const Vec3T< T > &v) noexcept
Cross product of two 3D vectors.
T dot(const Vec2T< T > &u, const Vec2T< T > &v) noexcept
Dot product of two 2D vectors.
V type
The scalar type.
Definition EBGeometry_SIMD.hpp:177
A 3D vector of V, where V is a scalar or a SIMD::Pack: x, y and z each hold one lane per triangle.
Definition EBGeometry_TriangleDistance.hpp:39
V y
y components.
Definition EBGeometry_TriangleDistance.hpp:48
V z
z components.
Definition EBGeometry_TriangleDistance.hpp:53
V x
x components.
Definition EBGeometry_TriangleDistance.hpp:43