diff --git a/crates/fj-kernel/src/algorithms/cast_ray/mod.rs b/crates/fj-kernel/src/algorithms/cast_ray/mod.rs new file mode 100644 index 000000000..7496fc786 --- /dev/null +++ b/crates/fj-kernel/src/algorithms/cast_ray/mod.rs @@ -0,0 +1,37 @@ +//! Ray casting + +mod segment; + +pub use self::segment::RaySegmentHit; + +use fj_math::Point; + +/// Implemented by types that support ray casting +pub trait CastRay { + /// The type that describes a hit of the ray on the implementing type + type Hit; + + /// Cast a ray against `self` + fn cast_ray(&self, ray: HorizontalRayToTheRight) -> Option; +} + +/// A horizontal ray that goes to the right +/// +/// For in-kernel use, we don't need anything more flexible, and being exactly +/// horizontal simplifies some calculations. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct HorizontalRayToTheRight { + /// The point where the ray originates + pub origin: Point, +} + +impl From

for HorizontalRayToTheRight +where + P: Into>, +{ + fn from(point: P) -> Self { + Self { + origin: point.into(), + } + } +} diff --git a/crates/fj-kernel/src/algorithms/ray_cast.rs b/crates/fj-kernel/src/algorithms/cast_ray/segment.rs similarity index 53% rename from crates/fj-kernel/src/algorithms/ray_cast.rs rename to crates/fj-kernel/src/algorithms/cast_ray/segment.rs index 89d9e6f51..e0fcdebe6 100644 --- a/crates/fj-kernel/src/algorithms/ray_cast.rs +++ b/crates/fj-kernel/src/algorithms/cast_ray/segment.rs @@ -1,39 +1,31 @@ -//! Ray casting +use fj_math::Segment; -use fj_math::{Point, Segment}; +use super::{CastRay, HorizontalRayToTheRight}; -/// A horizontal ray that goes to the right -/// -/// For in-kernel use, we don't need anything more flexible, and being exactly -/// horizontal simplifies some calculations. -pub struct HorizontalRayToTheRight { - /// The point where the ray originates - pub origin: Point, -} +impl CastRay<2> for Segment<2> { + type Hit = RaySegmentHit; -impl HorizontalRayToTheRight<2> { - /// Determine whether the ray hits the given line segment - pub fn hits_segment( + fn cast_ray( &self, - segment: impl Into>, + ray: HorizontalRayToTheRight<2>, ) -> Option { - let [a, b] = segment.into().points(); + let [a, b] = self.points(); let [lower, upper] = if a.v <= b.v { [a, b] } else { [b, a] }; let right = if a.u > b.u { a } else { b }; - if self.origin.v > upper.v { + if ray.origin.v > upper.v { // ray is above segment return None; } - if self.origin.v < lower.v { + if ray.origin.v < lower.v { // ray is below segment return None; } - if self.origin.v == lower.v && lower.v == upper.v { + if ray.origin.v == lower.v && lower.v == upper.v { // ray and segment are parallel and at same height - if self.origin.u > right.u { + if ray.origin.u > right.u { return None; } @@ -49,17 +41,17 @@ impl HorizontalRayToTheRight<2> { y: upper.v, }; let pc = robust::Coord { - x: self.origin.u, - y: self.origin.v, + x: ray.origin.u, + y: ray.origin.v, }; if robust::orient2d(pa, pb, pc) >= 0. { // ray starts on the line or left of it - if self.origin.v == upper.v { + if ray.origin.v == upper.v { return Some(RaySegmentHit::UpperVertex); } - if self.origin.v == lower.v { + if ray.origin.v == lower.v { return Some(RaySegmentHit::LowerVertex); } @@ -70,17 +62,6 @@ impl HorizontalRayToTheRight<2> { } } -impl From

for HorizontalRayToTheRight -where - P: Into>, -{ - fn from(point: P) -> Self { - Self { - origin: point.into(), - } - } -} - /// A hit between a ray and a line segment #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum RaySegmentHit { @@ -99,20 +80,24 @@ pub enum RaySegmentHit { #[cfg(test)] mod tests { + use fj_math::Segment; + + use crate::algorithms::cast_ray::CastRay; + use super::{HorizontalRayToTheRight, RaySegmentHit}; #[test] fn hits_segment_right() { let ray = HorizontalRayToTheRight::from([0., 2.]); - let below = [[1., 0.], [1., 1.]]; - let above = [[1., 3.], [1., 4.]]; - let same_level = [[1., 1.], [1., 3.]]; + let below = Segment::from([[1., 0.], [1., 1.]]); + let above = Segment::from([[1., 3.], [1., 4.]]); + let same_level = Segment::from([[1., 1.], [1., 3.]]); - assert!(ray.hits_segment(below).is_none()); - assert!(ray.hits_segment(above).is_none()); + assert!(below.cast_ray(ray).is_none()); + assert!(above.cast_ray(ray).is_none()); assert!(matches!( - ray.hits_segment(same_level), + same_level.cast_ray(ray), Some(RaySegmentHit::Segment) )); } @@ -121,31 +106,31 @@ mod tests { fn hits_segment_left() { let ray = HorizontalRayToTheRight::from([1., 2.]); - let same_level = [[0., 1.], [0., 3.]]; - assert!(ray.hits_segment(same_level).is_none()); + let same_level = Segment::from([[0., 1.], [0., 3.]]); + assert!(same_level.cast_ray(ray).is_none()); } #[test] fn hits_segment_overlapping() { let ray = HorizontalRayToTheRight::from([1., 1.]); - let no_hit = [[0., 0.], [2., 3.]]; + let no_hit = Segment::from([[0., 0.], [2., 3.]]); - let hit_segment = [[0., 0.], [3., 2.]]; - let hit_upper = [[0., 0.], [2., 1.]]; - let hit_lower = [[0., 2.], [2., 1.]]; + let hit_segment = Segment::from([[0., 0.], [3., 2.]]); + let hit_upper = Segment::from([[0., 0.], [2., 1.]]); + let hit_lower = Segment::from([[0., 2.], [2., 1.]]); - assert!(ray.hits_segment(no_hit).is_none()); + assert!(no_hit.cast_ray(ray).is_none()); assert!(matches!( - ray.hits_segment(hit_segment), + hit_segment.cast_ray(ray), Some(RaySegmentHit::Segment) )); assert!(matches!( - ray.hits_segment(hit_upper), + hit_upper.cast_ray(ray), Some(RaySegmentHit::UpperVertex), )); assert!(matches!( - ray.hits_segment(hit_lower), + hit_lower.cast_ray(ray), Some(RaySegmentHit::LowerVertex), )); } @@ -154,20 +139,20 @@ mod tests { fn hits_segment_on_segment() { let ray = HorizontalRayToTheRight::from([1., 1.]); - let hit_segment = [[0., 0.], [2., 2.]]; - let hit_upper = [[0., 0.], [1., 1.]]; - let hit_lower = [[1., 1.], [2., 2.]]; + let hit_segment = Segment::from([[0., 0.], [2., 2.]]); + let hit_upper = Segment::from([[0., 0.], [1., 1.]]); + let hit_lower = Segment::from([[1., 1.], [2., 2.]]); assert!(matches!( - ray.hits_segment(hit_segment), + hit_segment.cast_ray(ray), Some(RaySegmentHit::Segment) )); assert!(matches!( - ray.hits_segment(hit_upper), + hit_upper.cast_ray(ray), Some(RaySegmentHit::UpperVertex), )); assert!(matches!( - ray.hits_segment(hit_lower), + hit_lower.cast_ray(ray), Some(RaySegmentHit::LowerVertex), )); } @@ -176,18 +161,15 @@ mod tests { fn hits_segment_parallel() { let ray = HorizontalRayToTheRight::from([2., 0.]); - let left = [[0., 0.], [1., 0.]]; - let overlapping = [[1., 0.], [3., 0.]]; - let right = [[3., 0.], [4., 0.]]; + let left = Segment::from([[0., 0.], [1., 0.]]); + let overlapping = Segment::from([[1., 0.], [3., 0.]]); + let right = Segment::from([[3., 0.], [4., 0.]]); - assert!(ray.hits_segment(left).is_none()); - assert!(matches!( - ray.hits_segment(overlapping), - Some(RaySegmentHit::Parallel) - )); + assert!(left.cast_ray(ray).is_none()); assert!(matches!( - ray.hits_segment(right), + overlapping.cast_ray(ray), Some(RaySegmentHit::Parallel) )); + assert!(matches!(right.cast_ray(ray), Some(RaySegmentHit::Parallel))); } } diff --git a/crates/fj-kernel/src/algorithms/mod.rs b/crates/fj-kernel/src/algorithms/mod.rs index 08319f8a7..889f66288 100644 --- a/crates/fj-kernel/src/algorithms/mod.rs +++ b/crates/fj-kernel/src/algorithms/mod.rs @@ -9,8 +9,8 @@ mod sweep; mod transform; mod triangulate; +pub mod cast_ray; pub mod intersection; -pub mod ray_cast; pub use self::{ approx::{CycleApprox, FaceApprox, InvalidTolerance, Tolerance}, diff --git a/crates/fj-kernel/src/algorithms/triangulate/polygon.rs b/crates/fj-kernel/src/algorithms/triangulate/polygon.rs index eac7d93e8..b53aca5a7 100644 --- a/crates/fj-kernel/src/algorithms/triangulate/polygon.rs +++ b/crates/fj-kernel/src/algorithms/triangulate/polygon.rs @@ -2,7 +2,7 @@ use fj_interop::debug::{DebugInfo, TriangleEdgeCheck}; use fj_math::{Point, PolyChain, Segment}; use crate::{ - algorithms::ray_cast::{HorizontalRayToTheRight, RaySegmentHit}, + algorithms::cast_ray::{CastRay, HorizontalRayToTheRight, RaySegmentHit}, objects::Surface, }; @@ -154,13 +154,11 @@ impl Polygon { // first segment. The logic in the loop properly takes care of that, // as long as we initialize the `previous_hit` variable with the // result of the last segment. - let mut previous_hit = edges - .last() - .copied() - .and_then(|edge| ray.hits_segment(edge)); + let mut previous_hit = + edges.last().copied().and_then(|edge| edge.cast_ray(ray)); for edge in edges { - let hit = ray.hits_segment(edge); + let hit = edge.cast_ray(ray); let count_hit = match (hit, previous_hit) { (Some(RaySegmentHit::Segment), _) => {