EBGeometry
Compact, header-only C++ library for fast evaluation of signed distance functions
Loading...
Searching...
No Matches
EBGeometry_MeshRayForms.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
17#ifndef EBGEOMETRY_MESHRAYFORMS_HPP
18#define EBGEOMETRY_MESHRAYFORMS_HPP
19
20// Std includes
21#include <cstddef>
22#include <cstdint>
23
24// Our includes
26#include "EBGeometry_Ray.hpp"
27
28namespace EBGeometry {
29
40template <class T, size_t K, size_t W>
41struct RayForm<TriMeshSDF<T, K, W>>
42{
52 [[nodiscard]] EBGEOMETRY_HOST_DEVICE
53 static inline HitT<T>
54 firstHit(const TriMeshSDF<T, K, W>& a_shape,
55 const RayT<T>& a_ray,
56 T a_tMin,
57 T a_tMax,
58 const RayOptions<T>& a_options) noexcept;
59
69 [[nodiscard]] EBGEOMETRY_HOST_DEVICE
70 static inline HitStatus
71 intersects(const TriMeshSDF<T, K, W>& a_shape,
72 const RayT<T>& a_ray,
73 T a_tMin,
74 T a_tMax,
75 const RayOptions<T>& a_options) noexcept;
76
89 static inline void
90 spans(const TriMeshSDF<T, K, W>& a_shape,
91 const RayT<T>& a_ray,
92 T a_tMin,
93 T a_tMax,
94 const RayOptions<T>& a_options,
95 bool a_firstOnly,
96 RaySpansT<T>& a_spans) noexcept;
97};
98
108template <class T, size_t K>
109struct RayForm<MeshSDF<T, K>>
110{
120 [[nodiscard]] EBGEOMETRY_HOST_DEVICE
121 static inline HitT<T>
122 firstHit(
123 const MeshSDF<T, K>& a_shape, const RayT<T>& a_ray, T a_tMin, T a_tMax, const RayOptions<T>& a_options) noexcept;
124
134 [[nodiscard]] EBGEOMETRY_HOST_DEVICE
135 static inline HitStatus
137 const MeshSDF<T, K>& a_shape, const RayT<T>& a_ray, T a_tMin, T a_tMax, const RayOptions<T>& a_options) noexcept;
138
151 static inline void
152 spans(const MeshSDF<T, K>& a_shape,
153 const RayT<T>& a_ray,
154 T a_tMin,
155 T a_tMax,
156 const RayOptions<T>& a_options,
157 bool a_firstOnly,
158 RaySpansT<T>& a_spans) noexcept;
159};
160
166template <class T>
168{
178 [[nodiscard]] EBGEOMETRY_HOST_DEVICE
179 static inline HitT<T>
180 firstHit(
181 const FlatMeshSDF<T>& a_shape, const RayT<T>& a_ray, T a_tMin, T a_tMax, const RayOptions<T>& a_options) noexcept;
182
195 static inline void
196 spans(const FlatMeshSDF<T>& a_shape,
197 const RayT<T>& a_ray,
198 T a_tMin,
199 T a_tMax,
200 const RayOptions<T>& a_options,
201 bool a_firstOnly,
202 RaySpansT<T>& a_spans) noexcept;
203};
204
205namespace RayDetail {
206
217template <class T>
218[[nodiscard]] EBGEOMETRY_HOST_DEVICE
219inline HitT<T>
220faceHit(const RayT<T>& a_ray, const T a_t, const uint32_t a_face, const Vec3T<T>& a_normal) noexcept
221{
222 HitT<T> hit;
223
224 if (a_face != UINT32_MAX && a_t < Math::Limits<T>::infinity()) {
226 hit.m_t = a_t;
227 hit.m_point = a_ray.at(a_t);
228 hit.m_primitive = a_face;
229 hit.m_normal = a_normal;
230 hit.m_entering = dot(a_ray.m_direction, a_normal) < T(0);
231 hit.m_exact = true;
232 }
233
234 return hit;
235}
236
245template <class T>
247{
251 static constexpr uint32_t Capacity = 2U * RaySpansT<T>::Capacity + 1U;
252
256 struct Crossing
257 {
262
267 };
268
273
277 uint32_t m_count = 0;
278
283
290 inline void
291 insert(const T a_t, const bool a_entering) noexcept
292 {
293 if (m_count == Capacity) {
294 if (!(a_t < m_crossings[Capacity - 1U].m_t)) {
295 return;
296 }
297
298 m_count--;
299 }
300
301 uint32_t i = m_count;
302
303 while (i > 0 && m_crossings[i - 1U].m_t > a_t) {
304 m_crossings[i] = m_crossings[i - 1U];
305
306 i--;
307 }
308
309 m_crossings[i] = Crossing{a_t, a_entering};
310
311 m_count++;
312 }
313
318 [[nodiscard]] EBGEOMETRY_HOST_DEVICE
319 inline T
320 horizon() const noexcept
321 {
322 return (m_count == Capacity) ? m_crossings[Capacity - 1U].m_t : m_tMax;
323 }
324
332 inline void
333 toSpans(const T a_t0, const bool a_inside, RaySpansT<T>& a_spans) const noexcept
334 {
335 const T horizon = this->horizon();
336
337 a_spans.reset(horizon);
338
339 if (m_count == 0U) {
340 if (a_inside) {
341 a_spans.add(RayDetail::openAt(a_t0), RayDetail::openAt(horizon));
342 }
343
344 return;
345 }
346
347 CrossingPairer<T> pairer;
348
349 for (uint32_t i = 0; i < m_count; i++) {
350 const Crossing& c = m_crossings[i];
351
352 pairer.take(SpanEndT<T>{c.m_t, 0U, SpanEndT<T>::Exact}, c.m_entering, a_t0, a_spans);
353 }
354
355 pairer.finish(horizon, a_spans);
356 }
357};
358
359} // namespace RayDetail
360
361template <class T, size_t K, size_t W>
363inline HitT<T>
365 const RayT<T>& a_ray,
366 const T a_tMin,
367 const T a_tMax,
368 const RayOptions<T>& /*a_options*/) noexcept
369{
370 // The state is the nearest crossing so far; it starts at the far end, so that the walk's bound (the
371 // state's distance) leaves out every box beyond it.
372 struct Nearest
373 {
374 T m_t;
375 uint32_t m_face;
376 Vec3T<T> m_normal;
377 };
378
379 Nearest nearest{a_tMax, UINT32_MAX, Vec3T<T>::zeros()};
380
381 const auto bvh = a_shape.getRoot();
382 const auto groups = bvh.getPrimitives();
383
384 const auto leaf = [&groups, &a_ray, a_tMin](Nearest& a_state, size_t a_offset, size_t a_count) noexcept {
385 for (size_t i = a_offset; i < a_offset + a_count; i++) {
386 uint32_t face = UINT32_MAX;
388
389 const T t = groups[static_cast<uint32_t>(i)].rayCrossing(
390 a_ray.m_origin, a_ray.m_direction, a_tMin, a_state.m_t, face, normal);
391
392 if (face != UINT32_MAX && t <= a_state.m_t) {
393 a_state = Nearest{t, face, normal};
394 }
395 }
396 };
397
398 const auto bound = [](const Nearest& a_state) noexcept -> T { return a_state.m_t; };
399
400 bvh.traverse(BVH::RayQuery<T>(a_ray.m_origin, a_ray.m_direction, a_tMin), nearest, leaf, bound);
401
402 HitT<T> hit;
403
404 if (nearest.m_face != UINT32_MAX) {
406 hit.m_t = nearest.m_t;
407 hit.m_point = a_ray.at(nearest.m_t);
408 hit.m_primitive = nearest.m_face;
409 hit.m_normal = nearest.m_normal;
410 hit.m_entering = dot(a_ray.m_direction, nearest.m_normal) < T(0);
411 hit.m_exact = true;
412 }
413
414 return hit;
415}
416
417template <class T, size_t K, size_t W>
419inline HitStatus
421 const RayT<T>& a_ray,
422 const T a_tMin,
423 const T a_tMax,
424 const RayOptions<T>& /*a_options*/) noexcept
425{
426 // The walk ends once a crossing is found: the bound drops to -infinity, which no box is within.
427 bool found = false;
428
429 const auto bvh = a_shape.getRoot();
430 const auto groups = bvh.getPrimitives();
431
432 const auto leaf = [&groups, &a_ray, a_tMin, a_tMax](bool& a_found, size_t a_offset, size_t a_count) noexcept {
433 for (size_t i = a_offset; i < a_offset + a_count && !a_found; i++) {
434 uint32_t face = UINT32_MAX;
436
437 const T t =
438 groups[static_cast<uint32_t>(i)].rayCrossing(a_ray.m_origin, a_ray.m_direction, a_tMin, a_tMax, face, normal);
439
440 a_found = face != UINT32_MAX && t <= a_tMax;
441 }
442 };
443
444 const auto bound = [a_tMax](const bool& a_found) noexcept -> T {
445 return a_found ? -Math::Limits<T>::infinity() : a_tMax;
446 };
447
448 bvh.traverse(BVH::RayQuery<T>(a_ray.m_origin, a_ray.m_direction, a_tMin), found, leaf, bound);
449
450 return found ? HitStatus::Hit : HitStatus::Miss;
451}
452
453template <class T, size_t K>
455inline HitT<T>
457 const RayT<T>& a_ray,
458 const T a_tMin,
459 const T a_tMax,
460 const RayOptions<T>& /*a_options*/) noexcept
461{
462 // The state is the nearest crossing so far and its face; it starts at the far end, so that the walk's
463 // bound leaves out every box beyond it.
464 struct Nearest
465 {
466 T m_t;
467 uint32_t m_face;
468 };
469
470 Nearest nearest{a_tMax, UINT32_MAX};
471
472 const auto bvh = a_shape.getRoot();
473 const auto mesh = a_shape.getMesh();
474 const auto faceIds = bvh.getPrimitives();
475
476 const auto leaf = [&faceIds, &mesh, &a_ray, a_tMin](Nearest& a_state, size_t a_offset, size_t a_count) noexcept {
477 for (size_t i = a_offset; i < a_offset + a_count; i++) {
478 const uint32_t face = faceIds[static_cast<uint32_t>(i)];
479 const T t = mesh.getFace(face).rayCrossing(a_ray.m_origin, a_ray.m_direction, a_tMin, a_state.m_t, mesh);
480
481 // A crossing (finite: none is +infinity, however far the interval goes) is within the nearest so far.
482 if (t < Math::Limits<T>::infinity()) {
483 a_state = Nearest{t, face};
484 }
485 }
486 };
487
488 const auto bound = [](const Nearest& a_state) noexcept -> T { return a_state.m_t; };
489
490 bvh.traverse(BVH::RayQuery<T>(a_ray.m_origin, a_ray.m_direction, a_tMin), nearest, leaf, bound);
491
492 return nearest.m_face == UINT32_MAX
493 ? HitT<T>()
494 : RayDetail::faceHit(a_ray, nearest.m_t, nearest.m_face, mesh.getFace(nearest.m_face).getNormal());
495}
496
497template <class T, size_t K>
499inline HitStatus
501 const RayT<T>& a_ray,
502 const T a_tMin,
503 const T a_tMax,
504 const RayOptions<T>& /*a_options*/) noexcept
505{
506 // The walk ends once a crossing is found: the bound drops to -infinity, which no box is within.
507 bool found = false;
508
509 const auto bvh = a_shape.getRoot();
510 const auto mesh = a_shape.getMesh();
511 const auto faceIds = bvh.getPrimitives();
512
513 const auto leaf = [&faceIds, &mesh, &a_ray, a_tMin, a_tMax](bool& a_found, size_t a_offset, size_t a_count) noexcept {
514 for (size_t i = a_offset; i < a_offset + a_count && !a_found; i++) {
515 const auto& face = mesh.getFace(faceIds[static_cast<uint32_t>(i)]);
516
517 // None is +infinity, which an unbounded interval would contain.
518 a_found = face.rayCrossing(a_ray.m_origin, a_ray.m_direction, a_tMin, a_tMax, mesh) < Math::Limits<T>::infinity();
519 }
520 };
521
522 const auto bound = [a_tMax](const bool& a_found) noexcept -> T {
523 return a_found ? -Math::Limits<T>::infinity() : a_tMax;
524 };
525
526 bvh.traverse(BVH::RayQuery<T>(a_ray.m_origin, a_ray.m_direction, a_tMin), found, leaf, bound);
527
528 return found ? HitStatus::Hit : HitStatus::Miss;
529}
530
531template <class T>
533inline HitT<T>
535 const RayT<T>& a_ray,
536 const T a_tMin,
537 const T a_tMax,
538 const RayOptions<T>& /*a_options*/) noexcept
539{
540 const auto mesh = a_shape.getMesh();
541
542 T nearest = a_tMax;
543 uint32_t face = UINT32_MAX;
544
545 for (uint32_t i = 0; i < mesh.numFaces(); i++) {
546 const T t = mesh.getFace(i).rayCrossing(a_ray.m_origin, a_ray.m_direction, a_tMin, nearest, mesh);
547
548 if (t < Math::Limits<T>::infinity()) {
549 nearest = t;
550 face = i;
551 }
552 }
553
554 return face == UINT32_MAX ? HitT<T>() : RayDetail::faceHit(a_ray, nearest, face, mesh.getFace(face).getNormal());
555}
556
557template <class T, size_t K, size_t W>
559inline void
561 const RayT<T>& a_ray,
562 const T a_tMin,
563 const T a_tMax,
564 const RayOptions<T>& a_options,
565 const bool a_firstOnly,
566 RaySpansT<T>& a_spans) noexcept
567{
568 if (a_firstOnly) {
569 RayDetail::firstSpan(a_shape, a_ray, a_tMin, a_tMax, a_options, a_spans);
570
571 return;
572 }
573
575
576 crossings.m_tMax = a_tMax;
577
578 const auto bvh = a_shape.getRoot();
579 const auto groups = bvh.getPrimitives();
580
581 const auto leaf =
582 [&groups, &a_ray, a_tMin](RayDetail::NearestCrossings<T>& a_state, size_t a_offset, size_t a_count) noexcept {
583 for (size_t i = a_offset; i < a_offset + a_count; i++) {
584 const auto& group = groups[static_cast<uint32_t>(i)];
585
586 T t[W];
587
588 group.rayCrossings(a_ray.m_origin, a_ray.m_direction, a_tMin, a_state.horizon(), t);
589
590 for (size_t lane = 0; lane < W; lane++) {
591 if (t[lane] < Math::Limits<T>::infinity()) {
592 a_state.insert(t[lane], dot(a_ray.m_direction, group.getNormal(lane)) < T(0));
593 }
594 }
595 }
596 };
597
598 const auto bound = [](const RayDetail::NearestCrossings<T>& a_state) noexcept -> T { return a_state.horizon(); };
599
600 bvh.traverse(BVH::RayQuery<T>(a_ray.m_origin, a_ray.m_direction, a_tMin), crossings, leaf, bound);
601
602 crossings.toSpans(a_tMin, crossings.m_count == 0U && RayDetail::insideAt(a_shape, a_ray.at(a_tMin)), a_spans);
603}
604
605template <class T, size_t K>
607inline void
609 const RayT<T>& a_ray,
610 const T a_tMin,
611 const T a_tMax,
612 const RayOptions<T>& a_options,
613 const bool a_firstOnly,
614 RaySpansT<T>& a_spans) noexcept
615{
616 if (a_firstOnly) {
617 RayDetail::firstSpan(a_shape, a_ray, a_tMin, a_tMax, a_options, a_spans);
618
619 return;
620 }
621
623
624 crossings.m_tMax = a_tMax;
625
626 const auto bvh = a_shape.getRoot();
627 const auto mesh = a_shape.getMesh();
628 const auto faceIds = bvh.getPrimitives();
629
630 const auto leaf = [&faceIds, &mesh, &a_ray, a_tMin](
631 RayDetail::NearestCrossings<T>& a_state, size_t a_offset, size_t a_count) noexcept {
632 for (size_t i = a_offset; i < a_offset + a_count; i++) {
633 const uint32_t face = faceIds[static_cast<uint32_t>(i)];
634 const auto& f = mesh.getFace(face);
635 const T t = f.rayCrossing(a_ray.m_origin, a_ray.m_direction, a_tMin, a_state.horizon(), mesh);
636
637 if (t < Math::Limits<T>::infinity()) {
638 a_state.insert(t, dot(a_ray.m_direction, f.getNormal()) < T(0));
639 }
640 }
641 };
642
643 const auto bound = [](const RayDetail::NearestCrossings<T>& a_state) noexcept -> T { return a_state.horizon(); };
644
645 bvh.traverse(BVH::RayQuery<T>(a_ray.m_origin, a_ray.m_direction, a_tMin), crossings, leaf, bound);
646
647 crossings.toSpans(a_tMin, crossings.m_count == 0U && RayDetail::insideAt(a_shape, a_ray.at(a_tMin)), a_spans);
648}
649
650template <class T>
652inline void
654 const RayT<T>& a_ray,
655 const T a_tMin,
656 const T a_tMax,
657 const RayOptions<T>& a_options,
658 const bool a_firstOnly,
659 RaySpansT<T>& a_spans) noexcept
660{
661 if (a_firstOnly) {
662 RayDetail::firstSpan(a_shape, a_ray, a_tMin, a_tMax, a_options, a_spans);
663
664 return;
665 }
666
668
669 crossings.m_tMax = a_tMax;
670
671 const auto mesh = a_shape.getMesh();
672
673 for (uint32_t i = 0; i < mesh.numFaces(); i++) {
674 const auto& f = mesh.getFace(i);
675 const T t = f.rayCrossing(a_ray.m_origin, a_ray.m_direction, a_tMin, crossings.horizon(), mesh);
676
677 if (t < Math::Limits<T>::infinity()) {
678 crossings.insert(t, dot(a_ray.m_direction, f.getNormal()) < T(0));
679 }
680 }
681
682 crossings.toSpans(a_tMin, crossings.m_count == 0U && RayDetail::insideAt(a_shape, a_ray.at(a_tMin)), a_spans);
683}
684
685} // namespace EBGeometry
686
687#endif
#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
Declaration of signed distance functions for DCEL meshes.
HitT< T > faceHit(const RayT< T > &a_ray, const T a_t, const uint32_t a_face, const Vec3T< T > &a_normal) noexcept
A crossing of a mesh face as a HitT.
Definition EBGeometry_MeshRayForms.hpp:220
Ray queries on any shape: where a ray, or the segment between two points, first crosses its surface (...
Signed distance function for a DCEL mesh. Does not use BVHs.
Definition EBGeometry_MeshDistanceFunctions.hpp:79
A FlatMeshSDF must be trivially copyable: that is what lets a rebasedView() be byte-copied into a dev...
Definition EBGeometry_MeshDistanceFunctions.hpp:249
Signed distance function for a pure triangle mesh using SoA-grouped primitives in a compact (lineariz...
Definition EBGeometry_MeshDistanceFunctions.hpp:514
Three-dimensional vector class with arithmetic operators.
Definition EBGeometry_Vec.hpp:258
static constexpr Vec3T< T > zeros() noexcept
Return a vector with x = y = z = 0.
Namespace containing all of EBGeometry's functionality.
Definition EBGeometry_Affine.hpp:26
HitStatus intersects(const S &a_shape, const RayT< T > &a_ray, typename RayT< T >::Scalar a_tMin, typename RayT< T >::Scalar a_tMax, const RayOptions< T > &a_options=RayOptions< T >()) noexcept
Whether a ray crosses a shape's surface in [a_tMin, a_tMax].
T dot(const Vec2T< T > &u, const Vec2T< T > &v) noexcept
Dot product of two 2D vectors.
Vec3T< T > normal(const F &a_function, const Vec3T< T > &a_point, const T &a_delta) noexcept
Outward unit normal of a function's level set, by central finite differences.
HitStatus
The outcome of a ray query.
Definition EBGeometry_Ray.hpp:84
@ Miss
The ray does not cross the surface in the interval.
@ Hit
The ray crosses the surface; HitT says where.
HitT< T > firstHit(const S &a_shape, const RayT< T > &a_ray, typename RayT< T >::Scalar a_tMin, typename RayT< T >::Scalar a_tMax, const RayOptions< T > &a_options=RayOptions< T >()) noexcept
The first crossing of a shape's surface along a ray, entering or leaving.
A ray tracked through a BVH: the key of a box is the distance along the ray at which it enters the bo...
Definition EBGeometry_BVHQuery.hpp:286
Where a ray first crosses a surface.
Definition EBGeometry_Ray.hpp:96
bool m_entering
Whether the ray enters the object there (it was outside just before), rather than leaving it.
Definition EBGeometry_Ray.hpp:129
bool m_exact
Whether the crossing is exact (found by a ray form), rather than found to within RayOptions::m_tolera...
Definition EBGeometry_Ray.hpp:135
T m_t
Distance along the ray: of the crossing for a hit, of the last point reached for an unresolved query,...
Definition EBGeometry_Ray.hpp:106
Vec3T< T > m_normal
The outward unit normal at the crossing: from the shape's ray form, or the normalized gradient of its...
Definition EBGeometry_Ray.hpp:124
uint32_t m_primitive
The finest element the shape can name at the crossing: the face of a mesh SDF, the deciding leaf of a...
Definition EBGeometry_Ray.hpp:118
HitStatus m_status
Whether the ray crosses the surface.
Definition EBGeometry_Ray.hpp:100
Vec3T< T > m_point
The point at m_t (for a hit or an unresolved query).
Definition EBGeometry_Ray.hpp:111
Numeric limits, with the meanings of the std::numeric_limits members of the same names.
Definition EBGeometry_Math.hpp:91
Pairs crossings, in order, into spans: a span from each crossing the ray enters at to the next it lea...
Definition EBGeometry_RaySpans.hpp:707
void take(const SpanEndT< T > &a_end, const bool a_entering, const T a_t0, RaySpansT< T > &a_spans) noexcept
Take the next crossing.
Definition EBGeometry_RaySpans.hpp:732
void finish(const T a_t, RaySpansT< T > &a_spans) noexcept
Close the open span, if any, at an Open end.
Definition EBGeometry_RaySpans.hpp:759
A crossing.
Definition EBGeometry_MeshRayForms.hpp:257
bool m_entering
Whether the ray enters there: it runs against the face's outward normal.
Definition EBGeometry_MeshRayForms.hpp:266
T m_t
Distance along the ray.
Definition EBGeometry_MeshRayForms.hpp:261
The nearest crossings of a mesh along a ray, in order, as a BVH walk finds them: at most Capacity,...
Definition EBGeometry_MeshRayForms.hpp:247
uint32_t m_count
How many are held.
Definition EBGeometry_MeshRayForms.hpp:277
Crossing m_crossings[Capacity]
The crossings, nearest first.
Definition EBGeometry_MeshRayForms.hpp:272
T horizon() const noexcept
How far the crossings are known: the end of the interval, or the farthest held once full.
Definition EBGeometry_MeshRayForms.hpp:320
void toSpans(const T a_t0, const bool a_inside, RaySpansT< T > &a_spans) const noexcept
The spans: the crossings paired by the side of the face the ray crosses from.
Definition EBGeometry_MeshRayForms.hpp:333
void insert(const T a_t, const bool a_entering) noexcept
Take a crossing.
Definition EBGeometry_MeshRayForms.hpp:291
static constexpr uint32_t Capacity
Most crossings held.
Definition EBGeometry_MeshRayForms.hpp:251
T m_tMax
End of the interval.
Definition EBGeometry_MeshRayForms.hpp:282
How to ray trace a shape directly, instead of marching its signed distance.
Definition EBGeometry_Ray.hpp:347
How a query marches, where a shape has no ray form.
Definition EBGeometry_Ray.hpp:155
Where a ray is inside an object over an interval [t0, t1]: its spans, in order, each from where it en...
Definition EBGeometry_Ray.hpp:248
A ray: the points origin + t * direction.
Definition EBGeometry_Ray.hpp:54
One end of a span: where a ray enters or leaves an object.
Definition EBGeometry_Ray.hpp:200