|
| 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 | +} |
0 commit comments