diff --git a/crates/fj-core/src/algorithms/approx/edge.rs b/crates/fj-core/src/algorithms/approx/edge.rs index a3310ece7..e8d4c5f0b 100644 --- a/crates/fj-core/src/algorithms/approx/edge.rs +++ b/crates/fj-core/src/algorithms/approx/edge.rs @@ -10,12 +10,12 @@ use std::collections::BTreeMap; use fj_math::Point; use crate::{ - geometry::{GlobalPath, SurfacePath}, + geometry::{BoundaryOnCurve, GlobalPath, SurfacePath}, objects::{GlobalEdge, HalfEdge, Surface, Vertex}, storage::{Handle, ObjectId}, }; -use super::{path::RangeOnPath, Approx, ApproxPoint, Tolerance}; +use super::{Approx, ApproxPoint, Tolerance}; impl Approx for (&HalfEdge, &Surface) { type Approximation = HalfEdgeApprox; @@ -28,9 +28,6 @@ impl Approx for (&HalfEdge, &Surface) { ) -> Self::Approximation { let (half_edge, surface) = self; - let boundary = half_edge.boundary(); - let range = RangeOnPath { boundary }; - let position_surface = half_edge.start_position(); let position_global = match cache.get_position(half_edge.start_vertex()) { @@ -87,20 +84,22 @@ impl Approx for (&HalfEdge, &Surface) { // // Only item 2. is something we can do right here. Item 1. requires // a change to the object graph. - let cached_approx = - cache.get_edge(half_edge.global_form().clone(), range); + let cached_approx = cache.get_edge( + half_edge.global_form().clone(), + half_edge.boundary(), + ); let approx = match cached_approx { Some(approx) => approx, None => { let approx = approx_edge( &half_edge.path(), surface, - range, + half_edge.boundary(), tolerance, ); cache.insert_edge( half_edge.global_form().clone(), - range, + half_edge.boundary(), approx, ) } @@ -148,7 +147,7 @@ impl HalfEdgeApprox { fn approx_edge( path: &SurfacePath, surface: &Surface, - range: RangeOnPath, + boundary: BoundaryOnCurve, tolerance: impl Into, ) -> GlobalEdgeApprox { // There are different cases of varying complexity. Circles are the hard @@ -164,7 +163,7 @@ fn approx_edge( ) } (SurfacePath::Circle(_), GlobalPath::Line(_)) => { - (path, range) + (path, boundary) .approx_with_cache(tolerance, &mut ()) .into_iter() .map(|(point_curve, point_surface)| { @@ -192,7 +191,7 @@ fn approx_edge( } (SurfacePath::Line(line), _) => { let range_u = - RangeOnPath::from(range.boundary.map(|point_curve| { + BoundaryOnCurve::from(boundary.inner.map(|point_curve| { [path.point_from_path_coords(point_curve).u] })); @@ -224,7 +223,7 @@ fn approx_edge( /// A cache for results of an approximation #[derive(Default)] pub struct EdgeCache { - edge_approx: BTreeMap<(ObjectId, RangeOnPath), GlobalEdgeApprox>, + edge_approx: BTreeMap<(ObjectId, BoundaryOnCurve), GlobalEdgeApprox>, vertex_approx: BTreeMap>, } @@ -238,15 +237,15 @@ impl EdgeCache { pub fn get_edge( &self, handle: Handle, - range: RangeOnPath, + boundary: BoundaryOnCurve, ) -> Option { - if let Some(approx) = self.edge_approx.get(&(handle.id(), range)) { + if let Some(approx) = self.edge_approx.get(&(handle.id(), boundary)) { return Some(approx.clone()); } if let Some(approx) = - self.edge_approx.get(&(handle.id(), range.reverse())) + self.edge_approx.get(&(handle.id(), boundary.reverse())) { - // If we have a cache entry for the reverse range, we need to use + // If we have a cache entry for the reverse boundary, we need to use // that too! return Some(approx.clone().reverse()); } @@ -258,11 +257,11 @@ impl EdgeCache { pub fn insert_edge( &mut self, handle: Handle, - range: RangeOnPath, + boundary: BoundaryOnCurve, approx: GlobalEdgeApprox, ) -> GlobalEdgeApprox { self.edge_approx - .insert((handle.id(), range), approx.clone()) + .insert((handle.id(), boundary), approx.clone()) .unwrap_or(approx) } @@ -303,8 +302,8 @@ mod tests { use pretty_assertions::assert_eq; use crate::{ - algorithms::approx::{path::RangeOnPath, Approx, ApproxPoint}, - geometry::{GlobalPath, SurfaceGeometry}, + algorithms::approx::{Approx, ApproxPoint}, + geometry::{BoundaryOnCurve, GlobalPath, SurfaceGeometry}, objects::{HalfEdge, Surface}, operations::BuildHalfEdge, services::Services, @@ -346,7 +345,7 @@ mod tests { let mut services = Services::new(); let path = GlobalPath::circle_from_radius(1.); - let range = RangeOnPath::from([[0.], [TAU]]); + let boundary = BoundaryOnCurve::from([[0.], [TAU]]); let surface = Surface::new(SurfaceGeometry { u: path, @@ -354,14 +353,14 @@ mod tests { }); let half_edge = HalfEdge::line_segment( [[0., 1.], [TAU, 1.]], - Some(range.boundary), + Some(boundary.inner), &mut services, ); let tolerance = 1.; let approx = (&half_edge, &surface).approx(tolerance); - let expected_approx = (path, range) + let expected_approx = (path, boundary) .approx(tolerance) .into_iter() .map(|(point_local, _)| { @@ -386,7 +385,7 @@ mod tests { let approx = (&half_edge, surface.deref()).approx(tolerance); let expected_approx = - (&half_edge.path(), RangeOnPath::from([[0.], [TAU]])) + (&half_edge.path(), BoundaryOnCurve::from([[0.], [TAU]])) .approx(tolerance) .into_iter() .map(|(_, point_surface)| { diff --git a/crates/fj-core/src/algorithms/approx/path.rs b/crates/fj-core/src/algorithms/approx/path.rs index 981bad08a..b06415248 100644 --- a/crates/fj-core/src/algorithms/approx/path.rs +++ b/crates/fj-core/src/algorithms/approx/path.rs @@ -32,11 +32,11 @@ use std::iter; use fj_math::{Circle, Point, Scalar, Sign}; -use crate::geometry::{GlobalPath, SurfacePath}; +use crate::geometry::{BoundaryOnCurve, GlobalPath, SurfacePath}; use super::{Approx, Tolerance}; -impl Approx for (&SurfacePath, RangeOnPath) { +impl Approx for (&SurfacePath, BoundaryOnCurve) { type Approximation = Vec<(Point<1>, Point<2>)>; type Cache = (); @@ -56,7 +56,7 @@ impl Approx for (&SurfacePath, RangeOnPath) { } } -impl Approx for (GlobalPath, RangeOnPath) { +impl Approx for (GlobalPath, BoundaryOnCurve) { type Approximation = Vec<(Point<1>, Point<3>)>; type Cache = (); @@ -76,46 +76,21 @@ impl Approx for (GlobalPath, RangeOnPath) { } } -/// The range on which a path should be approximated -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub struct RangeOnPath { - /// The boundary of the range - pub boundary: [Point<1>; 2], -} - -impl RangeOnPath { - /// Reverse the direction of the range - pub fn reverse(self) -> Self { - let [a, b] = self.boundary; - Self { boundary: [b, a] } - } -} - -impl From<[T; 2]> for RangeOnPath -where - T: Into>, -{ - fn from(boundary: [T; 2]) -> Self { - let boundary = boundary.map(Into::into); - Self { boundary } - } -} - /// Approximate a circle /// /// `tolerance` specifies how much the approximation is allowed to deviate /// from the circle. fn approx_circle( circle: &Circle, - range: impl Into, + boundary: impl Into, tolerance: Tolerance, ) -> Vec<(Point<1>, Point)> { - let range = range.into(); + let boundary = boundary.into(); let params = PathApproxParams::for_circle(circle, tolerance); let mut points = Vec::new(); - for point_curve in params.points(range) { + for point_curve in params.points(boundary) { let point_global = circle.point_from_circle_coords(point_curve); points.push((point_curve, point_global)); } @@ -152,11 +127,11 @@ impl PathApproxParams { pub fn points( &self, - range: impl Into, + boundary: impl Into, ) -> impl Iterator> + '_ { - let range = range.into(); + let boundary = boundary.into(); - let [a, b] = range.boundary.map(|point| point.t / self.increment()); + let [a, b] = boundary.inner.map(|point| point.t / self.increment()); let direction = (b - a).sign(); let [min, max] = if a < b { [a, b] } else { [b, a] }; @@ -195,7 +170,7 @@ mod tests { use fj_math::{Circle, Point, Scalar}; - use crate::algorithms::approx::{path::RangeOnPath, Tolerance}; + use crate::algorithms::approx::{path::BoundaryOnCurve, Tolerance}; use super::PathApproxParams; @@ -245,7 +220,7 @@ mod tests { test_path([[TAU - 2.], [0.]], [2., 1.]); fn test_path( - range: impl Into, + boundary: impl Into, expected_coords: impl IntoIterator>, ) { // Choose radius and tolerance such, that we need 4 vertices to @@ -257,7 +232,7 @@ mod tests { let circle = Circle::from_center_and_radius([0., 0.], radius); let params = PathApproxParams::for_circle(&circle, tolerance); - let points = params.points(range).collect::>(); + let points = params.points(boundary).collect::>(); let expected_points = expected_coords .into_iter() diff --git a/crates/fj-core/src/algorithms/bounding_volume/edge.rs b/crates/fj-core/src/algorithms/bounding_volume/edge.rs index ceea6bf9a..50be621af 100644 --- a/crates/fj-core/src/algorithms/bounding_volume/edge.rs +++ b/crates/fj-core/src/algorithms/bounding_volume/edge.rs @@ -18,7 +18,7 @@ impl super::BoundingVolume<2> for HalfEdge { }) } SurfacePath::Line(_) => { - let points = self.boundary().map(|point_curve| { + let points = self.boundary().inner.map(|point_curve| { self.path().point_from_path_coords(point_curve) }); diff --git a/crates/fj-core/src/algorithms/intersect/curve_edge.rs b/crates/fj-core/src/algorithms/intersect/curve_edge.rs index e5678a330..934b79fe9 100644 --- a/crates/fj-core/src/algorithms/intersect/curve_edge.rs +++ b/crates/fj-core/src/algorithms/intersect/curve_edge.rs @@ -44,6 +44,7 @@ impl CurveEdgeIntersection { let edge_vertices = half_edge .boundary() + .inner .map(|point| edge_path_as_line.point_from_line_coords(point)); Segment::from_points(edge_vertices) diff --git a/crates/fj-core/src/algorithms/intersect/ray_edge.rs b/crates/fj-core/src/algorithms/intersect/ray_edge.rs index 5c5311d26..dab988e9d 100644 --- a/crates/fj-core/src/algorithms/intersect/ray_edge.rs +++ b/crates/fj-core/src/algorithms/intersect/ray_edge.rs @@ -26,6 +26,7 @@ impl Intersect for (&HorizontalRayToTheRight<2>, &Handle) { let points = edge .boundary() + .inner .map(|point| line.point_from_line_coords(point)); let segment = Segment::from_points(points); diff --git a/crates/fj-core/src/algorithms/sweep/edge.rs b/crates/fj-core/src/algorithms/sweep/edge.rs index d0510fe4b..929011469 100644 --- a/crates/fj-core/src/algorithms/sweep/edge.rs +++ b/crates/fj-core/src/algorithms/sweep/edge.rs @@ -47,7 +47,7 @@ impl Sweep for (&HalfEdge, &Handle, &Surface, Option) { // Let's figure out the surface coordinates of the edge vertices. let surface_points = { - let [a, b] = edge.boundary(); + let [a, b] = edge.boundary().inner; [ [a.t, Scalar::ZERO], @@ -65,7 +65,7 @@ impl Sweep for (&HalfEdge, &Handle, &Surface, Option) { // Now, the boundaries of each edge. let boundaries = { - let [a, b] = edge.boundary(); + let [a, b] = edge.boundary().inner; let [c, d] = [0., 1.].map(|coord| Point::from([coord])); [[a, b], [c, d], [b, a], [d, c]] diff --git a/crates/fj-core/src/geometry/boundary.rs b/crates/fj-core/src/geometry/boundary.rs new file mode 100644 index 000000000..c38a02563 --- /dev/null +++ b/crates/fj-core/src/geometry/boundary.rs @@ -0,0 +1,26 @@ +use fj_math::Point; + +/// A boundary on a curve +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct BoundaryOnCurve { + /// The raw representation of the boundary + pub inner: [Point<1>; 2], +} + +impl BoundaryOnCurve { + /// Reverse the direction of the boundary + pub fn reverse(self) -> Self { + let [a, b] = self.inner; + Self { inner: [b, a] } + } +} + +impl From<[T; 2]> for BoundaryOnCurve +where + T: Into>, +{ + fn from(boundary: [T; 2]) -> Self { + let inner = boundary.map(Into::into); + Self { inner } + } +} diff --git a/crates/fj-core/src/geometry/mod.rs b/crates/fj-core/src/geometry/mod.rs index 45dca87b6..cb5a1cb36 100644 --- a/crates/fj-core/src/geometry/mod.rs +++ b/crates/fj-core/src/geometry/mod.rs @@ -1,9 +1,11 @@ //! Types that are tied to objects, but aren't objects themselves +mod boundary; mod path; mod surface; pub use self::{ + boundary::BoundaryOnCurve, path::{GlobalPath, SurfacePath}, surface::SurfaceGeometry, }; diff --git a/crates/fj-core/src/objects/kinds/cycle.rs b/crates/fj-core/src/objects/kinds/cycle.rs index 61b9f401e..bc7558307 100644 --- a/crates/fj-core/src/objects/kinds/cycle.rs +++ b/crates/fj-core/src/objects/kinds/cycle.rs @@ -82,7 +82,7 @@ impl Cycle { .next() .expect("Invalid cycle: expected at least one half-edge"); - let [a, b] = first.boundary(); + let [a, b] = first.boundary().inner; let edge_direction_positive = a < b; let circle = match first.path() { diff --git a/crates/fj-core/src/objects/kinds/edge.rs b/crates/fj-core/src/objects/kinds/edge.rs index d174ce009..5154fd799 100644 --- a/crates/fj-core/src/objects/kinds/edge.rs +++ b/crates/fj-core/src/objects/kinds/edge.rs @@ -1,7 +1,7 @@ use fj_math::Point; use crate::{ - geometry::SurfacePath, + geometry::{BoundaryOnCurve, SurfacePath}, objects::Vertex, storage::{Handle, HandleWrapper}, }; @@ -41,7 +41,7 @@ use crate::{ #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct HalfEdge { path: SurfacePath, - boundary: [Point<1>; 2], + boundary: BoundaryOnCurve, start_vertex: HandleWrapper, global_form: HandleWrapper, } @@ -50,13 +50,13 @@ impl HalfEdge { /// Create an instance of `HalfEdge` pub fn new( path: SurfacePath, - boundary: [Point<1>; 2], + boundary: impl Into, start_vertex: Handle, global_form: Handle, ) -> Self { Self { path, - boundary, + boundary: boundary.into(), start_vertex: start_vertex.into(), global_form: global_form.into(), } @@ -68,7 +68,7 @@ impl HalfEdge { } /// Access the boundary points of the half-edge on the curve - pub fn boundary(&self) -> [Point<1>; 2] { + pub fn boundary(&self) -> BoundaryOnCurve { self.boundary } @@ -78,7 +78,7 @@ impl HalfEdge { // `HalfEdge` "owns" its start position. There is no competing code that // could compute the surface position from slightly different data. - let [start, _] = self.boundary; + let [start, _] = self.boundary.inner; self.path.point_from_path_coords(start) } diff --git a/crates/fj-core/src/operations/build/edge.rs b/crates/fj-core/src/operations/build/edge.rs index f4f82e749..6e850e549 100644 --- a/crates/fj-core/src/operations/build/edge.rs +++ b/crates/fj-core/src/operations/build/edge.rs @@ -2,7 +2,7 @@ use fj_interop::ext::ArrayExt; use fj_math::{Arc, Point, Scalar}; use crate::{ - geometry::SurfacePath, + geometry::{BoundaryOnCurve, SurfacePath}, objects::{GlobalEdge, HalfEdge, Vertex}, operations::Insert, services::Services, @@ -13,7 +13,7 @@ pub trait BuildHalfEdge { /// Create a half-edge that is not joined to another fn unjoined( path: SurfacePath, - boundary: [Point<1>; 2], + boundary: impl Into, services: &mut Services, ) -> HalfEdge { let start_vertex = Vertex::new().insert(services); diff --git a/crates/fj-core/src/operations/join/cycle.rs b/crates/fj-core/src/operations/join/cycle.rs index e630c6d3c..4782300e2 100644 --- a/crates/fj-core/src/operations/join/cycle.rs +++ b/crates/fj-core/src/operations/join/cycle.rs @@ -1,10 +1,9 @@ use std::ops::RangeInclusive; -use fj_math::Point; use itertools::Itertools; use crate::{ - geometry::SurfacePath, + geometry::{BoundaryOnCurve, SurfacePath}, objects::{Cycle, HalfEdge}, operations::{BuildHalfEdge, Insert, UpdateCycle, UpdateHalfEdge}, services::Services, @@ -17,7 +16,9 @@ pub trait JoinCycle { #[must_use] fn add_joined_edges(&self, edges: Es, services: &mut Services) -> Self where - Es: IntoIterator, SurfacePath, [Point<1>; 2])>, + Es: IntoIterator< + Item = (Handle, SurfacePath, BoundaryOnCurve), + >, Es::IntoIter: Clone + ExactSizeIterator; /// Join the cycle to another @@ -62,7 +63,9 @@ pub trait JoinCycle { impl JoinCycle for Cycle { fn add_joined_edges(&self, edges: Es, services: &mut Services) -> Self where - Es: IntoIterator, SurfacePath, [Point<1>; 2])>, + Es: IntoIterator< + Item = (Handle, SurfacePath, BoundaryOnCurve), + >, Es::IntoIter: Clone + ExactSizeIterator, { self.add_half_edges(edges.into_iter().circular_tuple_windows().map( diff --git a/crates/fj-core/src/operations/reverse/cycle.rs b/crates/fj-core/src/operations/reverse/cycle.rs index 1f71c845a..62cac2ef1 100644 --- a/crates/fj-core/src/operations/reverse/cycle.rs +++ b/crates/fj-core/src/operations/reverse/cycle.rs @@ -11,14 +11,9 @@ impl Reverse for Cycle { let mut edges = self .half_edge_pairs() .map(|(current, next)| { - let boundary = { - let [a, b] = current.boundary(); - [b, a] - }; - HalfEdge::new( current.path(), - boundary, + current.boundary().reverse(), next.start_vertex().clone(), current.global_form().clone(), ) diff --git a/crates/fj-core/src/validate/cycle.rs b/crates/fj-core/src/validate/cycle.rs index 9aa5852a4..a837c8843 100644 --- a/crates/fj-core/src/validate/cycle.rs +++ b/crates/fj-core/src/validate/cycle.rs @@ -65,7 +65,7 @@ impl CycleValidationError { ) { for (first, second) in cycle.half_edge_pairs() { let end_of_first = { - let [_, end] = first.boundary(); + let [_, end] = first.boundary().inner; first.path().point_from_path_coords(end) }; let start_of_second = second.start_position(); diff --git a/crates/fj-core/src/validate/edge.rs b/crates/fj-core/src/validate/edge.rs index 3b4efbf2e..9516664b6 100644 --- a/crates/fj-core/src/validate/edge.rs +++ b/crates/fj-core/src/validate/edge.rs @@ -54,7 +54,7 @@ impl HalfEdgeValidationError { config: &ValidationConfig, errors: &mut Vec, ) { - let [back_position, front_position] = half_edge.boundary(); + let [back_position, front_position] = half_edge.boundary().inner; let distance = (back_position - front_position).magnitude(); if distance < config.distinct_min_distance { diff --git a/crates/fj-core/src/validate/shell.rs b/crates/fj-core/src/validate/shell.rs index 5e42e65f1..c324f24d4 100644 --- a/crates/fj-core/src/validate/shell.rs +++ b/crates/fj-core/src/validate/shell.rs @@ -73,8 +73,8 @@ fn distances( percent: f64, (edge, surface): (&Handle, SurfaceGeometry), ) -> Point<3> { - let boundary = edge.boundary(); - let path_coords = boundary[0] + (boundary[1] - boundary[0]) * percent; + let [start, end] = edge.boundary().inner; + let path_coords = start + (end - start) * percent; let surface_coords = edge.path().point_from_path_coords(path_coords); surface.point_from_surface_coords(surface_coords) }