Skip to content

Commit 972e099

Browse files
committed
Add Intersect trait and ray/plane and ray/bbox intersection tests
Implements the "slab method" described in https://en.wikipedia.org/wiki/Slab_method
1 parent d8eea75 commit 972e099

File tree

2 files changed

+305
-0
lines changed

2 files changed

+305
-0
lines changed

geom/src/isect.rs

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
use core::fmt::Debug;
2+
3+
use retrofire_core::{
4+
geom::{Plane3, Ray},
5+
math::{ApproxEq, Point3, vec3},
6+
render::scene::BBox,
7+
};
8+
9+
/// Trait for calculating whether and at which points two objects intersect.
10+
pub trait Intersect<T> {
11+
/// The result of an intersection test.
12+
type Result;
13+
14+
/// Finds the point(s) where `self` and another object intersect, if any.
15+
///
16+
/// It is implementation defined whether this method returns all the
17+
/// intersection points or, for instance, only the closest one.
18+
fn intersect(&self, other: &T) -> Self::Result;
19+
}
20+
21+
impl<B> Intersect<Plane3<B>> for Ray<Point3<B>> {
22+
type Result = Option<(f32, Point3<B>)>;
23+
24+
/// Returns the unique intersection point of `self` and a plane,
25+
/// or `None` if they do not intersect.
26+
///
27+
/// If an intersection point exists, returns `Some((t, point))`, where
28+
/// `point` is the intersection point and `t` is the ray parameter value
29+
/// such that `self.orig + t * self.dir == point`.
30+
fn intersect(&self, p: &Plane3<B>) -> Self::Result {
31+
let Self(orig, dir) = self;
32+
33+
// TODO checking two very unlikely conditions
34+
35+
let num = p.signed_dist(*orig);
36+
if num.approx_eq(&0.0) {
37+
// Origin point coincident with the plane
38+
return Some((0.0, *orig));
39+
}
40+
let denom = dir.dot(&p.normal().to());
41+
if denom.approx_eq(&0.0) {
42+
// Ray parallel with but not coincident with the plane
43+
// (or dir is a zero vector) -> no intersection
44+
return None;
45+
}
46+
let t = -num / denom;
47+
if t.approx_le(&0.0) {
48+
// Ray points away from the plane, intersection "behind" it
49+
return None;
50+
}
51+
Some((t, *orig + t * *dir))
52+
}
53+
}
54+
55+
impl<B: Debug + Default> Intersect<BBox<B>> for Ray<Point3<B>> {
56+
type Result = Option<(f32, Point3<B>)>; // Only closest for now
57+
58+
/// Returns the nearest intersection point of `self` and a box,
59+
/// or `None` if they do not intersect.
60+
///
61+
/// If an intersection point exists, returns `Some((t, point))`, where
62+
/// `point` is the intersection point and `t` is the ray parameter value
63+
/// such that `self.orig + t * self.dir == point`.
64+
fn intersect(&self, bbox: &BBox<B>) -> Self::Result {
65+
let &BBox(low, upp) = bbox;
66+
let Ray(orig, dir) = *self;
67+
68+
// Ray equation:
69+
// p(t) = O + d·t
70+
// x(t) = Ox + dx·t
71+
// y(t) = Oy + dy·t
72+
// z(t) = Oz + dz·t
73+
//
74+
// Plane equations:
75+
// x = lx, x = ux
76+
// y = ly, x = uy
77+
// z = lz, x = uz
78+
//
79+
// For each slab, ie. pair of parallel planes:
80+
// Substitute eg.
81+
// x_l = Ox + x_d·t0
82+
// x_u = Ox + x_d·t1
83+
//
84+
// t0 = (x_l - x_O) / x_d | x_d=0 iff ray parallel with planes
85+
// t1 = (x_u - x_O) / x_d
86+
//
87+
// Same for y and z slabs
88+
89+
if bbox.is_empty() {
90+
return None;
91+
}
92+
93+
let r_d = vec3(1.0 / dir.x(), 1.0 / dir.y(), 1.0 / dir.z());
94+
let low = (low - orig) * r_d;
95+
let upp = (upp - orig) * r_d;
96+
97+
let near = low.zip_map(upp, |l, u| l.min(u));
98+
let far = low.zip_map(upp, |l, u| l.max(u));
99+
100+
let near_t = near[0].max(near[1]).max(near[2]);
101+
let far_t = far[0].min(far[1]).min(far[2]);
102+
103+
if far_t.is_infinite() {
104+
return None;
105+
}
106+
107+
if near_t > far_t || far_t < 0.0 {
108+
// ---max---min--- (misses the box) or
109+
// ---min---max---0---> (box behind ray)
110+
return None;
111+
}
112+
let t = if near_t >= 0.0 {
113+
// ---0---min---max---> (hits box)
114+
near_t
115+
} else {
116+
// ---min---0---max---> (inside the box, unlikely)
117+
far_t
118+
};
119+
Some((t, orig + t * dir))
120+
}
121+
}
122+
123+
#[cfg(test)]
124+
mod tests {
125+
use retrofire_core::math::{Linear, Vec3, pt3};
126+
127+
use super::*;
128+
129+
mod ray_plane {
130+
use super::*;
131+
132+
const PLANE: Plane3<()> = Plane3::new(0.0, 1.0, 0.0, 2.0);
133+
134+
#[test]
135+
#[ignore]
136+
fn ray_plane_xxx() {
137+
let r = Ray::<Point3>(
138+
pt3(-3.308549, 6.2584567, -3.351655),
139+
vec3(3.308549, -6.2584567, 3.351655),
140+
);
141+
let bbox = BBox(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0));
142+
143+
assert_eq!(r.intersect(&bbox), Some((0.0, pt3(0.0, 0.0, 0.0))));
144+
}
145+
146+
#[test]
147+
fn ray_towards_plane_has_intersection() {
148+
// Outside
149+
let r = Ray(pt3(0.0, 3.0, 0.0), vec3(1.0, -1.0, 1.0));
150+
assert_eq!(r.intersect(&PLANE), Some((1.0, pt3(1.0, 2.0, 1.0))));
151+
152+
// Inside
153+
let r = Ray(pt3(0.0, 1.0, 0.0), vec3(1.0, 1.0, 1.0));
154+
assert_eq!(r.intersect(&PLANE), Some((1.0, pt3(1.0, 2.0, 1.0))));
155+
}
156+
#[test]
157+
fn ray_origin_on_plane_has_intersection() {
158+
let r = Ray(pt3(0.0, 2.0, 0.0), vec3(1.0, -1.0, 1.0));
159+
assert_eq!(r.intersect(&PLANE), Some((0.0, pt3(0.0, 2.0, 0.0))));
160+
}
161+
#[test]
162+
fn ray_coincident_with_plane_has_intersection() {
163+
let r = Ray(pt3(0.0, 2.0, 0.0), vec3(1.0, 0.0, 1.0));
164+
assert_eq!(r.intersect(&PLANE), Some((0.0, pt3(0.0, 2.0, 0.0))));
165+
}
166+
#[test]
167+
fn ray_parallel_with_plane_no_intersection() {
168+
let r = Ray(pt3(0.0, 3.0, 0.0), vec3(1.0, 0.0, 1.0));
169+
assert_eq!(r.intersect(&PLANE), None);
170+
}
171+
#[test]
172+
fn ray_points_away_from_plane_no_intersection() {
173+
// Outside
174+
let r = Ray(pt3(0.0, 3.0, 0.0), vec3(1.0, 1.0, 1.0));
175+
assert_eq!(r.intersect(&PLANE), None);
176+
177+
// Inside
178+
let r = Ray(pt3(0.0, 1.0, 0.0), vec3(1.0, -1.0, 1.0));
179+
assert_eq!(r.intersect(&PLANE), None);
180+
}
181+
#[test]
182+
fn degenerate_ray_only_intersects_if_coincident() {
183+
let r = Ray(pt3(0.0, 3.0, 0.0), Vec3::zero());
184+
assert_eq!(r.intersect(&PLANE), None);
185+
186+
let r = Ray(pt3(0.0, 1.0, 0.0), Vec3::zero());
187+
assert_eq!(r.intersect(&PLANE), None);
188+
189+
let r = Ray(pt3(0.0, 2.0, 0.0), Vec3::zero());
190+
assert_eq!(r.intersect(&PLANE), Some((0.0, pt3(0.0, 2.0, 0.0))));
191+
}
192+
}
193+
194+
mod ray_bbox {
195+
use super::*;
196+
197+
const BBOX: BBox<()> = BBox(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0));
198+
199+
#[test]
200+
fn parallel_simple() {
201+
// +----+
202+
// x--> | |
203+
// +----+
204+
let ray = Ray(pt3(0.0, 0.0, -2.0), vec3(0.0, 0.0, 1.0));
205+
assert_eq!(ray.intersect(&BBOX), Some((1.0, pt3(0.0, 0.0, -1.0))));
206+
}
207+
#[test]
208+
fn diagonal_simple() {
209+
//
210+
// +----+
211+
// ^| |
212+
// / +----+
213+
// x
214+
let ray = Ray(pt3(-1.5, 0.0, -2.0), vec3(1.0, 0.0, 1.0));
215+
assert_eq!(ray.intersect(&BBOX), Some((1.0, pt3(-0.5, 0.0, -1.0))));
216+
}
217+
#[test]
218+
#[ignore]
219+
fn parallel_intersect_at_vertex() {
220+
// x--> ,_____.
221+
// / /|
222+
// /_____/ |
223+
// | | /
224+
// |_____|/
225+
let ray = Ray(pt3(1.0, 1.0, -2.0), vec3(0.0, 0.0, 1.0));
226+
assert_eq!(ray.intersect(&BBOX), Some((1.0, pt3(1.0, 1.0, -1.0))));
227+
}
228+
#[test]
229+
#[ignore]
230+
fn parallel_intersect_at_edge() {
231+
// ,_____.
232+
// x--> / /|
233+
// /_____/ |
234+
// | | /
235+
// |_____|/
236+
//
237+
let ray = Ray(pt3(0.0, 1.0, -2.0), vec3(0.0, 0.0, 1.0));
238+
assert_eq!(ray.intersect(&BBOX), Some((1.0, pt3(0.0, 1.0, -1.0))));
239+
}
240+
#[test]
241+
fn ray_starts_inside() {
242+
// +--^----+
243+
// | | |
244+
// | x |
245+
// +-------+
246+
let ray = Ray(pt3(0.0, 0.0, -0.5), vec3(0.0, 1.0, 0.0));
247+
assert_eq!(ray.intersect(&BBOX), Some((1.0, pt3(0.0, 1.0, -0.5))));
248+
}
249+
#[test]
250+
fn ray_starts_on_side_plane() {
251+
// Points away
252+
// +-----+
253+
// <--x |
254+
// +-----+
255+
let ray = Ray(pt3(0.0, 0.0, -1.0), vec3(0.0, 0.0, -1.0));
256+
assert_eq!(ray.intersect(&BBOX), Some((0.0, pt3(0.0, 0.0, -1.0))));
257+
// Points inside
258+
// +-----+
259+
// x--> |
260+
// +-----+
261+
let ray = Ray(pt3(0.0, 0.0, -1.0), vec3(0.0, 0.0, 1.0));
262+
assert_eq!(ray.intersect(&BBOX), Some((0.0, pt3(0.0, 0.0, -1.0))));
263+
}
264+
#[test]
265+
fn no_intersection() {
266+
// Diagonal ray
267+
// ^
268+
// / +----+
269+
// x | |
270+
// +----+
271+
let ray = Ray(pt3(0.0, 0.0, -2.5), vec3(0.0, 1.0, 1.0));
272+
assert_eq!(ray.intersect(&BBOX), None);
273+
274+
// Parallel but offset ray
275+
// x--->
276+
// +----+
277+
// | |
278+
// +----+
279+
let ray = Ray(pt3(0.0, 1.5, -2.0), vec3(0.0, 0.0, 1.0));
280+
assert_eq!(ray.intersect(&BBOX), None);
281+
}
282+
#[test]
283+
fn opposite_direction() {
284+
// +----+
285+
// | | x--->
286+
// +----+
287+
let ray = Ray(pt3(0.0, 0.0, 2.0), vec3(0.0, 0.0, 1.0));
288+
assert_eq!(ray.intersect(&BBOX), None);
289+
}
290+
#[test]
291+
fn zero_length_ray() {
292+
let ray = Ray(pt3(0.0, 0.0, -2.0), vec3(0.0, 0.0, 0.0));
293+
assert_eq!(ray.intersect(&BBOX), None);
294+
}
295+
#[test]
296+
fn empty_box() {
297+
let empty = BBox::<()>(pt3(-1.0, -1.0, 1.0), pt3(1.0, 1.0, -1.0));
298+
let ray = Ray(pt3(0.0, 0.0, -2.0), vec3(0.0, 0.0, 1.0));
299+
assert_eq!(ray.intersect(&empty), None);
300+
}
301+
}
302+
}

geom/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ extern crate core;
66
extern crate std;
77

88
pub mod io;
9+
pub mod isect;
910
pub mod solids;
11+
12+
pub use isect::Intersect;

0 commit comments

Comments
 (0)