cgv
Loading...
Searching...
No Matches
intersection.h
1#pragma once
2
3#include "functions.h"
4#include "fvec.h"
5#include "pose.h"
6#include "fray.h"
7#include <limits>
8
10namespace cgv {
11namespace math {
12
22template <typename T>
23int ray_box_intersection(const fray<T, 3>& ray, fvec<T, 3> extent, fvec<T, 2>& out_ts, fvec<T, 3>* out_normal = nullptr) {
24 fvec<T, 3> m = fvec<T, 3>(T(1)) / ray.direction; // could be precomputed if traversing a set of aligned boxes
25 fvec<T, 3> n = m * ray.origin; // could be precomputed if traversing a set of aligned boxes
26 fvec<T, 3> k = abs(m) * extent;
27 fvec<T, 3> t1 = -n - k;
28 fvec<T, 3> t2 = -n + k;
29 T t_near = std::max(std::max(t1.x(), t1.y()), t1.z());
30 T t_far = std::min(std::min(t2.x(), t2.y()), t2.z());
31
32 if(t_near > t_far || t_far < T(0))
33 return 0;
34
35 out_ts[0] = t_near;
36 out_ts[1] = t_far;
37
38 if(out_normal)
39 *out_normal = -sign(ray.direction)
40 * step(fvec<T, 3>(t1.y(), t1.z(), t1.x()), fvec<T, 3>(t1.x(), t1.y(), t1.z()))
41 * step(fvec<T, 3>(t1.z(), t1.x(), t1.y()), fvec<T, 3>(t1.x(), t1.y(), t1.z()));
42
43 return 2;
44}
45
55template <typename T>
56int ray_box_intersection(const fray<T, 3> &ray, const fvec<T, 3> &min, const fvec<T, 3> &max, fvec<T, 2>& out_ts) {
57 fvec<T, 3> t0 = (min - ray.origin) / ray.direction;
58 fvec<T, 3> t1 = (max - ray.origin) / ray.direction;
59
60 if(t0.x() > t1.x())
61 std::swap(t0.x(), t1.x());
62
63 if(t0.y() > t1.y())
64 std::swap(t0.y(), t1.y());
65
66 if(t0.z() > t1.z())
67 std::swap(t0.z(), t1.z());
68
69 if(t0.x() > t1.y() || t0.y() > t1.x() ||
70 t0.x() > t1.z() || t0.z() > t1.x() ||
71 t0.z() > t1.y() || t0.y() > t1.z())
72 return 0;
73
74 T t_near = std::max(std::max(t0.x(), t0.y()), t0.z());
75 T t_far = std::min(std::min(t1.x(), t1.y()), t1.z());
76
77 if(t_near > t_far)
78 std::swap(t_near, t_far);
79
80 out_ts[0] = t_near;
81 out_ts[1] = t_far;
82
83 return 2;
84}
85
100template <typename T>
101int ray_triangle_intersection(const fray<T, 3>& ray, const fvec<T, 3>& corner0, const fvec<T, 3>& corner1, const fvec<T, 3>& corner2, T& out_t, bool* out_is_backside = nullptr, fvec<T, 3>* out_normal = nullptr, fvec<T, 3>* out_barycentric = nullptr) {
102 // Using Möller–Trumbore algorithm for calculating the intersection between a ray and oriented triangle
103 fvec<T, 3> edge10 = corner1 - corner0;
104 fvec<T, 3> edge20 = corner2 - corner0;
105 fvec<T, 3> normal = cross(edge10, edge20);
106 fvec<T, 3> ao = ray.origin - corner0;
107 fvec<T, 3> dao = cross(ao, ray.direction);
108
109 T determinant = -dot(ray.direction, normal);
110
111 T inverse_determinant = T(1) / determinant;
112
113 // Calculate distance t to triangle and barycentric coordinates of intersection point
114 T t = dot(ao, normal) * inverse_determinant;
115 T u = dot(edge20, dao) * inverse_determinant;
116 T v = -dot(edge10, dao) * inverse_determinant;
117 T w = T(1) - u - v;
118
119 // The sign of the determinant identifies an intersection from the front- (+) or backside (-)
120 if(out_is_backside)
121 *out_is_backside = determinant < T(0);
122 // Make sure the determinant is positive to accept intersections from both sides
123 determinant = std::abs(determinant);
124
125 out_t = t;
126 if(out_normal)
127 *out_normal = normal;
128 if(out_barycentric)
129 *out_barycentric = { w, u, v };
130 bool hit = determinant >= std::numeric_limits<T>::epsilon() && t >= T(0) && u >= T(0) && v >= T(0) && w >= T(0);
131 return hit ? 1 : 0;
132}
133
145template <typename T>
146int ray_cylinder_intersection(const fray<T, 3>& ray, const fvec<T, 3>& position, const fvec<T, 3>& axis, T radius, T& out_t, fvec<T, 3>* out_normal = nullptr) {
147 fvec<T, 3> oc = ray.origin - position;
148 T caca = dot(axis, axis);
149 T card = dot(axis, ray.direction);
150 T caoc = dot(axis, oc);
151 T a = caca - card * card;
152 T b = caca * dot(oc, ray.direction) - caoc * card;
153 T c = caca * dot(oc, oc) - caoc * caoc - radius * radius * caca;
154 T h = b * b - a * c;
155
156 if(h < T(0))
157 return 0;
158
159 h = std::sqrt(h);
160 out_t = (-b - h) / a;
161
162 // body
163 T y = caoc + out_t * card;
164 if(y > T(0) && y < caca) {
165 if(out_normal)
166 *out_normal = (oc + out_t * ray.direction - axis * y / caca) / radius;
167 return 1;
168 }
169
170 // caps
171 out_t = ((y < T(0) ? T(0) : caca) - caoc) / card;
172 if(std::abs(b + a * out_t) < h) {
173 if(out_normal)
174 *out_normal = axis * sign(y) / caca;
175 return 1;
176 }
177
178 return 0;
179}
180
192template <typename T>
193int ray_cylinder_intersection2(const fray<T, 3>& ray, const fvec<T, 3>& start_position, const fvec<T, 3>& end_position, T radius, T& out_t, fvec<T, 3>* out_normal = nullptr) {
194 return ray_cylinder_intersection(ray, start_position, end_position - start_position, radius, out_t, out_normal);
195}
196
206template <typename T>
207int ray_plane_intersection(const fray<T, 3>& ray, const fvec<T, 3>& origin, const fvec<T, 3>& normal, T& out_t) {
208 T denom = dot(normal, ray.direction);
209 if(std::abs(denom) < std::numeric_limits<T>::epsilon())
210 return 0;
211
212 out_t = dot(origin - ray.origin, normal) / denom;
213 return 1;
214};
215
227template <typename T>
228int ray_axis_aligned_rectangle_intersection(const fray<T, 3>& ray, const fvec<T, 3>& position, const fvec<T, 2>& extent, int axis_index, T& out_t, fvec<T, 2>* out_uv = nullptr) {
229 assert(axis_index >= 0 && axis_index < 3);
230
231 fvec<T, 3> normal = { T(0) };
232 normal[axis_index] = T(1);
233
234 T t = std::numeric_limits<T>::max();
235 if(ray_plane_intersection(ray, position, normal, t)) {
236 fvec<T, 3> intersection_position = ray.position(t);
237 intersection_position -= position;
238
239 vec2 uv;
240 switch(axis_index) {
241 case 0:
242 uv[0] = intersection_position[1];
243 uv[1] = intersection_position[2];
244 break;
245 case 1:
246 uv[0] = intersection_position[0];
247 uv[1] = intersection_position[2];
248 break;
249 case 2:
250 uv[0] = intersection_position[0];
251 uv[1] = intersection_position[1];
252 break;
253 default:
254 return 0;
255 }
256
257 uv += T(0.5) * extent;
258
259 if(uv[0] >= T(0) && uv[0] <= extent.x() && uv[1] >= T(0) && uv[1] <= extent.y()) {
260 out_t = t;
261 if(out_uv)
262 *out_uv = uv / extent;
263 return 1;
264 }
265 }
266
267 return 0;
268}
269
282template <typename T>
283int ray_parallelogram_intersection(const fray<T, 3>& ray, const fvec<T, 3>& origin, const fvec<T, 3> edge_u, const fvec<T, 3>& edge_v, T& out_t, fvec<T, 3>* out_normal = nullptr, fvec<T, 2>* out_uv = nullptr) {
284 fvec<T, 3> normal = normalize(cross(edge_u, edge_v));
285
286 T sf = T(0);
287 int ku = 0;
288 int kv = 1;
289
290 // decide on best projection plane based on projected surface area
291 //area in xy plane
292 T axy = edge_u.x() * edge_u.x() + edge_u.y() * edge_u.y();
293 axy *= edge_v.x() * edge_v.x() + edge_v.y() * edge_v.y();
294
295 //area in xz plane
296 T axz = edge_u.x() * edge_u.x() + edge_u.z() * edge_u.z();
297 axz *= edge_v.x() * edge_v.x() + edge_v.z() * edge_v.z();
298
299 //area in yz plane
300 T ayz = edge_u.y() * edge_u.y() + edge_u.z() * edge_u.z();
301 ayz *= edge_v.y() * edge_v.y() + edge_v.z() * edge_v.z();
302
303 if(axy > axz) {
304 if(axy > ayz) {
305 //xy
306 ku = 0;
307 kv = 1;
308 sf = normal.z() < T(0) ? T(1) : -T(1);
309 } else {
310 //yz
311 ku = 1;
312 kv = 2;
313 sf = normal.x() < T(0) ? T(1) : -T(1);
314 }
315 } else {
316 if(axz > ayz) {
317 //xz
318 ku = 0;
319 kv = 2;
320 sf = normal.y() < T(0) ? -T(1) : T(1);
321 } else {
322 //yz
323 ku = 1;
324 kv = 2;
325 sf = normal.x() < T(0) ? T(1) : -T(1);
326 }
327 }
328
329 T ndd = dot(normal, ray.direction);
330 if(std::abs(ndd) < std::numeric_limits<T>::epsilon())
331 return 0;
332
333 T t = dot(normal, origin - ray.origin) / ndd;
334
335 //ray intersects plane
336 //now test if hitpoint is inside parallelogram
337 fvec<T, 3> x = ray.position(t);
338 fvec<T, 2> x2d(x[ku] - origin[ku], x[kv] - origin[kv]);
339
340 fvec<T, 2> e1(edge_u[ku], edge_u[kv]);
341 fvec<T, 2> e2(edge_v[ku], edge_v[kv]);
342
343 T s = e1.x() * x2d.y() - e1.y() * x2d.x();
344 if(sf * s > -std::numeric_limits<T>::epsilon())
345 return 0;
346
347 s = e2.x() * x2d.y() - e2.y() * x2d.x();
348 if(sf * s < std::numeric_limits<T>::epsilon())
349 return 0;
350
351 x2d -= (e1 + e2);
352
353 s = e1.y() * x2d.x() - e1.x() * x2d.y();
354 if(sf * s > -std::numeric_limits<T>::epsilon())
355 return 0;
356
357 s = e2.y() * x2d.x() - e2.x() * x2d.y();
358 if(sf * s < std::numeric_limits<T>::epsilon())
359 return 0;
360
361 out_t = t;
362
363 if(out_normal)
364 *out_normal = normal;
365
366 if(out_uv) {
367 fvec<T, 2> uv = x2d;
368 uv.x() /= length(e1);
369 uv.y() /= length(e2);
370 *out_uv = uv;
371 }
372
373 return 1;
374};
375
388template <typename T>
389int ray_rectangle_intersection(const fray<T, 3>& ray, const fvec<T, 3>& position, const fvec<T, 2> extent, const quaternion<T>& rotation, T& out_t, fvec<T, 3>* out_normal = nullptr, fvec<T, 2>* out_uv = nullptr) {
390 // define tangent and bitangent assuming the normal is (0, 1, 0) without rotation
391 fvec<T, 3> tangent = { T(1), T(0), T(0) };
392 fvec<T, 3> bitangent = { T(0), T(1), T(0) };
393
394 tangent = rotation.apply(tangent);
395 bitangent = rotation.apply(bitangent);
396
397 fvec<T, 3> corner = position - T(0.5) * extent.x() * tangent - T(0.5) * extent.y() * bitangent;
398
399 fvec<T, 3> edge_u = extent.x() * tangent;
400 fvec<T, 3> edge_v = extent.y() * bitangent;
401
402 return ray_parallelogram_intersection(ray, corner, edge_u, edge_v, out_t, out_normal, out_uv);
403};
404
414template <typename T>
415int ray_sphere_intersection(const fray<T, 3>& ray, const fvec<T, 3>& center, T radius, fvec<T, 2>& out_ts) {
416 fvec<T, 3> d = ray.origin - center;
417 T il = T(1) / dot(ray.direction, ray.direction);
418 T b = il * dot(d, ray.direction);
419 T c = il * (dot(d, d) - radius * radius);
420 T D = b * b - c;
421
422 if(D < T(0))
423 return 0;
424
425 if(D < std::numeric_limits<T>::epsilon()) {
426 out_ts = -b;
427 return 1;
428 }
429
430 D = std::sqrt(D);
431 out_ts[0] = -b - D;
432 out_ts[1] = -b + D;
433
434 return 2;
435}
436
447template <typename T>
448int first_ray_sphere_intersection(const fray<T, 3>& ray, const fvec<T, 3>& center, T radius, T& out_t, fvec<T, 3>* out_normal = nullptr) {
449 fvec<T, 2> ts;
450 int k = ray_sphere_intersection(ray, center, radius, ts);
451
452 if(k == 1 || (k == 2 && ts[0] > T(0)))
453 out_t = ts[0];
454 else if(k == 2 && ts[1] > T(0))
455 out_t = ts[1];
456 else
457 return 0;
458
459 if(out_normal)
460 *out_normal = normalize(ray.position(out_t) - center);
461
462 return 1;
463}
464
475template <typename T>
476int ray_torus_intersection(const fray<T, 3>& ray, T large_radius, T small_radius, T& out_t, fvec<T, 3>* out_normal = nullptr) {
477 T po = T(1);
478 T Ra2 = large_radius * large_radius;
479 T ra2 = small_radius * small_radius;
480 T m = dot(ray.origin, ray.origin);
481 T n = dot(ray.origin, ray.direction);
482 T k = (m + Ra2 - ra2) / T(2);
483 T k3 = n;
484 const fvec<T, 2>& ro_xy = reinterpret_cast<const fvec<T, 2>&>(ray.origin);
485 const fvec<T, 2>& rd_xy = reinterpret_cast<const fvec<T, 2>&>(ray.direction);
486 T k2 = n * n - Ra2 * dot(rd_xy, rd_xy) + k;
487 T k1 = n * k - Ra2 * dot(rd_xy, ro_xy);
488 T k0 = k * k - Ra2 * dot(ro_xy, ro_xy);
489
490 if(std::abs(k3 * (k3 * k3 - k2) + k1) < T(0.01)) {
491 po = T(-1);
492 T tmp = k1; k1 = k3; k3 = tmp;
493 k0 = T(1) / k0;
494 k1 = k1 * k0;
495 k2 = k2 * k0;
496 k3 = k3 * k0;
497 }
498
499 T c2 = k2 * T(2) - T(3) * k3 * k3;
500 T c1 = k3 * (k3 * k3 - k2) + k1;
501 T c0 = k3 * (k3 * (c2 + T(2) * k2) - T(8) * k1) + T(4) * k0;
502 c2 /= T(3);
503 c1 *= T(2);
504 c0 /= T(3);
505 T Q = c2 * c2 + c0;
506 T R = c2 * c2 * c2 - T(3) * c2 * c0 + c1 * c1;
507 T h = R * R - Q * Q * Q;
508
509 if(h >= T(0)) {
510 h = std::sqrt(h);
511 T v = sign(R + h) * std::pow(std::abs(R + h), T(1) / T(3)); // cube root
512 T u = sign(R - h) * std::pow(std::abs(R - h), T(1) / T(3)); // cube root
513 fvec<T, 2> s = fvec<T, 2>((v + u) + T(4) * c2, (v - u) * std::sqrt(T(3)));
514 T y = std::sqrt(T(0.5) * (length(s) + s.x()));
515 T x = T(0.5) * s.y() / y;
516 T r = T(2) * c1 / (x * x + y * y);
517 T t1 = x - r - k3; t1 = (po < T(0)) ? T(2) / t1 : t1;
518 T t2 = -x - r - k3; t2 = (po < T(0)) ? T(2) / t2 : t2;
519
520 if(t1 > T(0)) out_t = t1;
521 if(t2 > T(0)) out_t = std::min(out_t, t2);
522
523 if(out_normal) {
524 fvec<T, 3> pos = ray.position(out_t);
525 *out_normal = normalize(pos * ((dot(pos, pos) - ra2) * fvec<T, 3>(T(1)) - Ra2 * fvec<T, 3>(T(1), T(1), T(-1))));
526 }
527
528 return 1; // 2
529 }
530
531 T sQ = std::sqrt(Q);
532 T w = sQ * cos(acos(-R / (sQ * Q)) / T(3));
533 T d2 = -(w + c2);
534
535 if (d2 < T(0))
536 return 0;
537
538 T d1 = std::sqrt(d2);
539 T h1 = std::sqrt(w - T(2) * c2 + c1 / d1);
540 T h2 = std::sqrt(w - T(2) * c2 - c1 / d1);
541 T t1 = -d1 - h1 - k3; t1 = (po < T(0)) ? T(2) / t1 : t1;
542 T t2 = -d1 + h1 - k3; t2 = (po < T(0)) ? T(2) / t2 : t2;
543 T t3 = d1 - h2 - k3; t3 = (po < T(0)) ? T(2) / t3 : t3;
544 T t4 = d1 + h2 - k3; t4 = (po < T(0)) ? T(2) / t4 : t4;
545
546 if (t1 > T(0)) out_t = t1;
547 if (t2 > T(0)) out_t = std::min(out_t, t2);
548 if (t3 > T(0)) out_t = std::min(out_t, t3);
549 if (t4 > T(0)) out_t = std::min(out_t, t4);
550
551 if(out_normal) {
552 fvec<T, 3> pos = ray.position(out_t);
553 *out_normal = normalize(pos * ((dot(pos, pos) - ra2) * fvec<T, 3>(T(1)) - Ra2 * fvec<T, 3>(T(1), T(1), T(-1))));
554 }
555
556 return 1; // 4
557}
558
571template <typename T>
572int ray_torus_intersection(const fray<T, 3>& ray, const fvec<T, 3>& center, const fvec<T, 3>& normal, T large_radius, T small_radius, T& out_t, fvec<T, 3>* out_normal = nullptr) {
573 // compute pose transformation
574 fmat<T, 3, 4> pose;
575 pose_position(pose) = center;
576 fvec<T, 3>& x = reinterpret_cast<fvec<T, 3>&>(pose[0]);
577 fvec<T, 3>& y = reinterpret_cast<fvec<T, 3>&>(pose[3]);
578 fvec<T, 3>& z = reinterpret_cast<fvec<T, 3>&>(pose[6]);
579 z = normal;
580 x = normal;
581 int i = std::abs(normal[0]) < std::abs(normal[1]) ? 0 : 1;
582 i = std::abs(normal[i]) < std::abs(normal[2]) ? i : 2;
583 x[i] = T(1);
584 y = normalize(cross(normal, x));
585 x = cross(y, normal);
586
587 fray<T, 3> transformed_ray;
588 transformed_ray.origin = inverse_pose_transform_point(pose, ray.origin);
589 transformed_ray.direction = inverse_pose_transform_vector(pose, ray.direction);
590
591 // transform ray into torus pose
592 int res = ray_torus_intersection(transformed_ray, large_radius, small_radius, out_t, out_normal);
593
594 // in case of intersection, transform normal back to world space
595 if(res)
596 *out_normal = pose_transform_vector(pose, *out_normal);
597
598 return res;
599}
600
601} // namespace math
602} // namespace cgv
this header is dependency free
Definition print.h:11
cgv::math::fvec< float, 2 > vec2
declare type of 2d single precision floating point vectors
Definition fvec.h:692
helper functions to work with poses that can be represented with 3x4 matrix or quaternion plus vector
fvec< T, 3 > pose_transform_vector(const fmat< T, 3, 4 > &pose, const fvec< T, 3 > &v)
transform vector with pose matrix
Definition pose.h:29
fvec< T, 3 > & pose_position(fmat< T, 3, 4 > &pose)
extract position vector from pose matrix
Definition pose.h:18
fvec< T, 3 > inverse_pose_transform_vector(const fmat< T, 3, 4 > &pose, const fvec< T, 3 > &v)
transform vector with inverse of pose matrix
Definition pose.h:35
fvec< T, 3 > inverse_pose_transform_point(const fmat< T, 3, 4 > &pose, const fvec< T, 3 > &p)
transform point with inverse of pose matrix
Definition pose.h:32