Skip to content

Commit a1b05e4

Browse files
committed
Add intersection tests for several 2D types
* Line-line * Ray-line * Ray-edge * Edge-edge These are all in the same "family", just different constraints.
1 parent aa46e65 commit a1b05e4

File tree

2 files changed

+262
-8
lines changed

2 files changed

+262
-8
lines changed

core/src/geom/prim.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub type Plane3<B = ()> = Plane<Vector<[f32; 4], Hom<3, B>>>;
4545
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
4646
pub struct Ray<T: Affine>(pub T, pub T::Diff);
4747

48+
pub type Ray2<B = ()> = Ray<Point2<B>>;
4849
pub type Ray3<B = ()> = Ray<Point3<B>>;
4950

5051
/// A curve composed of a chain of line segments.
@@ -1094,7 +1095,7 @@ mod tests {
10941095
assert_eq!(format!("{:?}", l), "Line(x = 2)");
10951096

10961097
l = Line2::new(0.0, 1.0, 0.0); // y = 0
1097-
//assert_eq!(format!("{:?}", l), "Line(y = 0)");
1098+
assert_eq!(format!("{:?}", l), "Line(y = 0)");
10981099

10991100
l = Line2::from_points(pt2(0.0, -3.0), pt2(1.0, -3.0)); // y = -3
11001101
assert_eq!(l.slope_intercept(), Some((0.0, -3.0)));

geom/src/isect.rs

Lines changed: 260 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
use core::fmt::Debug;
1+
use core::fmt::{Debug, Formatter};
22

3-
#[cfg(feature = "std")]
4-
use retrofire_core::geom::Sphere;
53
use retrofire_core::{
6-
geom::{Plane3, Ray, Ray3},
7-
math::{Point3, vec3},
4+
geom::{Edge, Line2, Plane3, Ray, Ray2, Ray3},
5+
mat,
6+
math::{ApproxEq, Mat2, Point2, Point3, pt2, vec3},
87
render::scene::BBox,
98
};
109

11-
/// Trait for calculating whether and at which points two objects intersect.
10+
#[cfg(feature = "std")]
11+
use retrofire_core::geom::Sphere;
12+
13+
/// Trait for finding intersection points of geometric objects.
1214
pub trait Intersect<T> {
1315
/// The result of an intersection test.
1416
type Result;
@@ -20,7 +22,48 @@ pub trait Intersect<T> {
2022
fn intersect(&self, other: &T) -> Self::Result;
2123
}
2224

23-
type RayIntersect3<B> = Option<(f32, Point3<B>)>;
25+
pub type RayIntersect3<B> = Option<(f32, Point3<B>)>;
26+
pub type RayIntersect2<B> = Option<(f32, Point2<B>)>;
27+
28+
#[derive(Copy, Clone, PartialEq)]
29+
pub enum LineIntersect<B> {
30+
/// Unique intersection point.
31+
Point(Point2<B>),
32+
/// Line is coincident with the intersecting object.
33+
Coincident,
34+
}
35+
36+
//
37+
// Inherent impls
38+
//
39+
40+
impl<B> LineIntersect<B> {
41+
/// Returns the intersection point if `self` is a `LineIntersect::Point`,
42+
/// `None` otherwise.
43+
pub fn point(&self) -> Option<Point2<B>> {
44+
match self {
45+
LineIntersect::Point(p) => Some(*p),
46+
LineIntersect::Coincident => None,
47+
}
48+
}
49+
}
50+
51+
//
52+
// Trait impls
53+
//
54+
55+
impl<B: Debug + Default> Debug for LineIntersect<B> {
56+
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
57+
match self {
58+
Self::Point(p) => write!(f, "Point({:?})", p),
59+
Self::Coincident => f.write_str("Coincident"),
60+
}
61+
}
62+
}
63+
64+
//
65+
// 3D Intersect impls
66+
//
2467

2568
impl<B> Intersect<Plane3<B>> for Ray3<B> {
2669
type Result = RayIntersect3<B>;
@@ -243,6 +286,215 @@ impl<B> Intersect<Sphere<B>> for Ray3<B> {
243286
}
244287
}
245288

289+
//
290+
// 2D intersection
291+
//
292+
293+
impl<B> Intersect<Self> for Line2<B> {
294+
type Result = Option<LineIntersect<B>>;
295+
296+
/// Computes the intersection point of `self` and another 2-line.
297+
///
298+
/// If the lines are parallel but not coincident, returns `None`. Otherwise,
299+
/// if the lines are coincident, returns `Some(LineIntersect::Coincident)`.
300+
/// Otherwise, returns `Some(LineIntersect::Point(p))`, where `p` is the
301+
/// unique intersection point.
302+
///
303+
/// # Examples
304+
/// ```
305+
/// use retrofire_core::{
306+
/// assert_approx_eq, geom::{Ray, Line2}, math::{pt2, vec2},
307+
/// };
308+
/// use retrofire_geom::{Intersect, isect::LineIntersect::*};
309+
///
310+
/// let horiz = Line2::<()>::from(Ray(pt2(0.0, 2.0), vec2(1.0, 0.0)));
311+
/// let vert = Line2::<()>::from(Ray(pt2(3.0, 0.0), vec2(0.0, 1.0)));
312+
/// assert_eq!(horiz.intersect(&vert), Some(Point(pt2(3.0, 2.0))));
313+
///
314+
/// let horiz2 = Line2::<()>::from(Ray(pt2(0.0, 3.0), vec2(1.0, 0.0)));
315+
/// assert_eq!(horiz.intersect(&horiz2), None);
316+
///
317+
/// assert_eq!(horiz.intersect(&horiz), Some(Coincident));
318+
///
319+
///
320+
/// ```
321+
fn intersect(&self, other: &Self) -> Self::Result {
322+
let [a, b, c] = self.coeffs();
323+
let [d, e, f] = other.coeffs();
324+
325+
// Solve the system of equations for x and y:
326+
// ax + by = c // self
327+
// dx + ey = f // other
328+
//
329+
// Write in matrix form and solve:
330+
// (a b) (x) = (c)
331+
// (d e) (y) (f)
332+
//
333+
// -1
334+
// (x) = (a b) (c)
335+
// (y) (d e) (f)
336+
let abde: Mat2<B> = mat![
337+
a, b;
338+
d, e;
339+
];
340+
match abde.checked_inverse() {
341+
Some(inv) => {
342+
let res = inv.apply(&pt2(c, f));
343+
Some(LineIntersect::Point(res))
344+
}
345+
None if [a, b, c].approx_eq(&[d, e, f]) => {
346+
Some(LineIntersect::Coincident)
347+
}
348+
None => None,
349+
}
350+
}
351+
}
352+
353+
impl<B> Intersect<Line2<B>> for Ray2<B> {
354+
type Result = RayIntersect2<B>;
355+
356+
// Returns the intersection point of self and a line.
357+
fn intersect(&self, line: &Line2<B>) -> Self::Result {
358+
// TODO if degenerate ray, could return if ray origin on edge
359+
360+
// First find intersection of lines
361+
let ray_line = Line2::from(*self);
362+
let isect = ray_line.intersect(line)?;
363+
364+
let Self(orig, dir) = self;
365+
match isect {
366+
LineIntersect::Point(pt) => {
367+
// Check if the point lies in the correct half-line
368+
let t = (pt - *orig).dot(dir);
369+
(t >= 0.0).then_some((t / dir.len_sqr(), pt))
370+
}
371+
LineIntersect::Coincident => {
372+
// Ray lies on the line, the closest common point
373+
// is simply the origin point
374+
Some((0.0, *orig))
375+
}
376+
}
377+
}
378+
}
379+
380+
impl<B> Intersect<Edge<Point2<B>>> for Ray2<B> {
381+
type Result = RayIntersect2<B>;
382+
383+
/// Returns the intersection of `self` and an edge, or `None` if there is
384+
/// no intersection point.
385+
///
386+
/// # Examples
387+
/// ```
388+
/// use retrofire_core::{
389+
/// assert_approx_eq, geom::{Edge, Ray}, math::{pt2, vec2, Point2},
390+
/// };
391+
/// use retrofire_geom::isect::Intersect;
392+
///
393+
/// // ^
394+
/// // 3 O
395+
/// // | \
396+
/// // | v
397+
/// // E==1=======X==E
398+
/// // |
399+
/// // <----+-------------->
400+
/// let ray: Ray<Point2> = Ray(pt2(2.0, 3.0), vec2(1.0, -2.0));
401+
/// let edge = Edge(pt2(-1.0, 1.0), pt2(4.0, 1.0));
402+
///
403+
/// let (t, point) = ray.intersect(&edge).unwrap();
404+
/// assert_eq!(t, 1.0);
405+
/// assert_approx_eq!(point, pt2(3.0, 1.0));
406+
///
407+
/// // O
408+
/// // \
409+
/// // E=====E v
410+
/// //
411+
/// let edge = Edge(pt2(-1.0, 1.0), pt2(2.0, 1.0));
412+
/// assert_eq!(ray.intersect(&edge), None);
413+
/// ```
414+
// // TODO check that these are handled by actual unit tests
415+
// // E=====E <---O
416+
// let ray: Ray<Point2> = Ray(pt2(4.0, 1.0), vec2(-1.0, 0.0));
417+
// assert_eq!(ray.intersect(&edge), Some((2.0, pt2(2.0, 1.0))));
418+
//
419+
// // E==O--E-->
420+
// let ray: Ray<Point2> = Ray(pt2(1.0, 1.0), vec2(1.0, 0.0));
421+
// assert_eq!(ray.intersect(&edge), Some((0.0, pt2(1.0, 1.0))));
422+
//
423+
// // E=====E O--->
424+
// let ray: Ray<Point2> = Ray(pt2(4.0, 1.0), vec2(1.0, 0.0));
425+
// assert_eq!(ray.intersect(&edge), None);
426+
fn intersect(&self, edge: &Edge<Point2<B>>) -> Self::Result {
427+
// Compute ray-line intersection
428+
let (t, pt) = self.intersect(&Line2::from(*edge))?;
429+
430+
// Ray intersects the line of edge, but still have to check
431+
// whether the point is between edge endpoints
432+
let e01 = edge.1 - edge.0;
433+
let u = (pt - edge.0).dot(&e01);
434+
if u.approx_in(0.0..e01.len_sqr()) {
435+
return Some((t, pt));
436+
}
437+
438+
// If ray is coincident with the edge, ray-line gives ray.0 as the
439+
// intersection point. If ray.0 is outside the edge, there are two cases:
440+
// e-----e r--->
441+
// where there is no intersection, and
442+
// e-----e <---r
443+
// where the intersection point is the closest edge endpoint.
444+
if !self.0.approx_eq(&pt) {
445+
return None;
446+
}
447+
let t0 = (edge.0 - pt).dot(&self.1);
448+
if t0.approx_le(&0.0) {
449+
return None;
450+
}
451+
let t1 = (edge.1 - pt).dot(&self.1);
452+
if t0 <= t1 {
453+
Some((t0 / self.1.len_sqr(), edge.0))
454+
} else {
455+
Some((t1 / self.1.len_sqr(), edge.1))
456+
}
457+
}
458+
}
459+
460+
impl<B> Intersect<Self> for Edge<Point2<B>> {
461+
type Result = Option<Point2<B>>;
462+
463+
/// Returns the intersection point of `self` and another edge, or `None`
464+
/// if the edges do not intersect.
465+
///
466+
/// # Examples
467+
/// ```
468+
/// use retrofire_core::{
469+
/// assert_approx_eq,
470+
/// geom::Edge,
471+
/// math::{pt2, Point2}
472+
/// };
473+
/// use retrofire_geom::isect::Intersect;
474+
///
475+
/// //
476+
/// // O1
477+
/// // \
478+
/// // \
479+
/// // \
480+
/// // E1---X----E2
481+
/// // \
482+
/// // O2
483+
/// let edge: Edge<Point2> = Edge(pt2(0.0, 1.0), pt2(4.0, 1.0));
484+
/// let other = Edge(pt2(0.0, 3.0), pt2(3.0, 0.0));
485+
///
486+
/// assert_approx_eq!(edge.intersect(&other), Some(pt2(2.0, 1.0)));
487+
///
488+
/// ```
489+
fn intersect(&self, edge: &Self) -> Self::Result {
490+
let ray = Ray(self.0, self.1 - self.0);
491+
match ray.intersect(edge) {
492+
Some((t, pt)) if t <= 1.0 => Some(pt),
493+
_ => None,
494+
}
495+
}
496+
}
497+
246498
#[cfg(test)]
247499
mod tests {
248500
use retrofire_core::math::{Linear, Vec3, pt3};
@@ -453,4 +705,5 @@ mod tests {
453705
assert_eq!(ray.intersect(&SPHERE), None);
454706
}
455707
}
708+
// TODO 2D tests from stash
456709
}

0 commit comments

Comments
 (0)