Merge pull request #2268 from hannobraun/intersect

Remove unused intersection checks
This commit is contained in:
Hanno Braun 2024-03-18 13:01:43 +01:00 committed by GitHub
commit 68a4c0008f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1 additions and 867 deletions

View File

@ -1,147 +0,0 @@
use fj_math::{Point, Segment};
use crate::{geometry::SurfacePath, objects::HalfEdge};
use super::LineSegmentIntersection;
/// The intersection between a curve and a [`HalfEdge`]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum CurveEdgeIntersection {
/// The curve and edge intersect at a point
Point {
/// The intersection point, in curve coordinates on the curve
point_on_curve: Point<1>,
},
/// The edge lies on the curve
Coincident {
/// The end points of the edge, in curve coordinates on the curve
points_on_curve: [Point<1>; 2],
},
}
impl CurveEdgeIntersection {
/// Compute the intersection
///
/// # Panics
///
/// Currently, only intersections between lines and line segments can be
/// computed. Panics, if a different type of curve or [`HalfEdge`] is passed.
pub fn compute(path: &SurfacePath, edge: &HalfEdge) -> Option<Self> {
let path_as_line = match path {
SurfacePath::Line(line) => line,
_ => todo!("Curve-edge intersection only supports lines"),
};
let edge_as_segment = {
let edge_path_as_line = match edge.path() {
SurfacePath::Line(line) => line,
_ => {
todo!("Curve-edge intersection only supports line segments")
}
};
let edge_vertices = edge
.boundary()
.inner
.map(|point| edge_path_as_line.point_from_line_coords(point));
Segment::from_points(edge_vertices)
};
let intersection =
LineSegmentIntersection::compute(path_as_line, &edge_as_segment)?;
let intersection = match intersection {
LineSegmentIntersection::Point { point_on_line } => Self::Point {
point_on_curve: point_on_line,
},
LineSegmentIntersection::Coincident { points_on_line } => {
Self::Coincident {
points_on_curve: points_on_line,
}
}
};
Some(intersection)
}
}
#[cfg(test)]
mod tests {
use fj_math::Point;
use crate::{
geometry::SurfacePath, objects::HalfEdge,
operations::build::BuildHalfEdge, Core,
};
use super::CurveEdgeIntersection;
#[test]
fn compute_edge_in_front_of_curve_origin() {
let mut core = Core::new();
let path = SurfacePath::u_axis();
let edge =
HalfEdge::line_segment([[1., -1.], [1., 1.]], None, &mut core);
let intersection = CurveEdgeIntersection::compute(&path, &edge);
assert_eq!(
intersection,
Some(CurveEdgeIntersection::Point {
point_on_curve: Point::from([1.])
})
);
}
#[test]
fn compute_edge_behind_curve_origin() {
let mut core = Core::new();
let path = SurfacePath::u_axis();
let edge =
HalfEdge::line_segment([[-1., -1.], [-1., 1.]], None, &mut core);
let intersection = CurveEdgeIntersection::compute(&path, &edge);
assert_eq!(
intersection,
Some(CurveEdgeIntersection::Point {
point_on_curve: Point::from([-1.])
})
);
}
#[test]
fn compute_edge_parallel_to_curve() {
let mut core = Core::new();
let path = SurfacePath::u_axis();
let edge =
HalfEdge::line_segment([[-1., -1.], [1., -1.]], None, &mut core);
let intersection = CurveEdgeIntersection::compute(&path, &edge);
assert!(intersection.is_none());
}
#[test]
fn compute_edge_on_curve() {
let mut core = Core::new();
let path = SurfacePath::u_axis();
let edge =
HalfEdge::line_segment([[-1., 0.], [1., 0.]], None, &mut core);
let intersection = CurveEdgeIntersection::compute(&path, &edge);
assert_eq!(
intersection,
Some(CurveEdgeIntersection::Coincident {
points_on_curve: [Point::from([-1.]), Point::from([1.]),]
})
);
}
}

View File

@ -1,279 +0,0 @@
use std::vec;
use fj_interop::ext::SliceExt;
use fj_math::Point;
use crate::{geometry::SurfacePath, objects::Face};
use super::CurveEdgeIntersection;
/// The intersections between a curve and a [`Face`], in curve coordinates
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct CurveFaceIntersection {
/// The intervals where the curve and face intersect, in curve coordinates
pub intervals: Vec<CurveFaceIntersectionInterval>,
}
impl CurveFaceIntersection {
/// 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<CurveFaceIntersectionInterval>,
>,
) -> Self {
let intervals = intervals.into_iter().map(Into::into).collect();
Self { intervals }
}
/// Compute the intersection
pub fn compute(path: &SurfacePath, face: &Face) -> Self {
let edges = face
.region()
.all_cycles()
.flat_map(|cycle| cycle.half_edges());
let mut intersections = Vec::new();
for edge in edges {
let intersection = CurveEdgeIntersection::compute(path, edge);
if let Some(intersection) = intersection {
match intersection {
CurveEdgeIntersection::Point { point_on_curve } => {
intersections.push(point_on_curve);
}
CurveEdgeIntersection::Coincident { points_on_curve } => {
intersections.extend(points_on_curve);
}
}
}
}
assert!(intersections.len() % 2 == 0);
intersections.sort();
let intervals = intersections
.as_slice()
.array_chunks_ext()
.map(|&[start, end]| CurveFaceIntersectionInterval { start, end })
.collect();
Self { intervals }
}
/// Merge this intersection list with another
///
/// The merged list will contain all overlaps of the intervals from the two
/// other lists.
pub fn merge(&self, other: &Self) -> Self {
let mut self_intervals = self.intervals.iter().copied();
let mut other_interval = other.intervals.iter().copied();
let mut next_self = self_intervals.next();
let mut next_other = other_interval.next();
let mut intervals = Vec::new();
while let (Some(self_), Some(other)) = (next_self, next_other) {
// If we're starting another loop iteration, we have another
// interval available from both `self` and `other` each. Only if
// that's the case, is there a chance for an overlap.
// Build the overlap of the two next intervals, by comparing them.
// At this point we don't know yet, if this is a valid interval.
let overlap_start = self_.start.max(other.start);
let overlap_end = self_.end.min(other.end);
if overlap_start < overlap_end {
// This is indeed a valid overlap. Add it to our list of
// results.
intervals.push(CurveFaceIntersectionInterval {
start: overlap_start,
end: overlap_end,
});
}
// Only if the end of the overlap interval has overtaken one of the
// input ones are we done with it. An input interval that hasn't
// been overtaken by the overlap, could still overlap with another
// interval.
if self_.end <= overlap_end {
// Current interval from `self` has been overtaken. Let's grab
// the next one.
next_self = self_intervals.next();
}
if other.end <= overlap_end {
// Current interval from `other` has been overtaken. Let's grab
// the next one.
next_other = other_interval.next();
}
}
Self { intervals }
}
/// Indicate whether the intersection list is empty
pub fn is_empty(&self) -> bool {
self.intervals.is_empty()
}
}
impl IntoIterator for CurveFaceIntersection {
type Item = CurveFaceIntersectionInterval;
type IntoIter = vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.intervals.into_iter()
}
}
/// An intersection between a curve and a face
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct CurveFaceIntersectionInterval {
/// The start of the intersection interval, in curve coordinates
pub start: Point<1>,
/// The end of the intersection interval, in curve coordinates
pub end: Point<1>,
}
impl<P> From<[P; 2]> for CurveFaceIntersectionInterval
where
P: Into<Point<1>>,
{
fn from(interval: [P; 2]) -> Self {
let [start, end] = interval.map(Into::into);
Self { start, end }
}
}
#[cfg(test)]
mod tests {
use crate::{
geometry::SurfacePath,
objects::{Cycle, Face},
operations::{
build::{BuildCycle, BuildFace},
update::{UpdateFace, UpdateRegion},
},
Core,
};
use super::CurveFaceIntersection;
#[test]
fn compute() {
let mut core = Core::new();
let (path, _) = SurfacePath::line_from_points([[-3., 0.], [-2., 0.]]);
#[rustfmt::skip]
let exterior_points = [
[-2., -2.],
[ 2., -2.],
[ 2., 2.],
[-2., 2.],
];
#[rustfmt::skip]
let interior_points = [
[-1., -1.],
[-1., 1.],
[ 1., 1.],
[ 1., -1.],
];
let face =
Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core)
.update_region(
|region, core| {
region
.update_exterior(
|_, core| Cycle::polygon(exterior_points, core),
core,
)
.add_interiors(
[Cycle::polygon(interior_points, core)],
core,
)
},
&mut core,
);
let expected =
CurveFaceIntersection::from_intervals([[[1.], [2.]], [[4.], [5.]]]);
assert_eq!(CurveFaceIntersection::compute(&path, &face), expected);
}
#[test]
fn merge() {
let a = CurveFaceIntersection::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: 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 = CurveFaceIntersection::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
]);
let merged = a.merge(&b);
let expected = CurveFaceIntersection::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
]);
assert_eq!(merged, expected);
}
}

View File

@ -1,397 +0,0 @@
//! Intersection between faces and points in 2D
use fj_math::Point;
use crate::{
objects::{Face, HalfEdge},
storage::Handle,
};
use super::{
ray_segment::RaySegmentIntersection, HorizontalRayToTheRight, Intersect,
};
impl Intersect for (&Face, &Point<2>) {
type Intersection = FacePointIntersection;
fn intersect(self) -> Option<Self::Intersection> {
let (face, point) = self;
let ray = HorizontalRayToTheRight { origin: *point };
let mut num_hits = 0;
for cycle in face.region().all_cycles() {
// We need to properly detect the ray passing the boundary at the
// "seam" of the polygon, i.e. the vertex between the last and the
// 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 = cycle
.half_edges()
.iter()
.last()
.and_then(|edge| (&ray, edge).intersect());
for (edge, next_edge) in cycle.half_edges().pairs() {
let hit = (&ray, edge).intersect();
let count_hit = match (hit, previous_hit) {
(
Some(RaySegmentIntersection::RayStartsOnSegment),
_,
) => {
// If the ray starts on the boundary of the face,
// there's nothing to else check.
return Some(FacePointIntersection::PointIsOnEdge(
edge.clone()
));
}
(Some(RaySegmentIntersection::RayStartsOnOnFirstVertex), _) => {
let vertex = edge.start_position();
return Some(
FacePointIntersection::PointIsOnVertex(vertex)
);
}
(Some(RaySegmentIntersection::RayStartsOnSecondVertex), _) => {
let vertex = next_edge.start_position();
return Some(
FacePointIntersection::PointIsOnVertex(vertex)
);
}
(Some(RaySegmentIntersection::RayHitsSegment), _) => {
// We're hitting a segment right-on. Clear case.
true
}
(
Some(RaySegmentIntersection::RayHitsUpperVertex),
Some(RaySegmentIntersection::RayHitsLowerVertex),
)
| (
Some(RaySegmentIntersection::RayHitsLowerVertex),
Some(RaySegmentIntersection::RayHitsUpperVertex),
) => {
// If we're hitting a vertex, only count it if we've hit
// the other kind of vertex right before.
//
// That means, we're passing through the polygon
// boundary at where two edges touch. Depending on the
// order in which edges are checked, we're seeing this
// as a hit to one edge's lower/upper vertex, then the
// other edge's opposite vertex.
//
// If we're seeing two of the same vertices in a row,
// we're not actually passing through the polygon
// boundary. Then we're just touching a vertex without
// passing through anything.
true
}
(Some(RaySegmentIntersection::RayHitsSegmentAndAreParallel), _) => {
// A parallel edge must be completely ignored. Its
// presence won't change anything, so we can treat it as
// if it wasn't there, and its neighbors were connected
// to each other.
continue;
}
_ => {
// Any other case is not a valid hit.
false
}
};
if count_hit {
num_hits += 1;
}
previous_hit = hit;
}
}
if num_hits % 2 == 1 {
Some(FacePointIntersection::PointIsInsideFace)
} else {
None
}
}
}
/// The intersection between a face and a point
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum FacePointIntersection {
/// The point is inside of the face
PointIsInsideFace,
/// The point is coincident with an edge
PointIsOnEdge(Handle<HalfEdge>),
/// The point is coincident with a vertex
PointIsOnVertex(Point<2>),
}
#[cfg(test)]
mod tests {
use fj_math::Point;
use pretty_assertions::assert_eq;
use crate::{
algorithms::intersect::{face_point::FacePointIntersection, Intersect},
objects::{Cycle, Face},
operations::{
build::{BuildCycle, BuildFace},
update::{UpdateFace, UpdateRegion},
},
Core,
};
#[test]
fn point_is_outside_face() {
let mut core = Core::new();
let face =
Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core)
.update_region(
|region, core| {
region.update_exterior(
|_, core| {
Cycle::polygon(
[[0., 0.], [1., 1.], [0., 2.]],
core,
)
},
core,
)
},
&mut core,
);
let point = Point::from([2., 1.]);
let intersection = (&face, &point).intersect();
assert_eq!(intersection, None);
}
#[test]
fn ray_hits_vertex_while_passing_outside() {
let mut core = Core::new();
let face =
Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core)
.update_region(
|region, core| {
region.update_exterior(
|_, core| {
Cycle::polygon(
[[0., 0.], [2., 1.], [0., 2.]],
core,
)
},
core,
)
},
&mut core,
);
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
}
#[test]
fn ray_hits_vertex_at_cycle_seam() {
let mut core = Core::new();
let face =
Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core)
.update_region(
|region, core| {
region.update_exterior(
|_, core| {
Cycle::polygon(
[[4., 2.], [0., 4.], [0., 0.]],
core,
)
},
core,
)
},
&mut core,
);
let point = Point::from([1., 2.]);
let intersection = (&face, &point).intersect();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
}
#[test]
fn ray_hits_vertex_while_staying_inside() {
let mut core = Core::new();
let face =
Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core)
.update_region(
|region, core| {
region.update_exterior(
|_, core| {
Cycle::polygon(
[[0., 0.], [2., 1.], [3., 0.], [3., 4.]],
core,
)
},
core,
)
},
&mut core,
);
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
}
#[test]
fn ray_hits_parallel_edge_and_leaves_face_at_vertex() {
let mut core = Core::new();
let face =
Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core)
.update_region(
|region, core| {
region.update_exterior(
|_, core| {
Cycle::polygon(
[[0., 0.], [2., 1.], [3., 1.], [0., 2.]],
core,
)
},
core,
)
},
&mut core,
);
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
}
#[test]
fn ray_hits_parallel_edge_and_does_not_leave_face_there() {
let mut core = Core::new();
let face =
Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core)
.update_region(
|region, core| {
region.update_exterior(
|_, core| {
Cycle::polygon(
[
[0., 0.],
[2., 1.],
[3., 1.],
[4., 0.],
[4., 5.],
],
core,
)
},
core,
)
},
&mut core,
);
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
}
#[test]
fn point_is_coincident_with_edge() {
let mut core = Core::new();
let face =
Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core)
.update_region(
|region, core| {
region.update_exterior(
|_, core| {
Cycle::polygon(
[[0., 0.], [2., 0.], [0., 1.]],
core,
)
},
core,
)
},
&mut core,
);
let point = Point::from([1., 0.]);
let intersection = (&face, &point).intersect();
let edge = face
.region()
.exterior()
.half_edges()
.iter()
.find(|edge| edge.start_position() == Point::from([0., 0.]))
.unwrap();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsOnEdge(edge.clone()))
);
}
#[test]
fn point_is_coincident_with_vertex() {
let mut core = Core::new();
let face =
Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core)
.update_region(
|region, core| {
region.update_exterior(
|_, core| {
Cycle::polygon(
[[0., 0.], [1., 0.], [0., 1.]],
core,
)
},
core,
)
},
&mut core,
);
let point = Point::from([1., 0.]);
let intersection = (&face, &point).intersect();
let vertex = face
.region()
.exterior()
.half_edges()
.iter()
.find(|edge| edge.start_position() == Point::from([1., 0.]))
.map(|edge| edge.start_position())
.unwrap();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsOnVertex(vertex))
);
}
}

View File

@ -1,20 +1,12 @@
//! Intersection algorithms //! Intersection algorithms
pub mod face_point;
pub mod ray_edge;
pub mod ray_segment; pub mod ray_segment;
mod curve_edge;
mod curve_face;
mod line_segment; mod line_segment;
use fj_math::{Point, Vector}; use fj_math::{Point, Vector};
pub use self::{ pub use self::line_segment::LineSegmentIntersection;
curve_edge::CurveEdgeIntersection,
curve_face::{CurveFaceIntersection, CurveFaceIntersectionInterval},
line_segment::LineSegmentIntersection,
};
/// Compute the intersection between a tuple of objects /// Compute the intersection between a tuple of objects
/// ///

View File

@ -1,35 +0,0 @@
//! Intersection between a ray and an edge in 2D
use fj_math::Segment;
use crate::{
algorithms::intersect::{HorizontalRayToTheRight, Intersect},
geometry::SurfacePath,
objects::HalfEdge,
storage::Handle,
};
use super::ray_segment::RaySegmentIntersection;
impl Intersect for (&HorizontalRayToTheRight<2>, &Handle<HalfEdge>) {
type Intersection = RaySegmentIntersection;
fn intersect(self) -> Option<Self::Intersection> {
let (ray, edge) = self;
let line = match edge.path() {
SurfacePath::Line(line) => line,
SurfacePath::Circle(_) => {
todo!("Casting rays against circles is not supported yet")
}
};
let points = edge
.boundary()
.inner
.map(|point| line.point_from_line_coords(point));
let segment = Segment::from_points(points);
(ray, &segment).intersect()
}
}