Skip to content

Commit

Permalink
Merge pull request #812 from hannobraun/intersection
Browse files Browse the repository at this point in the history
Add `CurveFaceIntersections`
  • Loading branch information
hannobraun authored Jul 13, 2022
2 parents 0bc3423 + 9cf47ea commit f7a4fe5
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 76 deletions.
173 changes: 98 additions & 75 deletions crates/fj-kernel/src/algorithms/intersection/curve_face.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,95 +3,120 @@ use parry2d_f64::query::{Ray, RayCast};

use crate::objects::{Curve, Face};

/// Determine the intersection between a [`Curve`] and a [`Face`]
///
/// Returns a list of intersections in curve coordinates.
pub fn curve_face(curve: &Curve<2>, face: &Face) -> Vec<[Scalar; 2]> {
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().collect();
edges
})
.map(|edge| {
let line = match edge.curve.local() {
Curve::Line(line) => line,
_ => todo!("Curve-face intersection only supports polygons"),
};
/// The intersections between a [`Curve`] and a [`Face`], in curve coordinates
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct CurveFaceIntersections(Vec<[Scalar; 2]>);

impl CurveFaceIntersections {
/// Create a new instance from the intersection intervals
///
/// This method is useful for test code.
pub fn from_intervals(
intervals: impl IntoIterator<Item = [impl Into<Scalar>; 2]>,
) -> Self {
let intervals = intervals
.into_iter()
.map(|interval| interval.map(Into::into))
.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 vertices = match edge.vertices() {
Some(vertices) => vertices,
None => todo!(
"Curve-face intersection does not support faces with \
let face_as_polygon = face
.exteriors()
.chain(face.interiors())
.flat_map(|cycle| {
let edges: Vec<_> = cycle.edges().collect();
edges
})
.map(|edge| {
let line = match edge.curve.local() {
Curve::Line(line) => line,
_ => {
todo!("Curve-face intersection only supports polygons")
}
};

let vertices = match edge.vertices() {
Some(vertices) => vertices,
None => todo!(
"Curve-face intersection does not support faces with \
continuous edges"
),
};
),
};

(line, vertices)
});
(line, vertices)
});

let mut intersections = Vec::new();
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);
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 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));
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));
}
}
}

assert!(intersections.len() % 2 == 0);
assert!(intersections.len() % 2 == 0);

intersections.sort();

intersections.sort();
// Can be cleaned up, once `array_chunks` is stable:
// https://doc.rust-lang.org/std/primitive.slice.html#method.array_chunks
let intersections = intersections
.chunks(2)
.map(|chunk| {
// Can't panic, as we passed `2` to `windows`.
[chunk[0], chunk[1]]
})
.collect();

// Can be cleaned up, once `array_chunks` is stable:
// https://doc.rust-lang.org/std/primitive.slice.html#method.array_chunks
intersections
.chunks(2)
.map(|chunk| {
// Can't panic, as we passed `2` to `windows`.
[chunk[0], chunk[1]]
})
.collect()
CurveFaceIntersections(intersections)
}
}

#[cfg(test)]
mod tests {
use fj_math::{Line, Point, Scalar, Vector};
use fj_math::{Line, Point, Vector};

use crate::objects::{Curve, Face, Surface};

use super::CurveFaceIntersections;

#[test]
fn curve_face() {
fn compute() {
let curve = Curve::Line(Line {
origin: Point::from([-3., 0.]),
direction: Vector::from([1., 0.]),
Expand All @@ -117,10 +142,8 @@ mod tests {
.with_interior_polygon(interior)
.build();

let expected: Vec<_> = [[1., 2.], [4., 5.]]
.into_iter()
.map(|interval: [f64; 2]| interval.map(Scalar::from))
.collect();
assert_eq!(super::curve_face(&curve, &face), expected);
let expected =
CurveFaceIntersections::from_intervals([[1., 2.], [4., 5.]]);
assert_eq!(CurveFaceIntersections::compute(&curve, &face), expected);
}
}
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/algorithms/intersection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod line_segment;
mod surface_surface;

pub use self::{
curve_face::curve_face,
curve_face::CurveFaceIntersections,
line_segment::{line_segment, LineSegmentIntersection},
surface_surface::surface_surface,
};

0 comments on commit f7a4fe5

Please sign in to comment.