diff --git a/crates/fj-kernel/src/algorithms/approx/edge.rs b/crates/fj-kernel/src/algorithms/approx/edge.rs index 1e5ba66c09..76988c7c47 100644 --- a/crates/fj-kernel/src/algorithms/approx/edge.rs +++ b/crates/fj-kernel/src/algorithms/approx/edge.rs @@ -377,15 +377,8 @@ mod tests { let mut services = Services::new(); let surface = services.objects.surfaces.xz_plane(); - let half_edge = { - let mut half_edge = PartialHalfEdge::new(&mut services.objects); - - half_edge.update_as_circle_from_radius(1.); - - half_edge - .build(&mut services.objects) - .insert(&mut services.objects) - }; + let half_edge = PartialHalfEdge::make_circle(1., &mut services.objects) + .build(&mut services.objects); let tolerance = 1.; let approx = (&half_edge, surface.deref()).approx(tolerance); diff --git a/crates/fj-kernel/src/algorithms/sweep/edge.rs b/crates/fj-kernel/src/algorithms/sweep/edge.rs index 42da26e0f4..5ed392b5a7 100644 --- a/crates/fj-kernel/src/algorithms/sweep/edge.rs +++ b/crates/fj-kernel/src/algorithms/sweep/edge.rs @@ -1,12 +1,11 @@ use fj_interop::{ext::ArrayExt, mesh::Color}; use fj_math::{Point, Scalar, Vector}; -use itertools::Itertools; use crate::{ builder::{CycleBuilder, HalfEdgeBuilder}, insert::Insert, objects::{Face, HalfEdge, Objects, Surface, Vertex}, - partial::{PartialFace, PartialObject}, + partial::{PartialFace, PartialHalfEdge, PartialObject}, services::Service, storage::Handle, }; @@ -36,30 +35,27 @@ impl Sweep for (Handle, &Handle, &Surface, Color) { (edge.curve(), surface).sweep_with_cache(path, cache, objects), ); - // Now we're ready to create the edges. - let mut edge_bottom = face.exterior.write().add_half_edge(objects); - let mut edge_up = face.exterior.write().add_half_edge(objects); - let mut edge_top = face.exterior.write().add_half_edge(objects); - let mut edge_down = face.exterior.write().add_half_edge(objects); - - // Those edges aren't fully defined yet. We'll do that shortly, but - // first we have to figure a few things out. - // - // Let's start with the global vertices and edges. - let (global_vertices, global_edges) = { + // Next, we need to define the boundaries of the face. Let's start with + // the global vertices and edges. + let (vertices, global_edges) = { let [a, b] = [edge.start_vertex(), next_vertex].map(Clone::clone); - let (edge_right, [_, c]) = + let (edge_up, [_, c]) = b.clone().sweep_with_cache(path, cache, objects); - let (edge_left, [_, d]) = + let (edge_down, [_, d]) = a.clone().sweep_with_cache(path, cache, objects); ( [a, b, c, d], - [edge.global_form().clone(), edge_right, edge_left], + [ + Some(edge.global_form().clone()), + Some(edge_up), + Some(edge_down), + None, + ], ) }; - // Next, let's figure out the surface coordinates of the edge vertices. + // Let's figure out the surface coordinates of the edge vertices. let surface_points = { let [a, b] = edge.boundary(); @@ -71,6 +67,11 @@ impl Sweep for (Handle, &Handle, &Surface, Color) { ] .map(Point::from) }; + let surface_points_next = { + let mut points = surface_points; + points.rotate_left(1); + points + }; // Now, the boundaries of each edge. let boundaries = { @@ -80,51 +81,24 @@ impl Sweep for (Handle, &Handle, &Surface, Color) { [[a, b], [c, d], [b, a], [d, c]] }; - // Armed with all of that, we can set the edge's vertices. - [ - edge_bottom.write(), - edge_up.write(), - edge_top.write(), - edge_down.write(), - ] - .zip_ext(boundaries) - .zip_ext(global_vertices) - .map(|((mut half_edge, boundary), global_vertex)| { - for (a, b) in half_edge.boundary.each_mut_ext().zip_ext(boundary) { - *a = Some(b); - } - - // Writing to the start vertices is enough. Neighboring half- - // edges share surface vertices, so writing the start vertex of - // each half-edge writes to all vertices. - half_edge.start_vertex = global_vertex; - }); - - // With the vertices set, we can now update the curves. - // - // Those are all line segments. For the bottom and top curve, because - // even if the original edge was a circle, it's still going to be a line - // when projected into the new surface. For the side edges, because - // we're sweeping along a straight path. - for ((mut half_edge, start), (_, end)) in [ - edge_bottom.clone(), - edge_up.clone(), - edge_top.clone(), - edge_down.clone(), - ] - .zip_ext(surface_points) - .into_iter() - .circular_tuple_windows() - { - half_edge.write().update_as_line_segment(start, end); - } - - // Finally, we can make sure that all edges refer to the correct global - // edges. - [edge_bottom.write(), edge_up.write(), edge_down.write()] + // Armed with all of that, we're ready to create the edges. + let [_edge_bottom, _edge_up, edge_top, _edge_down] = boundaries + .zip_ext(surface_points) + .zip_ext(surface_points_next) + .zip_ext(vertices) .zip_ext(global_edges) - .map(|(mut half_edge, global_edge)| { - half_edge.global_form = global_edge; + .map(|((((boundary, start), end), start_vertex), global_edge)| { + let half_edge = PartialHalfEdge::make_line_segment( + [start, end], + Some(boundary), + Some(start_vertex), + global_edge, + objects, + ); + + face.exterior.write().add_half_edge(half_edge.clone()); + + half_edge }); // And we're done creating the face! All that's left to do is build our diff --git a/crates/fj-kernel/src/builder/cycle.rs b/crates/fj-kernel/src/builder/cycle.rs index 62aadad7f7..2ee669fec0 100644 --- a/crates/fj-kernel/src/builder/cycle.rs +++ b/crates/fj-kernel/src/builder/cycle.rs @@ -1,9 +1,8 @@ use fj_math::Point; -use itertools::Itertools; use crate::{ objects::{HalfEdge, Objects}, - partial::{Partial, PartialCycle}, + partial::{Partial, PartialCycle, PartialHalfEdge}, services::Service, }; @@ -20,10 +19,7 @@ pub trait CycleBuilder { /// /// If this is the first half-edge being added, it is connected to itself, /// meaning its front and back vertices are the same. - fn add_half_edge( - &mut self, - objects: &mut Service, - ) -> Partial; + fn add_half_edge(&mut self, half_edge: Partial); /// Update cycle as a polygon from the provided points fn update_as_polygon_from_points( @@ -33,7 +29,7 @@ pub trait CycleBuilder { ) -> O::SameSize> where O: ObjectArgument

, - P: Into>; + P: Clone + Into>; /// Connect the cycles to the provided half-edges /// @@ -51,13 +47,8 @@ pub trait CycleBuilder { } impl CycleBuilder for PartialCycle { - fn add_half_edge( - &mut self, - objects: &mut Service, - ) -> Partial { - let half_edge = Partial::new(objects); - self.half_edges.push(half_edge.clone()); - half_edge + fn add_half_edge(&mut self, half_edge: Partial) { + self.half_edges.push(half_edge); } fn update_as_polygon_from_points( @@ -67,23 +58,21 @@ impl CycleBuilder for PartialCycle { ) -> O::SameSize> where O: ObjectArgument

, - P: Into>, + P: Clone + Into>, { - let mut start_positions = Vec::new(); - let half_edges = points.map(|point| { - start_positions.push(point.into()); - self.add_half_edge(objects) - }); + points.map_with_next(|start, end| { + let half_edge = PartialHalfEdge::make_line_segment( + [start, end], + None, + None, + None, + objects, + ); - for ((start, end), half_edge) in start_positions - .into_iter() - .circular_tuple_windows() - .zip(&mut self.half_edges) - { - half_edge.write().update_as_line_segment(start, end); - } + self.add_half_edge(half_edge.clone()); - half_edges + half_edge + }) } fn connect_to_edges( @@ -95,8 +84,11 @@ impl CycleBuilder for PartialCycle { O: ObjectArgument>, { edges.map_with_prev(|_, prev| { - let mut edge = self.add_half_edge(objects); + let mut edge: Partial = Partial::new(objects); edge.write().start_vertex = prev.read().start_vertex.clone(); + + self.add_half_edge(edge.clone()); + edge }) } diff --git a/crates/fj-kernel/src/builder/edge.rs b/crates/fj-kernel/src/builder/edge.rs index a214a232ee..7eb5625b78 100644 --- a/crates/fj-kernel/src/builder/edge.rs +++ b/crates/fj-kernel/src/builder/edge.rs @@ -1,110 +1,123 @@ use fj_interop::ext::ArrayExt; -use fj_math::{Point, Scalar}; +use fj_math::{Arc, Point, Scalar}; -use crate::{geometry::curve::Curve, partial::PartialHalfEdge}; +use crate::{ + geometry::curve::Curve, + insert::Insert, + objects::{GlobalEdge, HalfEdge, Objects, Vertex}, + partial::{Partial, PartialHalfEdge}, + services::Service, + storage::Handle, +}; /// Builder API for [`PartialHalfEdge`] pub trait HalfEdgeBuilder { - /// Update partial half-edge to be a circle, from the given radius - fn update_as_circle_from_radius( - &mut self, + /// Create a circle + fn make_circle( radius: impl Into, - ) -> Curve; + objects: &mut Service, + ) -> Partial; - /// Update partial half-edge to be an arc, spanning the given angle in - /// radians + /// Create an arc /// /// # Panics /// /// Panics if the given angle is not within the range (-2pi, 2pi) radians. - fn update_as_arc( - &mut self, - start: Point<2>, - end: Point<2>, + fn make_arc( + start: impl Into>, + end: impl Into>, angle_rad: impl Into, - ); - - /// Update partial half-edge to be a line segment - fn update_as_line_segment( - &mut self, - start: Point<2>, - end: Point<2>, - ) -> Curve; + objects: &mut Service, + ) -> Partial; + + /// Create a line segment + fn make_line_segment( + points_surface: [impl Into>; 2], + boundary: Option<[Point<1>; 2]>, + start_vertex: Option>, + global_form: Option>, + objects: &mut Service, + ) -> Partial; + + /// Create a half-edge + fn make_half_edge( + curve: Curve, + boundary: [Point<1>; 2], + start_vertex: Option>, + global_form: Option>, + objects: &mut Service, + ) -> Partial; } impl HalfEdgeBuilder for PartialHalfEdge { - fn update_as_circle_from_radius( - &mut self, + fn make_circle( radius: impl Into, - ) -> Curve { - let path = Curve::circle_from_radius(radius); - self.curve = Some(path); - - let [a_curve, b_curve] = + objects: &mut Service, + ) -> Partial { + let curve = Curve::circle_from_radius(radius); + let boundary = [Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord])); - for (point_boundary, point_curve) in - self.boundary.each_mut_ext().zip_ext([a_curve, b_curve]) - { - *point_boundary = Some(point_curve); - } - - path + Self::make_half_edge(curve, boundary, None, None, objects) } - fn update_as_arc( - &mut self, - start: Point<2>, - end: Point<2>, + fn make_arc( + start: impl Into>, + end: impl Into>, angle_rad: impl Into, - ) { + objects: &mut Service, + ) -> Partial { let angle_rad = angle_rad.into(); if angle_rad <= -Scalar::TAU || angle_rad >= Scalar::TAU { panic!("arc angle must be in the range (-2pi, 2pi) radians"); } - let arc = fj_math::Arc::from_endpoints_and_angle(start, end, angle_rad); - - let path = Curve::circle_from_center_and_radius(arc.center, arc.radius); - self.curve = Some(path); + let arc = Arc::from_endpoints_and_angle(start, end, angle_rad); - let [a_curve, b_curve] = + let curve = + Curve::circle_from_center_and_radius(arc.center, arc.radius); + let boundary = [arc.start_angle, arc.end_angle].map(|coord| Point::from([coord])); - for (point_boundary, point_curve) in - self.boundary.each_mut_ext().zip_ext([a_curve, b_curve]) - { - *point_boundary = Some(point_curve); - } + Self::make_half_edge(curve, boundary, None, None, objects) } - fn update_as_line_segment( - &mut self, - start: Point<2>, - end: Point<2>, - ) -> Curve { - let points_surface = [start, end]; - - let path = if let [Some(start), Some(end)] = self.boundary { - let points = [start, end].zip_ext(points_surface); - - let path = Curve::line_from_points_with_coords(points); - self.curve = Some(path); - - path - } else { - let (path, _) = Curve::line_from_points(points_surface); - self.curve = Some(path); - - for (vertex, position) in - self.boundary.each_mut_ext().zip_ext([0., 1.]) - { - *vertex = Some([position].into()); - } - - path - }; + fn make_line_segment( + points_surface: [impl Into>; 2], + boundary: Option<[Point<1>; 2]>, + start_vertex: Option>, + global_form: Option>, + objects: &mut Service, + ) -> Partial { + let boundary = + boundary.unwrap_or_else(|| [[0.], [1.]].map(Point::from)); + let curve = Curve::line_from_points_with_coords( + boundary.zip_ext(points_surface), + ); + + Self::make_half_edge( + curve, + boundary, + start_vertex, + global_form, + objects, + ) + } - path + fn make_half_edge( + curve: Curve, + boundary: [Point<1>; 2], + start_vertex: Option>, + global_form: Option>, + objects: &mut Service, + ) -> Partial { + Partial::from_partial(PartialHalfEdge { + curve: Some(curve), + boundary: boundary.map(Some), + start_vertex: start_vertex + .unwrap_or_else(|| Vertex::new().insert(objects)), + global_form: global_form + .unwrap_or_else(|| GlobalEdge::new().insert(objects)), + }) } } diff --git a/crates/fj-kernel/src/builder/mod.rs b/crates/fj-kernel/src/builder/mod.rs index 0facf369ae..5447b2d143 100644 --- a/crates/fj-kernel/src/builder/mod.rs +++ b/crates/fj-kernel/src/builder/mod.rs @@ -45,6 +45,14 @@ pub trait ObjectArgument: IntoIterator { where F: FnMut(T) -> R; + /// Create a return value by mapping the implementing type + /// + /// Provides access to the (circular) next item. + fn map_with_next(self, f: F) -> Self::SameSize + where + F: FnMut(T, T) -> R, + T: Clone; + /// Create a return value by mapping the implementing type /// /// Provides access to the (circular) previous item. @@ -80,19 +88,32 @@ impl ObjectArgument for Vec { ret } - fn map_with_prev(self, mut f: F) -> Self::SameSize + fn map_with_next(self, mut f: F) -> Self::SameSize where F: FnMut(T, T) -> R, T: Clone, { - let mut prev = Vec::new(); - for i in 0..self.len() { - prev.push(self[(i + self.len() - 1) % self.len()].clone()); + let mut next = self.clone(); + next.rotate_left(1); + + let mut ret = Vec::new(); + for (item, next) in self.into_iter().zip(next) { + ret.push(f(item, next)); } + ret + } + + fn map_with_prev(self, mut f: F) -> Self::SameSize + where + F: FnMut(T, T) -> R, + T: Clone, + { + let mut prev = self.clone(); + prev.rotate_right(1); + let mut ret = Vec::new(); - for (i, item) in self.into_iter().enumerate() { - let prev = prev[i].clone(); + for (item, prev) in self.into_iter().zip(prev) { ret.push(f(item, prev)); } @@ -131,14 +152,30 @@ macro_rules! impl_object_argument_for_arrays { self.map(f) } + fn map_with_next(self, mut f: F) -> Self::SameSize + where + F: FnMut(T, T) -> R, + T: Clone, + { + let mut next = self.clone(); + next.rotate_left(1); + + let mut i = 0; + self.map(|item| { + let next = next[i].clone(); + i += 1; + + f(item, next) + }) + } + fn map_with_prev(self, mut f: F) -> Self::SameSize where F: FnMut(T, T) -> R, T: Clone, { - let prev: [_; $len] = array::from_fn(|i| { - self[(i + self.len() - 1) % self.len()].clone() - }); + let mut prev = self.clone(); + prev.rotate_right(1); let mut i = 0; self.map(|item| { diff --git a/crates/fj-operations/src/sketch.rs b/crates/fj-operations/src/sketch.rs index 7db8bb1efe..fea504801b 100644 --- a/crates/fj-operations/src/sketch.rs +++ b/crates/fj-operations/src/sketch.rs @@ -28,12 +28,8 @@ impl Shape for fj::Sketch { let face = match self.chain() { fj::Chain::Circle(circle) => { - let half_edge = { - let mut half_edge = PartialHalfEdge::new(objects); - half_edge.update_as_circle_from_radius(circle.radius()); - - Partial::from_partial(half_edge) - }; + let half_edge = + PartialHalfEdge::make_circle(circle.radius(), objects); let exterior = { let mut cycle = PartialCycle::new(objects); cycle.half_edges.push(half_edge); @@ -57,33 +53,37 @@ impl Shape for fj::Sketch { let exterior = { let mut cycle = PartialCycle::new(objects); - let half_edges = poly_chain + let segments = poly_chain .to_segments() .into_iter() .map(|fj::SketchSegment { endpoint, route }| { let endpoint = Point::from(endpoint); - let half_edge = cycle.add_half_edge(objects); - (half_edge, endpoint, route) + (endpoint, route) }) - .collect::>(); + .circular_tuple_windows(); - for ((mut half_edge, start, route), (_, end, _)) in - half_edges.into_iter().circular_tuple_windows() - { - match route { + for ((start, route), (end, _)) in segments { + let half_edge = match route { fj::SketchSegmentRoute::Direct => { - half_edge - .write() - .update_as_line_segment(start, end); + PartialHalfEdge::make_line_segment( + [start, end], + None, + None, + None, + objects, + ) } fj::SketchSegmentRoute::Arc { angle } => { - half_edge.write().update_as_arc( + PartialHalfEdge::make_arc( start, end, angle.rad(), - ); + objects, + ) } - } + }; + + cycle.add_half_edge(half_edge); } Partial::from_partial(cycle) @@ -132,11 +132,11 @@ impl Shape for fj::Sketch { segment.endpoint, fj_math::Scalar::from_f64(angle.rad()), ); - for circle_minmax_angle in + for circle_min_max_angle in [0., PI / 2., PI, 3. * PI / 2.] { let mm_angle = fj_math::Scalar::from_f64( - circle_minmax_angle, + circle_min_max_angle, ); if arc.start_angle < mm_angle && mm_angle < arc.end_angle @@ -145,9 +145,11 @@ impl Shape for fj::Sketch { arc.center + [ arc.radius - * circle_minmax_angle.cos(), + * circle_min_max_angle + .cos(), arc.radius - * circle_minmax_angle.sin(), + * circle_min_max_angle + .sin(), ], ); }