Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement curve/edge intersection #884

Merged
merged 7 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions crates/fj-kernel/src/algorithms/intersection/curve_edge.rs
Original file line number Diff line number Diff line change
@@ -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<Point<1>>) -> 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<Self> {
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());
}
}
205 changes: 81 additions & 124 deletions crates/fj-kernel/src/algorithms/intersection/curve_face.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -16,80 +18,33 @@ impl CurveFaceIntersectionList {
///
/// This method is useful for test code.
pub fn from_intervals(
intervals: impl IntoIterator<Item = [impl Into<Scalar>; 2]>,
intervals: impl IntoIterator<Item = [impl Into<Point<1>>; 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);
}
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down
Loading