diff --git a/crates/fj-core/src/algorithms/intersect/face_face.rs b/crates/fj-core/src/algorithms/intersect/face_face.rs deleted file mode 100644 index eb60b7ebd..000000000 --- a/crates/fj-core/src/algorithms/intersect/face_face.rs +++ /dev/null @@ -1,150 +0,0 @@ -use fj_interop::ext::ArrayExt; -use iter_fixed::IntoIteratorFixed; - -use crate::{geometry::SurfacePath, objects::Face}; - -use super::{CurveFaceIntersection, SurfaceSurfaceIntersection}; - -/// An intersection between two faces -#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct FaceFaceIntersection { - /// The intersection curves - /// - /// These curves correspond to the input faces, each being the local - /// representation of the intersection on the respective face's surface. - /// - /// They both represent the same global curve. - pub intersection_curves: [SurfacePath; 2], - - /// The interval of this intersection, in curve coordinates - /// - /// These curve coordinates apply to both intersection curves equally. - pub intersection_intervals: CurveFaceIntersection, -} - -impl FaceFaceIntersection { - /// Compute the intersections between two faces - pub fn compute(faces: [&Face; 2]) -> Option { - let surfaces = faces.map(|face| face.surface().clone()); - - let intersection_curves = - match SurfaceSurfaceIntersection::compute(surfaces) { - Some(intersection) => intersection.intersection_curves, - None => return None, - }; - - let curve_face_intersections = intersection_curves - .each_ref_ext() - .into_iter_fixed() - .zip(faces) - .map(|(curve, face)| CurveFaceIntersection::compute(curve, face)) - .collect::<[_; 2]>(); - - let intersection_intervals = { - let [a, b] = curve_face_intersections; - a.merge(&b) - }; - - if intersection_intervals.is_empty() { - return None; - } - - Some(Self { - intersection_curves, - intersection_intervals, - }) - } -} - -#[cfg(test)] -mod tests { - use pretty_assertions::assert_eq; - - use crate::{ - algorithms::intersect::CurveFaceIntersection, - geometry::SurfacePath, - objects::{Cycle, Face}, - operations::{ - build::{BuildCycle, BuildFace}, - update::{UpdateFace, UpdateRegion}, - }, - Core, - }; - - use super::FaceFaceIntersection; - - #[test] - fn compute_no_intersection() { - let mut core = Core::new(); - - #[rustfmt::skip] - let points = [ - [1., 1.], - [2., 1.], - [2., 2.], - [1., 2.], - ]; - let [a, b] = [ - core.layers.objects.surfaces.xy_plane(), - core.layers.objects.surfaces.xz_plane(), - ] - .map(|surface| { - Face::unbound(surface, &mut core).update_region( - |region, core| { - region.update_exterior( - |_, core| Cycle::polygon(points, core), - core, - ) - }, - &mut core, - ) - }); - - let intersection = FaceFaceIntersection::compute([&a, &b]); - assert!(intersection.is_none()); - } - - #[test] - fn compute_one_intersection() { - let mut core = Core::new(); - - #[rustfmt::skip] - let points = [ - [-1., -1.], - [ 1., -1.], - [ 1., 1.], - [-1., 1.], - ]; - let surfaces = [ - core.layers.objects.surfaces.xy_plane(), - core.layers.objects.surfaces.xz_plane(), - ]; - let [a, b] = surfaces.clone().map(|surface| { - Face::unbound(surface, &mut core).update_region( - |region, core| { - region.update_exterior( - |_, core| Cycle::polygon(points, core), - core, - ) - }, - &mut core, - ) - }); - - let intersection = FaceFaceIntersection::compute([&a, &b]); - - let expected_curves = surfaces.map(|_| { - let (path, _) = SurfacePath::line_from_points([[0., 0.], [1., 0.]]); - path - }); - let expected_intervals = - CurveFaceIntersection::from_intervals([[[-1.], [1.]]]); - assert_eq!( - intersection, - Some(FaceFaceIntersection { - intersection_curves: expected_curves, - intersection_intervals: expected_intervals - }) - ); - } -} diff --git a/crates/fj-core/src/algorithms/intersect/mod.rs b/crates/fj-core/src/algorithms/intersect/mod.rs index 49754eeb3..8a841efaa 100644 --- a/crates/fj-core/src/algorithms/intersect/mod.rs +++ b/crates/fj-core/src/algorithms/intersect/mod.rs @@ -2,23 +2,18 @@ pub mod face_point; pub mod ray_edge; -pub mod ray_face; pub mod ray_segment; mod curve_edge; mod curve_face; -mod face_face; mod line_segment; -mod surface_surface; use fj_math::{Point, Vector}; pub use self::{ curve_edge::CurveEdgeIntersection, curve_face::{CurveFaceIntersection, CurveFaceIntersectionInterval}, - face_face::FaceFaceIntersection, line_segment::LineSegmentIntersection, - surface_surface::SurfaceSurfaceIntersection, }; /// Compute the intersection between a tuple of objects diff --git a/crates/fj-core/src/algorithms/intersect/ray_face.rs b/crates/fj-core/src/algorithms/intersect/ray_face.rs deleted file mode 100644 index 23e70a385..000000000 --- a/crates/fj-core/src/algorithms/intersect/ray_face.rs +++ /dev/null @@ -1,409 +0,0 @@ -//! Intersection between a ray and a face, in 3D - -use fj_math::{Plane, Point, Scalar}; - -use crate::{ - algorithms::intersect::face_point::FacePointIntersection, - geometry::GlobalPath, - objects::{Face, HalfEdge}, - storage::Handle, -}; - -use super::{HorizontalRayToTheRight, Intersect}; - -impl Intersect for (&HorizontalRayToTheRight<3>, &Face) { - type Intersection = RayFaceIntersection; - - fn intersect(self) -> Option { - let (ray, face) = self; - - let plane = match face.surface().geometry().u { - GlobalPath::Circle(_) => todo!( - "Casting a ray against a swept circle is not supported yet" - ), - GlobalPath::Line(line) => Plane::from_parametric( - line.origin(), - line.direction(), - face.surface().geometry().v, - ), - }; - - if plane.is_parallel_to_vector(&ray.direction()) { - let a = plane.origin(); - let b = plane.origin() + plane.u(); - let c = plane.origin() + plane.v(); - let d = ray.origin; - - let [a, b, c, d] = [a, b, c, d] - .map(|point| [point.x, point.y, point.z]) - .map(|point| point.map(Scalar::into_f64)) - .map(|[x, y, z]| robust::Coord3D { x, y, z }); - - if robust::orient3d(a, b, c, d) == 0. { - return Some(RayFaceIntersection::RayHitsFaceAndAreParallel); - } else { - return None; - } - } - - // The pattern in this assertion resembles `ax*by = ay*bx`, which holds - // true if the vectors `a = (ax, ay)` and `b = (bx, by)` are parallel. - // - // We're looking at the plane's direction vectors here, but we're - // ignoring their x-components. By doing that, we're essentially - // projecting those vectors into the yz-plane. - // - // This means that the following assertion verifies that the projections - // of the plane's direction vectors into the yz-plane are not parallel. - // If they were, then the plane could only be parallel to the x-axis, - // and thus our ray. - // - // We already handled the case of the ray and plane being parallel - // above. The following assertion should thus never be triggered. - assert_ne!( - plane.u().y * plane.v().z, - plane.u().z * plane.v().y, - "Plane and ray are parallel; should have been ruled out previously" - ); - - // Let's figure out the intersection between the ray and the plane. - let (t, u, v) = { - // The following math would get *very* unwieldy with those - // full-length variable names. Let's define some short-hands. - let orx = ray.origin.x; - let ory = ray.origin.y; - let orz = ray.origin.z; - let opx = plane.origin().x; - let opy = plane.origin().y; - let opz = plane.origin().z; - let d1x = plane.u().x; - let d1y = plane.u().y; - let d1z = plane.u().z; - let d2x = plane.v().x; - let d2y = plane.v().y; - let d2z = plane.v().z; - - // Let's figure out where the intersection between the ray and the - // plane is. By equating the parametric equations of the ray and the - // plane, we get a vector equation, which in turn gives us a system - // of three equations with three unknowns: `t` (for the ray) and - // `u`/`v` (for the plane). - // - // Since the ray's direction vector is `(1, 0, 0)`, it works out - // such that `t` is not in the equations for y and z, meaning we can - // solve those equations for `u` and `v` independently. - // - // By doing some math, we get the following solutions: - let v = (d1y * (orz - opz) + (opy - ory) * d1z) - / (d1y * d2z - d2y * d1z); - let u = (ory - opy - d2y * v) / d1y; - let t = opx - orx + d1x * u + d2x * v; - - (t, u, v) - }; - - if t < Scalar::ZERO { - // Ray points away from plane. - return None; - } - - let point = Point::from([u, v]); - let intersection = match (face, &point).intersect()? { - FacePointIntersection::PointIsInsideFace => { - RayFaceIntersection::RayHitsFace - } - FacePointIntersection::PointIsOnEdge(edge) => { - RayFaceIntersection::RayHitsEdge(edge) - } - FacePointIntersection::PointIsOnVertex(vertex) => { - RayFaceIntersection::RayHitsVertex(vertex) - } - }; - - Some(intersection) - } -} - -/// A hit between a ray and a face -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum RayFaceIntersection { - /// The ray hits the face itself - RayHitsFace, - - /// The ray is parallel to the face - RayHitsFaceAndAreParallel, - - /// The ray hits an edge - RayHitsEdge(Handle), - - /// The ray hits a vertex - RayHitsVertex(Point<2>), -} - -#[cfg(test)] -mod tests { - use fj_math::Point; - - use crate::{ - algorithms::intersect::{ - ray_face::RayFaceIntersection, HorizontalRayToTheRight, Intersect, - }, - objects::{Cycle, Face}, - operations::{ - build::{BuildCycle, BuildFace}, - transform::TransformObject, - update::{UpdateFace, UpdateRegion}, - }, - Core, - }; - - #[test] - fn ray_misses_whole_surface() { - let mut core = Core::new(); - - let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - - let face = - Face::unbound(core.layers.objects.surfaces.yz_plane(), &mut core) - .update_region( - |region, core| { - region.update_exterior( - |_, core| { - Cycle::polygon( - [ - [-1., -1.], - [1., -1.], - [1., 1.], - [-1., 1.], - ], - core, - ) - }, - core, - ) - }, - &mut core, - ); - let face = face.translate([-1., 0., 0.], &mut core); - - assert_eq!((&ray, &face).intersect(), None); - } - - #[test] - fn ray_hits_face() { - let mut core = Core::new(); - - let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - - let face = - Face::unbound(core.layers.objects.surfaces.yz_plane(), &mut core) - .update_region( - |region, core| { - region.update_exterior( - |_, core| { - Cycle::polygon( - [ - [-1., -1.], - [1., -1.], - [1., 1.], - [-1., 1.], - ], - core, - ) - }, - core, - ) - }, - &mut core, - ); - let face = face.translate([1., 0., 0.], &mut core); - - assert_eq!( - (&ray, &face).intersect(), - Some(RayFaceIntersection::RayHitsFace) - ); - } - - #[test] - fn ray_hits_surface_but_misses_face() { - let mut core = Core::new(); - - let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - - let face = - Face::unbound(core.layers.objects.surfaces.yz_plane(), &mut core) - .update_region( - |region, core| { - region.update_exterior( - |_, core| { - Cycle::polygon( - [ - [-1., -1.], - [1., -1.], - [1., 1.], - [-1., 1.], - ], - core, - ) - }, - core, - ) - }, - &mut core, - ); - let face = face.translate([0., 0., 2.], &mut core); - - assert_eq!((&ray, &face).intersect(), None); - } - - #[test] - fn ray_hits_edge() { - let mut core = Core::new(); - - let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - - let face = - Face::unbound(core.layers.objects.surfaces.yz_plane(), &mut core) - .update_region( - |region, core| { - region.update_exterior( - |_, core| { - Cycle::polygon( - [ - [-1., -1.], - [1., -1.], - [1., 1.], - [-1., 1.], - ], - core, - ) - }, - core, - ) - }, - &mut core, - ); - let face = face.translate([1., 1., 0.], &mut core); - - let edge = face - .region() - .exterior() - .half_edges() - .iter() - .find(|edge| edge.start_position() == Point::from([-1., 1.])) - .unwrap(); - assert_eq!( - (&ray, &face).intersect(), - Some(RayFaceIntersection::RayHitsEdge(edge.clone())) - ); - } - - #[test] - fn ray_hits_vertex() { - let mut core = Core::new(); - - let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - - let face = - Face::unbound(core.layers.objects.surfaces.yz_plane(), &mut core) - .update_region( - |region, core| { - region.update_exterior( - |_, core| { - Cycle::polygon( - [ - [-1., -1.], - [1., -1.], - [1., 1.], - [-1., 1.], - ], - core, - ) - }, - core, - ) - }, - &mut core, - ); - let face = face.translate([1., 1., 1.], &mut core); - - let vertex = face - .region() - .exterior() - .half_edges() - .iter() - .find(|edge| edge.start_position() == Point::from([-1., -1.])) - .map(|edge| edge.start_position()) - .unwrap(); - assert_eq!( - (&ray, &face).intersect(), - Some(RayFaceIntersection::RayHitsVertex(vertex)) - ); - } - - #[test] - fn ray_is_parallel_to_surface_and_hits() { - let mut core = Core::new(); - - let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - - let face = - Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core) - .update_region( - |region, core| { - region.update_exterior( - |_, core| { - Cycle::polygon( - [ - [-1., -1.], - [1., -1.], - [1., 1.], - [-1., 1.], - ], - core, - ) - }, - core, - ) - }, - &mut core, - ); - - assert_eq!( - (&ray, &face).intersect(), - Some(RayFaceIntersection::RayHitsFaceAndAreParallel) - ); - } - - #[test] - fn ray_is_parallel_to_surface_and_misses() { - let mut core = Core::new(); - - let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - - let face = - Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core) - .update_region( - |region, core| { - region.update_exterior( - |_, core| { - Cycle::polygon( - [ - [-1., -1.], - [1., -1.], - [1., 1.], - [-1., 1.], - ], - core, - ) - }, - core, - ) - }, - &mut core, - ); - let face = face.translate([0., 0., 1.], &mut core); - - assert_eq!((&ray, &face).intersect(), None); - } -} diff --git a/crates/fj-core/src/algorithms/intersect/surface_surface.rs b/crates/fj-core/src/algorithms/intersect/surface_surface.rs deleted file mode 100644 index a5be37894..000000000 --- a/crates/fj-core/src/algorithms/intersect/surface_surface.rs +++ /dev/null @@ -1,113 +0,0 @@ -use fj_math::{Line, Plane, Point, Scalar}; - -use crate::{ - geometry::{GlobalPath, SurfacePath}, - objects::Surface, - storage::Handle, -}; - -/// The intersection between two surfaces -#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct SurfaceSurfaceIntersection { - /// The intersection curves - pub intersection_curves: [SurfacePath; 2], -} - -impl SurfaceSurfaceIntersection { - /// Compute the intersection between two surfaces - pub fn compute(surfaces: [Handle; 2]) -> Option { - // Algorithm from Real-Time Collision Detection by Christer Ericson. See - // section 5.4.4, Intersection of Two Planes. - // - // Adaptations were made to get the intersection curves in local - // coordinates for each surface. - - let planes = surfaces.map(|surface| plane_from_surface(&surface)); - - let [(a_distance, a_normal), (b_distance, b_normal)] = - planes.map(|plane| plane.constant_normal_form()); - - let direction = a_normal.cross(&b_normal); - - let denom = direction.dot(&direction); - if denom == Scalar::ZERO { - // Comparing `denom` against zero looks fishy. It's probably better - // to compare it against an epsilon value, but I don't know how - // large that epsilon should be. - // - // I'll just leave it like that, until we had the opportunity to - // collect some experience with this code. - // - @hannobraun - return None; - } - - let origin = (b_normal * a_distance - a_normal * b_distance) - .cross(&direction) - / denom; - let origin = Point { coords: origin }; - - let line = Line::from_origin_and_direction(origin, direction); - - let curves = - planes.map(|plane| SurfacePath::Line(plane.project_line(&line))); - - Some(Self { - intersection_curves: curves, - }) - } -} - -fn plane_from_surface(surface: &Surface) -> Plane { - let (line, path) = { - let line = match surface.geometry().u { - GlobalPath::Line(line) => line, - _ => todo!("Only plane-plane intersection is currently supported."), - }; - - (line, surface.geometry().v) - }; - - Plane::from_parametric(line.origin(), line.direction(), path) -} - -#[cfg(test)] -mod tests { - use fj_math::Transform; - use pretty_assertions::assert_eq; - - use crate::{ - geometry::SurfacePath, operations::transform::TransformObject, Core, - }; - - use super::SurfaceSurfaceIntersection; - - #[test] - fn plane_plane() { - let mut core = Core::new(); - - let xy = core.layers.objects.surfaces.xy_plane(); - let xz = core.layers.objects.surfaces.xz_plane(); - - // Coincident and parallel planes don't have an intersection curve. - assert_eq!( - SurfaceSurfaceIntersection::compute([ - xy.clone(), - xy.clone().transform( - &Transform::translation([0., 0., 1.],), - &mut core - ) - ],), - None, - ); - - let expected_xy = SurfacePath::u_axis(); - let expected_xz = SurfacePath::u_axis(); - - assert_eq!( - SurfaceSurfaceIntersection::compute([xy, xz],), - Some(SurfaceSurfaceIntersection { - intersection_curves: [expected_xy, expected_xz], - }) - ); - } -}