diff --git a/crates/fj-kernel/src/algorithms/intersection/curve_edge.rs b/crates/fj-kernel/src/algorithms/intersection/curve_edge.rs new file mode 100644 index 000000000..762e90d37 --- /dev/null +++ b/crates/fj-kernel/src/algorithms/intersection/curve_edge.rs @@ -0,0 +1,134 @@ +use fj_math::{Point, Segment}; +use parry2d_f64::query::{Ray, RayCast}; + +use crate::objects::{Curve, Edge}; + +/// The intersection between a [`Curve`] and an [`Edge`], in curve coordinates +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct CurveEdgeIntersection { + point_on_curve: Point<1>, +} + +impl CurveEdgeIntersection { + /// Construct an instance from a point on a curve + pub fn from_point_on_curve(point_on_curve: impl Into>) -> Self { + let point_on_curve = point_on_curve.into(); + Self { point_on_curve } + } + + /// Compute the intersection + /// + /// # Panics + /// + /// Currently, only intersections between lines and line segments can be + /// computed. Panics, if a different type of [`Curve`] or [`Edge`] is + /// passed. + pub fn compute(curve: &Curve<2>, edge: &Edge) -> Option { + let curve_as_line = match curve { + Curve::Line(line) => line, + _ => todo!("Curve-edge intersection only supports lines"), + }; + + let edge_as_segment = { + let line = match edge.curve().local_form() { + Curve::Line(line) => line, + _ => { + todo!("Curve-edge intersection only supports line segments") + } + }; + + let vertices = match edge.vertices().get() { + Some(vertices) => vertices.map(|vertex| { + line.point_from_line_coords(vertex.position()) + }), + None => todo!( + "Curve-edge intersection does not support continuous edges" + ), + }; + + Segment::from_points(vertices) + }; + + let ray = Ray { + origin: curve_as_line.origin.to_na(), + dir: curve_as_line.direction.to_na(), + }; + let ray_inv = Ray { + origin: curve_as_line.origin.to_na(), + dir: -curve_as_line.direction.to_na(), + }; + + let result = edge_as_segment.to_parry().cast_local_ray( + &ray, + f64::INFINITY, + false, + ); + let result_inv = edge_as_segment.to_parry().cast_local_ray( + &ray_inv, + f64::INFINITY, + false, + ); + + if let Some(result) = result { + return Some(Self { + point_on_curve: Point::from([result]), + }); + } + if let Some(result_inv) = result_inv { + return Some(Self { + point_on_curve: Point::from([-result_inv]), + }); + } + + None + } + + /// Access the intersection point on the curve + pub fn point_on_curve(&self) -> Point<1> { + self.point_on_curve + } +} + +#[cfg(test)] +mod tests { + use fj_math::Point; + + use crate::objects::{Curve, Edge, Surface}; + + use super::CurveEdgeIntersection; + + #[test] + fn compute() { + let surface = Surface::xy_plane(); + + let curve = Curve::u_axis(); + + let edge_left = Edge::build() + .line_segment_from_points(&surface, [[-1., -1.], [-1., 1.]]); + let edge_right = Edge::build() + .line_segment_from_points(&surface, [[1., -1.], [1., 1.]]); + let edge_below = Edge::build() + .line_segment_from_points(&surface, [[-1., -1.], [1., -1.]]); + + let intersection_with_edge_left = + CurveEdgeIntersection::compute(&curve, &edge_left); + let intersection_with_edge_right = + CurveEdgeIntersection::compute(&curve, &edge_right); + let intersection_with_edge_below = + CurveEdgeIntersection::compute(&curve, &edge_below); + + assert_eq!( + intersection_with_edge_left, + Some(CurveEdgeIntersection::from_point_on_curve(Point::from([ + -1. + ]))) + ); + assert_eq!( + intersection_with_edge_right, + Some(CurveEdgeIntersection::from_point_on_curve(Point::from([ + 1. + ]))) + ); + assert!(intersection_with_edge_below.is_none()); + } +} diff --git a/crates/fj-kernel/src/algorithms/intersection/curve_face.rs b/crates/fj-kernel/src/algorithms/intersection/curve_face.rs index 979ba4c8c..e9be9e811 100644 --- a/crates/fj-kernel/src/algorithms/intersection/curve_face.rs +++ b/crates/fj-kernel/src/algorithms/intersection/curve_face.rs @@ -1,9 +1,11 @@ use std::vec; -use fj_math::{Scalar, Segment}; -use parry2d_f64::query::{Ray, RayCast}; +use fj_math::Point; -use crate::objects::{Curve, Face}; +use crate::{ + algorithms::intersection::CurveEdgeIntersection, + objects::{Curve, Face}, +}; /// The intersections between a [`Curve`] and a [`Face`], in curve coordinates #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] @@ -16,80 +18,33 @@ impl CurveFaceIntersectionList { /// /// This method is useful for test code. pub fn from_intervals( - intervals: impl IntoIterator; 2]>, + intervals: impl IntoIterator>; 2]>, ) -> Self { let intervals = intervals .into_iter() - .map(|interval| interval.map(Into::into)) + .map(|interval| { + interval + .map(Into::into) + .map(CurveEdgeIntersection::from_point_on_curve) + }) .collect(); Self { intervals } } /// Compute the intersections between a [`Curve`] and a [`Face`] pub fn compute(curve: &Curve<2>, face: &Face) -> Self { - let line = match curve { - Curve::Line(line) => line, - _ => todo!("Curve-face intersection only supports lines"), - }; - - let face_as_polygon = face - .exteriors() - .chain(face.interiors()) - .flat_map(|cycle| { - let edges: Vec<_> = cycle.edges().cloned().collect(); - edges - }) - .map(|edge| { - let line = match edge.curve().local_form() { - Curve::Line(line) => line, - _ => { - todo!("Curve-face intersection only supports polygons") - } - }; - - let vertices = match edge.vertices().get() { - Some(vertices) => vertices.map(|&vertex| vertex), - None => todo!( - "Curve-face intersection does not support faces with \ - continuous edges" - ), - }; - - (*line, vertices) - }); + let edges = face.all_cycles().flat_map(|cycle| { + let edges: Vec<_> = cycle.edges().cloned().collect(); + edges + }); let mut intersections = Vec::new(); - for (edge_line, vertices) in face_as_polygon { - let vertices = vertices.map(|vertex| { - edge_line.point_from_line_coords(vertex.position()) - }); - let segment = Segment::from_points(vertices); - - let ray = Ray { - origin: line.origin.to_na(), - dir: line.direction.to_na(), - }; - let ray_inv = Ray { - origin: line.origin.to_na(), - dir: -line.direction.to_na(), - }; - - let result = - segment - .to_parry() - .cast_local_ray(&ray, f64::INFINITY, false); - let result_inv = segment.to_parry().cast_local_ray( - &ray_inv, - f64::INFINITY, - false, - ); - - if let Some(result) = result { - intersections.push(Scalar::from(result)); - } - if let Some(result_inv) = result_inv { - intersections.push(-Scalar::from(result_inv)); + for edge in edges { + let intersection = CurveEdgeIntersection::compute(curve, &edge); + + if let Some(intersection) = intersection { + intersections.push(intersection); } } @@ -178,7 +133,7 @@ impl IntoIterator for CurveFaceIntersectionList { } /// An intersection between a curve and a face, in curve coordinates -pub type CurveFaceIntersection = [Scalar; 2]; +pub type CurveFaceIntersection = [CurveEdgeIntersection; 2]; #[cfg(test)] mod tests { @@ -215,77 +170,79 @@ mod tests { .polygon_from_points(exterior) .with_hole(interior); - let expected = - CurveFaceIntersectionList::from_intervals([[1., 2.], [4., 5.]]); + let expected = CurveFaceIntersectionList::from_intervals([ + [[1.], [2.]], + [[4.], [5.]], + ]); assert_eq!(CurveFaceIntersectionList::compute(&curve, &face), expected); } #[test] fn merge() { let a = CurveFaceIntersectionList::from_intervals([ - [0., 1.], // 1: `a` and `b` are equal - [2., 5.], // 2: `a` contains `b` - [7., 8.], // 3: `b` contains `a` - [9., 11.], // 4: overlap; `a` is left - [14., 16.], // 5: overlap; `a` is right - [18., 21.], // 6: one of `a` partially overlaps two of `b` - [23., 25.], // 7: two of `a` partially overlap one of `b` - [26., 28.], // 7 - [31., 35.], // 8: one of `a` overlaps two of `b`; partial/complete - [36., 38.], // 9: two of `a` overlap one of `b`; partial/complete - [39., 40.], // 9 - [41., 45.], // 10: one of `a` overlaps two of `b`; complete/partial - [48., 49.], // 11: two of `a` overlap one of `b`; complete/partial - [50., 52.], // 11 - [53., 58.], // 12: one of `a` overlaps two of `b` completely - [60., 61.], // 13: one of `b` overlaps two of `a` completely - [62., 63.], // 13 - [65., 66.], // 14: one of `a` with no overlap in `b` + [[0.], [1.]], // 1: `a` and `b` are equal + [[2.], [5.]], // 2: `a` contains `b` + [[7.], [8.]], // 3: `b` contains `a` + [[9.], [11.]], // 4: overlap; `a` is left + [[14.], [16.]], // 5: overlap; `a` is right + [[18.], [21.]], // 6: one of `a` partially overlaps two of `b` + [[23.], [25.]], // 7: two of `a` partially overlap one of `b` + [[26.], [28.]], // 7 + [[31.], [35.]], // 8: partial/complete: one of `a`, two of `b`; + [[36.], [38.]], // 9: partial/complete: two of `a`, one of `b` + [[39.], [40.]], // 9 + [[41.], [45.]], // 10: complete/partial: one of `a`, two of `b` + [[48.], [49.]], // 11: complete/partial: two of `a`, one of `b` + [[50.], [52.]], // 11 + [[53.], [58.]], // 12: one of `a` overlaps two of `b` completely + [[60.], [61.]], // 13: one of `b` overlaps two of `a` completely + [[62.], [63.]], // 13 + [[65.], [66.]], // 14: one of `a` with no overlap in `b` ]); let b = CurveFaceIntersectionList::from_intervals([ - [0., 1.], // 1 - [3., 4.], // 2 - [6., 9.], // 3 - [10., 12.], // 4 - [13., 15.], // 5 - [17., 19.], // 6 - [20., 22.], // 6 - [24., 27.], // 7 - [30., 32.], // 8 - [33., 34.], // 8 - [37., 41.], // 9 - [42., 43.], // 10 - [44., 46.], // 10 - [47., 51.], // 11 - [54., 55.], // 12 - [56., 57.], // 12 - [59., 64.], // 13 + [[0.], [1.]], // 1 + [[3.], [4.]], // 2 + [[6.], [9.]], // 3 + [[10.], [12.]], // 4 + [[13.], [15.]], // 5 + [[17.], [19.]], // 6 + [[20.], [22.]], // 6 + [[24.], [27.]], // 7 + [[30.], [32.]], // 8 + [[33.], [34.]], // 8 + [[37.], [41.]], // 9 + [[42.], [43.]], // 10 + [[44.], [46.]], // 10 + [[47.], [51.]], // 11 + [[54.], [55.]], // 12 + [[56.], [57.]], // 12 + [[59.], [64.]], // 13 ]); let merged = a.merge(&b); let expected = CurveFaceIntersectionList::from_intervals([ - [0., 1.], // 1 - [3., 4.], // 2 - [7., 8.], // 3 - [10., 11.], // 4 - [14., 15.], // 5 - [18., 19.], // 6 - [20., 21.], // 6 - [24., 25.], // 7 - [26., 27.], // 7 - [31., 32.], // 8 - [33., 34.], // 8 - [37., 38.], // 9 - [39., 40.], // 9 - [42., 43.], // 10 - [44., 45.], // 10 - [48., 49.], // 11 - [50., 51.], // 11 - [54., 55.], // 12 - [56., 57.], // 12 - [60., 61.], // 13 - [62., 63.], // 13 + [[0.], [1.]], // 1 + [[3.], [4.]], // 2 + [[7.], [8.]], // 3 + [[10.], [11.]], // 4 + [[14.], [15.]], // 5 + [[18.], [19.]], // 6 + [[20.], [21.]], // 6 + [[24.], [25.]], // 7 + [[26.], [27.]], // 7 + [[31.], [32.]], // 8 + [[33.], [34.]], // 8 + [[37.], [38.]], // 9 + [[39.], [40.]], // 9 + [[42.], [43.]], // 10 + [[44.], [45.]], // 10 + [[48.], [49.]], // 11 + [[50.], [51.]], // 11 + [[54.], [55.]], // 12 + [[56.], [57.]], // 12 + [[60.], [61.]], // 13 + [[62.], [63.]], // 13 ]); assert_eq!(merged, expected); } diff --git a/crates/fj-kernel/src/algorithms/intersection/mod.rs b/crates/fj-kernel/src/algorithms/intersection/mod.rs index a67988b48..0afe778f6 100644 --- a/crates/fj-kernel/src/algorithms/intersection/mod.rs +++ b/crates/fj-kernel/src/algorithms/intersection/mod.rs @@ -1,10 +1,12 @@ //! Intersection algorithms +mod curve_edge; mod curve_face; mod line_segment; mod surface_surface; pub use self::{ + curve_edge::CurveEdgeIntersection, curve_face::{CurveFaceIntersection, CurveFaceIntersectionList}, line_segment::{line_segment, LineSegmentIntersection}, surface_surface::surface_surface,