FEAT 3
Finite Element Analysis Toolbox
Loading...
Searching...
No Matches
raycast.hpp
1// FEAT3: Finite Element Analysis Toolbox, Version 3
2// Copyright (C) 2010 by Stefan Turek & the FEAT group
3// FEAT3 is released under the GNU General Public License version 3,
4// see the file 'copyright.txt' in the top level directory for details.
5
6#include "kernel/adjacency/adjactor.hpp"
7#include "kernel/adjacency/base.hpp"
8#include "kernel/adjacency/graph.hpp"
9#include "kernel/shape.hpp"
10#include "kernel/util/math.hpp"
11#include "kernel/util/tiny_algebra.hpp"
13
14#include <limits>
15#include <optional>
16
17namespace FEAT::Geometry
18{
27 template<typename Vector_>
28 struct Ray
29 {
31 Vector_ origin;
32
34 Vector_ direction;
35 };
36
37 template<typename Vector_>
38 std::ostream& operator<<(std::ostream& out, const Ray<Vector_>& ray)
39 {
40 out << "Ray{ origin: " << ray.origin << ", direction: " << ray.direction << "}";
41 return out;
42 }
43
62 template<typename DT_>
64 {
66 explicit IntersectionData(DT_ t) : t_entry(t), t_exit(t)
67 {
68 }
69
71 IntersectionData(DT_ t1, DT_ t2) : t_entry(t1), t_exit(t2)
72 {
73 ASSERT(t1 <= t2);
74 }
75
78
80 DT_ t_exit;
81 };
82
83 template<typename DT_>
84 std::ostream& operator<<(std::ostream& out, const IntersectionData<DT_>& d)
85 {
86 out << "IntersectionData{ t_entry: " << d.t_entry << ", t_exit: " << d.t_exit << "}";
87 return out;
88 }
89
96 template<typename DT_>
98 {
99 return {Math::min(a.t_entry, b.t_entry), Math::max(a.t_exit, b.t_exit)};
100 }
101
103 template<typename DT_>
104 std::optional<IntersectionData<DT_>>
105 merge(const std::optional<IntersectionData<DT_>>& a, const std::optional<IntersectionData<DT_>>& b)
106 {
107 if(!a && !b)
108 {
109 return std::nullopt;
110 }
111 else if(a && !b)
112 {
113 return a;
114 }
115 else if(!a && b)
116 {
117 return b;
118 }
119 else
120 {
121 return std::make_optional(merge(a.value(), b.value()));
122 }
123 }
124
135 template<typename DT_>
137 {
138 public:
140 using DataType = DT_;
141
144
146 using IntersectionResultType = std::optional<IntersectionDataType>;
147
150
153
155 static constexpr DataType eps = Math::eps<DataType>();
156
171 {
172 const Vector2D ab = b - a;
173
174 // https://stackoverflow.com/a/565282
175 //
176 // Solve r.origin + t * r.direction = a + u * ab
177 // With x as the 2D cross product:
178 // => (r.origin + t * r.direction) x ab = (a + u* ab) x ab
179 // => t(r.direction x ab) = (a - r.origin) x ab
180 // => t = (a - r.origin) x ab / (r.direction x ab)
181 // Analoguous
182 // u = (r.origin - a) x r.direction / (ab x r.direction)
183 // with v x w = -w x v
184 // u = (a - r.origin) x r.direction / (r.direction x ab)
185
186 DataType denom = cross(r.direction, ab);
187 DataType t_num = cross(a - r.origin, ab);
188 DataType u_num = cross(a - r.origin, r.direction);
189
190 // Are directions of ray and segment parallel
191 bool dir_parallel = Math::abs(denom) < eps;
192
193 // Are ray and segment colinear
194 bool aligned = Math::abs(u_num) < eps;
195
196 if(dir_parallel && aligned)
197 {
198 // Solve for overlap between ray and segment
199
200 // Solve r.origin + t * r.direction = a
201 // => t * r.direction = a - r.origin
202 // => t = (a - r.origin) * r.direction / (r.direction * r.direction)
203
204 // Solve r.origin + t * r.direction = b
205 // => t = (a - r.origin) * r.direction / (r.direction * r.direction)
206
207 const DataType rr = Tiny::dot(r.direction, r.direction);
208 DataType t1 = Tiny::dot(a - r.origin, r.direction) / rr;
209 DataType t2 = Tiny::dot(b - r.origin, r.direction) / rr;
210
211 if(t1 > t2)
212 {
213 std::swap(t1, t2);
214 }
215
216 if(t2 < DataType(0.0))
217 {
218 // Exit point is behind ray start => no overlap
219 return std::nullopt;
220 }
221
222 // Discard overlap behind ray origin
223 t1 = Math::max(t1, DataType(0.0));
224 t2 = Math::max(t2, DataType(0.0));
225
226 return std::make_optional<IntersectionDataType>(t1, t2);
227 }
228 else if(dir_parallel && !aligned)
229 {
230 // Lines are parallel and non-intersecting
231 return std::nullopt;
232 }
233 else
234 {
235 const DataType t = t_num / denom;
236 const DataType u = u_num / denom;
237
238 if(t >= DT_(0.0) && DT_(0.0) <= u && u <= DT_(1.0))
239 {
240 // Intersection in front of ray
241 return std::make_optional<IntersectionDataType>(t);
242 }
243 else
244 {
245 // Either no intersection or intersection behind ray
246 return std::nullopt;
247 }
248 }
249 }
250
267 ray_triangle_intersection(const Ray<Vector3D>& r, const Vector3D& a, const Vector3D& b, const Vector3D& c)
268 {
269 // Solve system
270 // r.origin + t * r.direction = (1 - u - v)a + ub + uc
271 // with u >= 0, v >= 0, u + v <= 1.0
272 // Rearranging yields
273 // [-r.direction, b - a, c - a] [t, u, v]' = ray.origin - a
274 // The below code solves the system using Cramer's rule
275
276 // If true, r.direction and (c - a) are parallel. Reorder triangle so that determinant calculation does not fail
277 bool reorder = Math::abs(Tiny::dot(r.direction, (c - a)) / (r.direction.norm_euclid() * (c - a).norm_euclid())) > (DataType(1.0) - eps);
278
279 const Vector3D e1 = reorder ? c - b : b - a;
280 const Vector3D e2 = reorder ? a - b : c - a;
281
283 Tiny::cross(normal, r.direction, e2);
284
285 DataType det = Tiny::dot(e1, normal);
286
287 if(det > -eps && det < eps)
288 {
289 // Ray is parallel to triangle
290
291 if(Math::abs(Tiny::dot(normal, r.origin - a)) > eps)
292 {
293 // Ray is not coplanar => no intersection
294 return std::nullopt;
295 }
296
297 // Ray is coplanar. There might be an interval of intersection
298 // Reduce to 2D polygon intersection test
299
300 // Using a as origin and e1, e2 as axes for the 2D coordinate system
301 // The triangle is then always (0, 0), (1, 0), (0, 1)
302
303 // Project ray into this coordinate system
304
305 // https://math.stackexchange.com/a/1307635
306 const auto world_to_plane = [&](const Vector3D& v)
307 {
308 const DataType alpha = Tiny::dot(normal, cross(e2, v)) / Tiny::dot(normal, cross(e2, e1));
309 const DataType beta = Tiny::dot(normal, cross(e1, v)) / Tiny::dot(normal, cross(e1, e2));
310 return Vector2D{alpha, beta};
311 };
312
313 const auto plane_to_world = [&](const Vector2D& v) { return a + v[0] * e1 + v[1] * e2; };
314
315 Vector2D origin = world_to_plane(r.origin - a);
316 Vector2D direction = world_to_plane(r.direction);
317
318 auto i1 = ray_segment_intersection(Ray<Vector2D>{origin, direction}, Vector2D{0.0, 0.0}, Vector2D{1.0, 0.0});
319 auto i2 = ray_segment_intersection(Ray<Vector2D>{origin, direction}, Vector2D{1.0, 0.0}, Vector2D{0.0, 1.0});
320 auto i3 = ray_segment_intersection(Ray<Vector2D>{origin, direction}, Vector2D{0.0, 1.0}, Vector2D{0.0, 0.0});
321
322 DataType t1(std::numeric_limits<DataType>::max());
323 DataType t2(0.0);
324
325 const DataType ray_length = r.direction.norm_euclid();
326
327 auto i = merge(merge(i1, i2), i3);
328
329 if(i)
330 {
331 const Vector2D p1 = origin + i.value().t_entry * direction;
332 const Vector2D p2 = origin + i.value().t_exit * direction;
333
334 t1 = Math::min(t1, (plane_to_world(p1) - r.origin).norm_euclid() / ray_length);
335 t2 = Math::max(t2, (plane_to_world(p2) - r.origin).norm_euclid() / ray_length);
336
337 return std::make_optional<IntersectionDataType>(t1, t2);
338 }
339 else
340 {
341 return std::nullopt;
342 }
343 }
344
345 DataType inv_det = DataType(1.0) / det;
346
347 Vector3D tvec = r.origin - a;
348
349 // Calculate u
350 const DataType u = Tiny::dot(tvec, normal) * inv_det;
351 if(u < DataType(0.0) || u > DataType(1.0))
352 {
353 return std::nullopt;
354 }
355
356 Vector3D qvec;
357 Tiny::cross(qvec, tvec, e1);
358
359 // Calculate v
360 const DataType v = Tiny::dot(r.direction, qvec) * inv_det;
361 if(v < DataType(0.0) || u + v > DataType(1.0))
362 {
363 return std::nullopt;
364 }
365
366 const DataType t = Tiny::dot(e2, qvec) * inv_det;
367
368 if(t > eps)
369 {
370 return std::make_optional<IntersectionDataType>(t);
371 }
372 else
373 {
374 return std::nullopt;
375 }
376 }
377
378 protected:
379 static DataType cross(const Vector2D& a, const Vector2D& b)
380 {
381 return (a[0] * b[1]) - (a[1] * b[0]);
382 }
383
384 static Vector3D cross(const Vector3D& a, const Vector3D& b)
385 {
386 Vector3D result;
387 Tiny::cross(result, a, b);
388 return result;
389 }
390 };
391
392 template<typename MeshType_>
393 std::optional<IntersectionData<typename MeshType_::CoordType>>
394 facet_raycast(const MeshType_& mesh, const Ray<typename MeshType_::VertexType>& r, Index facet)
395 {
396 using DataType = typename MeshType_::CoordType;
397 using IntersectionData = IntersectionData<DataType>;
398 using RIP = RayIntersectionPrimitives<DataType>;
399
400 using ShapeType = typename MeshType_::ShapeType;
401
402 using FacetShapeType = typename Shape::FaceTraits<ShapeType, ShapeType::dimension - 1>::ShapeType;
403 static constexpr int facet_dim = FacetShapeType::dimension;
404 static constexpr int world_dim = MeshType_::world_dim;
405
406 const auto& v_at_f = mesh.template get_index_set<facet_dim, 0>();
407 const auto& vtx = mesh.get_vertex_set();
408
409 if constexpr(std::is_same_v<FacetShapeType, Shape::Vertex> && world_dim == 1)
410 {
411 // Raycasting on 1d mesh
412 DataType origin = r.origin[0];
413 DataType direction = r.direction[0];
414
415 DataType facet_coord = mesh.get_vertex_set()[facet][0];
416
417 if(direction > 0 && facet >= origin)
418 {
419 return std::make_optional<IntersectionData>((facet_coord - origin) / direction);
420 }
421 else if(direction < 0 && facet <= origin)
422 {
423 return std::make_optional<IntersectionData>((origin - facet_coord) / direction);
424 }
425 else
426 {
427 return std::nullopt;
428 }
429 }
430
431 if constexpr(std::is_same_v<FacetShapeType, Shape::Hypercube<1>> && world_dim == 2)
432 {
433 return RIP::ray_segment_intersection(r, vtx[v_at_f(facet, 0)], vtx[v_at_f(facet, 1)]);
434 }
435
436 if constexpr(std::is_same_v<FacetShapeType, Shape::Simplex<1>> && world_dim == 2)
437 {
438 return RIP::ray_segment_intersection(r, vtx[v_at_f(facet, 0)], vtx[v_at_f(facet, 1)]);
439 }
440
441 if constexpr(std::is_same_v<FacetShapeType, Shape::Simplex<2>> && world_dim == 3)
442 {
443 return RIP::ray_triangle_intersection(r, vtx[v_at_f(facet, 0)], vtx[v_at_f(facet, 1)], vtx[v_at_f(facet, 2)]);
444 }
445
446 if constexpr(std::is_same_v<FacetShapeType, Shape::Hypercube<2>> && world_dim == 3)
447 {
448 // Split face into two triangles
449 auto i1 = RIP::ray_triangle_intersection(r, vtx[v_at_f(facet, 0)], vtx[v_at_f(facet, 1)], vtx[v_at_f(facet, 2)]);
450 auto i2 = RIP::ray_triangle_intersection(r, vtx[v_at_f(facet, 2)], vtx[v_at_f(facet, 1)], vtx[v_at_f(facet, 3)]);
451
452 return merge(i1, i2);
453 }
454
455 XABORTM("Unsupported raycast case");
456 }
457
461 template<typename MeshType_>
463 {
464 public:
466 using MeshType = MeshType_;
467
469 using DataType = typename MeshType::CoordType;
470
473
475 using IntersectionResultType = std::optional<IntersectionDataType>;
476
478 using Vector = typename MeshType::VertexType;
479
481 static constexpr int shape_dim = MeshType::shape_dim;
482
484 static constexpr int facet_dim = shape_dim - 1;
485
486 protected:
489
492
493 public:
495 explicit VertexRaycaster(const MeshType& mesh) :
496 _mesh(mesh),
497 _c_at_v(Adjacency::RenderType::transpose, _mesh.template get_index_set<shape_dim, 0>())
498 {
499 }
500
516 IntersectionResultType cast(const Index vertex, const Vector& direction) const
517 {
518 const DataType eps = DataType(1e-8);
519 const Ray<Vector> r{_mesh.get_vertex_set()[vertex], direction};
520
521 const auto& f_at_c = _mesh.template get_index_set<shape_dim, facet_dim>();
522 const Adjacency::CompositeAdjactor adj(_c_at_v, f_at_c);
523
524 IntersectionResultType result(std::nullopt);
525
526 for(auto face = adj.image_begin(vertex); face != adj.image_end(vertex); ++face)
527 {
528 const IntersectionResultType i = facet_raycast(_mesh, r, *face);
529
530 if(!i)
531 {
532 continue;
533 }
534
535 const IntersectionDataType& hit = i.value();
536
537 if(!result && hit.t_exit > eps)
538 {
539 result = i;
540 }
541 else if(result)
542 {
543 const IntersectionDataType& res = result.value();
544
545 // Update the result if we either found an earlier exit point,
546 // or if we found an earlier entry point with the same exit distance.
547 if(
548 hit.t_exit > eps &&
549 (hit.t_exit < res.t_exit || (Math::abs(hit.t_exit - res.t_exit) < eps && hit.t_entry < res.t_entry)))
550 {
551 result = hit;
552 }
553 }
554 }
555
556 return result;
557 }
558 };
559} // namespace FEAT::Geometry
#define ASSERT(expr)
Debug-Assertion macro definition.
Definition: assertion.hpp:229
#define XABORTM(msg)
Abortion macro definition with custom message.
Definition: assertion.hpp:192
FEAT Kernel base header.
Composite Adjactor implementation.
Definition: adjactor.hpp:274
ImageIterator image_end(Index domain_node) const
Returns an iterator for the first position past the last adjacent image node.
Definition: adjactor.hpp:464
ImageIterator image_begin(Index domain_node) const
Returns an iterator for the first adjacent image node.
Definition: adjactor.hpp:457
Adjacency Graph implementation.
Definition: graph.hpp:34
Intersection tests between Rays and primitives.
Definition: raycast.hpp:137
Tiny::Vector< DataType, 2 > Vector2D
2D vector/point type
Definition: raycast.hpp:149
std::optional< IntersectionDataType > IntersectionResultType
Intersection result type.
Definition: raycast.hpp:146
static IntersectionResultType ray_segment_intersection(const Ray< Vector2D > &r, const Vector2D &a, const Vector2D &b)
2D ray-segment intersection
Definition: raycast.hpp:170
static IntersectionResultType ray_triangle_intersection(const Ray< Vector3D > &r, const Vector3D &a, const Vector3D &b, const Vector3D &c)
Definition: raycast.hpp:267
static constexpr DataType eps
Precision.
Definition: raycast.hpp:155
Tiny::Vector< DataType, 3 > Vector3D
3D vector/point type
Definition: raycast.hpp:152
Utility class for casting rays originating from a mesh vertex.
Definition: raycast.hpp:463
static constexpr int shape_dim
Dimension of the mesh shape.
Definition: raycast.hpp:481
VertexRaycaster(const MeshType &mesh)
Constructor.
Definition: raycast.hpp:495
IntersectionResultType cast(const Index vertex, const Vector &direction) const
Cast a ray from the given vertex in the given direction.
Definition: raycast.hpp:516
MeshType_ MeshType
Mesh type.
Definition: raycast.hpp:466
const MeshType & _mesh
Reference to mesh.
Definition: raycast.hpp:488
typename MeshType::CoordType DataType
Data type.
Definition: raycast.hpp:469
static constexpr int facet_dim
Dimension of the facets to test against.
Definition: raycast.hpp:484
typename MeshType::VertexType Vector
Vector type.
Definition: raycast.hpp:478
Adjacency::Graph _c_at_v
Precomputes mapping from vertices to adjacent shape_dim-cells.
Definition: raycast.hpp:491
std::optional< IntersectionDataType > IntersectionResultType
Intersection result type.
Definition: raycast.hpp:475
Geometry namespace.
IntersectionData< DT_ > merge(const IntersectionData< DT_ > &a, const IntersectionData< DT_ > &b)
Merge two intersection datas.
Definition: raycast.hpp:97
T_ abs(T_ x)
Returns the absolute value.
Definition: math.hpp:275
T_ min(T_ a, T_ b)
Returns the minimum of two values.
Definition: math.hpp:123
T_ max(T_ a, T_ b)
Returns the maximum of two values.
Definition: math.hpp:137
CUDA_HOST_DEVICE T_ dot(const T_ &a, const T_ &b)
Computes the dot-product of two scalars.
std::uint64_t Index
Index data type.
Contains information about a ray-primitive intersection.
Definition: raycast.hpp:64
IntersectionData(DT_ t1, DT_ t2)
Constructor.
Definition: raycast.hpp:71
DT_ t_entry
Distance along ray of entry point. Relative to ray direction.
Definition: raycast.hpp:77
DT_ t_exit
Distance along ray of exit point. Relative to ray direction.
Definition: raycast.hpp:80
IntersectionData(DT_ t)
Constructor.
Definition: raycast.hpp:66
Geometric ray.
Definition: raycast.hpp:29
Vector_ origin
Ray origin.
Definition: raycast.hpp:31
Vector_ direction
Ray direction.
Definition: raycast.hpp:34