diff --git a/Cargo.lock b/Cargo.lock index df3b4511e..81a5f43a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1137,6 +1137,7 @@ dependencies = [ "fj-interop", "fj-kernel", "fj-math", + "itertools", "thiserror", ] diff --git a/crates/fj-kernel/src/algorithms/approx/edge.rs b/crates/fj-kernel/src/algorithms/approx/edge.rs index 3258b35f6..a3542dbb4 100644 --- a/crates/fj-kernel/src/algorithms/approx/edge.rs +++ b/crates/fj-kernel/src/algorithms/approx/edge.rs @@ -246,8 +246,14 @@ mod tests { let half_edge = { let mut half_edge = PartialHalfEdge::default(); - half_edge.update_as_line_segment_from_points([[1., 1.], [2., 1.]]); - half_edge.infer_vertex_positions_if_necessary(&surface.geometry()); + half_edge.update_as_line_segment_from_points( + [[1., 1.], [2., 1.]], + half_edge.end_vertex.clone(), + ); + half_edge.infer_vertex_positions_if_necessary( + &surface.geometry(), + half_edge.end_vertex.clone(), + ); half_edge .build(&mut services.objects) @@ -273,8 +279,14 @@ mod tests { let half_edge = { let mut half_edge = PartialHalfEdge::default(); - half_edge.update_as_line_segment_from_points([[1., 1.], [2., 1.]]); - half_edge.infer_vertex_positions_if_necessary(&surface.geometry()); + half_edge.update_as_line_segment_from_points( + [[1., 1.], [2., 1.]], + half_edge.end_vertex.clone(), + ); + half_edge.infer_vertex_positions_if_necessary( + &surface.geometry(), + half_edge.end_vertex.clone(), + ); half_edge .build(&mut services.objects) @@ -300,12 +312,18 @@ mod tests { let half_edge = { let mut half_edge = PartialHalfEdge::default(); - half_edge.update_as_line_segment_from_points([[0., 1.], [1., 1.]]); + half_edge.update_as_line_segment_from_points( + [[0., 1.], [1., 1.]], + half_edge.end_vertex.clone(), + ); half_edge.boundary[0] = Some(range.boundary[0]); half_edge.boundary[1] = Some(range.boundary[1]); - half_edge.infer_vertex_positions_if_necessary(&surface.geometry()); + half_edge.infer_vertex_positions_if_necessary( + &surface.geometry(), + half_edge.end_vertex.clone(), + ); half_edge .build(&mut services.objects) @@ -338,7 +356,10 @@ mod tests { let mut half_edge = PartialHalfEdge::default(); half_edge.update_as_circle_from_radius(1.); - half_edge.infer_vertex_positions_if_necessary(&surface.geometry()); + half_edge.infer_vertex_positions_if_necessary( + &surface.geometry(), + half_edge.end_vertex.clone(), + ); half_edge .build(&mut services.objects) diff --git a/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs b/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs index 8fea643fa..05f5f363c 100644 --- a/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs +++ b/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs @@ -88,8 +88,14 @@ mod tests { let curve = Curve::u_axis(); let half_edge = { let mut half_edge = PartialHalfEdge::default(); - half_edge.update_as_line_segment_from_points([[1., -1.], [1., 1.]]); - half_edge.infer_vertex_positions_if_necessary(&surface.geometry()); + half_edge.update_as_line_segment_from_points( + [[1., -1.], [1., 1.]], + half_edge.end_vertex.clone(), + ); + half_edge.infer_vertex_positions_if_necessary( + &surface.geometry(), + half_edge.end_vertex.clone(), + ); half_edge.build(&mut services.objects) }; @@ -112,9 +118,14 @@ mod tests { let curve = Curve::u_axis(); let half_edge = { let mut half_edge = PartialHalfEdge::default(); - half_edge - .update_as_line_segment_from_points([[-1., -1.], [-1., 1.]]); - half_edge.infer_vertex_positions_if_necessary(&surface.geometry()); + half_edge.update_as_line_segment_from_points( + [[-1., -1.], [-1., 1.]], + half_edge.end_vertex.clone(), + ); + half_edge.infer_vertex_positions_if_necessary( + &surface.geometry(), + half_edge.end_vertex.clone(), + ); half_edge.build(&mut services.objects) }; @@ -137,9 +148,14 @@ mod tests { let curve = Curve::u_axis(); let half_edge = { let mut half_edge = PartialHalfEdge::default(); - half_edge - .update_as_line_segment_from_points([[-1., -1.], [1., -1.]]); - half_edge.infer_vertex_positions_if_necessary(&surface.geometry()); + half_edge.update_as_line_segment_from_points( + [[-1., -1.], [1., -1.]], + half_edge.end_vertex.clone(), + ); + half_edge.infer_vertex_positions_if_necessary( + &surface.geometry(), + half_edge.end_vertex.clone(), + ); half_edge.build(&mut services.objects) }; @@ -157,8 +173,14 @@ mod tests { let curve = Curve::u_axis(); let half_edge = { let mut half_edge = PartialHalfEdge::default(); - half_edge.update_as_line_segment_from_points([[-1., 0.], [1., 0.]]); - half_edge.infer_vertex_positions_if_necessary(&surface.geometry()); + half_edge.update_as_line_segment_from_points( + [[-1., 0.], [1., 0.]], + half_edge.end_vertex.clone(), + ); + half_edge.infer_vertex_positions_if_necessary( + &surface.geometry(), + half_edge.end_vertex.clone(), + ); half_edge.build(&mut services.objects) }; diff --git a/crates/fj-kernel/src/algorithms/reverse/cycle.rs b/crates/fj-kernel/src/algorithms/reverse/cycle.rs index 84150209f..09178ff0e 100644 --- a/crates/fj-kernel/src/algorithms/reverse/cycle.rs +++ b/crates/fj-kernel/src/algorithms/reverse/cycle.rs @@ -20,14 +20,12 @@ impl Reverse for Handle { let [a, b] = current.boundary(); [b, a] }; - let surface_vertices = - [next.start_vertex(), current.start_vertex()] - .map(Clone::clone); HalfEdge::new( current.curve(), boundary, - surface_vertices, + next.start_vertex().clone(), + current.start_vertex().clone(), current.global_form().clone(), ) .insert(objects) diff --git a/crates/fj-kernel/src/algorithms/sweep/edge.rs b/crates/fj-kernel/src/algorithms/sweep/edge.rs index d0f08d3f9..d3de6176a 100644 --- a/crates/fj-kernel/src/algorithms/sweep/edge.rs +++ b/crates/fj-kernel/src/algorithms/sweep/edge.rs @@ -1,5 +1,6 @@ use fj_interop::{ext::ArrayExt, mesh::Color}; use fj_math::{Point, Scalar, Vector}; +use itertools::Itertools; use crate::{ builder::{CycleBuilder, HalfEdgeBuilder}, @@ -106,7 +107,7 @@ impl Sweep for (Handle, &Handle, &Surface, Color) { // 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. - let mut vertex = half_edge.surface_vertices[0].write(); + let mut vertex = half_edge.start_vertex.write(); vertex.position = Some(surface_point); vertex.global_form = Partial::from(global_vertex); }, @@ -118,13 +119,17 @@ impl Sweep for (Handle, &Handle, &Surface, Color) { // 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 edge in [ - edge_bottom.write(), - edge_up.write(), - edge_top.write(), - edge_down.write(), - ] { - edge.update_as_line_segment(); + for (mut edge, next) in [ + edge_bottom.clone(), + edge_up.clone(), + edge_top.clone(), + edge_down.clone(), + ] + .into_iter() + .circular_tuple_windows() + { + edge.write() + .update_as_line_segment(next.read().start_vertex.clone()); } // Finally, we can make sure that all edges refer to the correct global diff --git a/crates/fj-kernel/src/algorithms/sweep/face.rs b/crates/fj-kernel/src/algorithms/sweep/face.rs index 40b98a0df..972775083 100644 --- a/crates/fj-kernel/src/algorithms/sweep/face.rs +++ b/crates/fj-kernel/src/algorithms/sweep/face.rs @@ -95,19 +95,18 @@ impl Sweep for Handle { .connect_to_closed_edges(top_edges, &top_surface.geometry()); for half_edge in &mut top_cycle.write().half_edges { - for surface_vertex in &mut half_edge.write().surface_vertices { - let mut surface_vertex = surface_vertex.write(); - let global_point = - surface_vertex.global_form.read().position; - - if surface_vertex.position.is_none() { - if let Some(global_point) = global_point { - surface_vertex.position = Some( - top_surface - .geometry() - .project_global_point(global_point), - ); - } + let mut half_edge = half_edge.write(); + + let mut start_vertex = half_edge.start_vertex.write(); + let global_point = start_vertex.global_form.read().position; + + if start_vertex.position.is_none() { + if let Some(global_point) = global_point { + start_vertex.position = Some( + top_surface + .geometry() + .project_global_point(global_point), + ); } } } diff --git a/crates/fj-kernel/src/algorithms/transform/edge.rs b/crates/fj-kernel/src/algorithms/transform/edge.rs index eeea338a6..e57320baa 100644 --- a/crates/fj-kernel/src/algorithms/transform/edge.rs +++ b/crates/fj-kernel/src/algorithms/transform/edge.rs @@ -18,17 +18,20 @@ impl TransformObject for HalfEdge { // coordinates. let curve = self.curve(); let boundary = self.boundary(); - let surface_vertices = self.surface_vertices().map(|surface_vertex| { - surface_vertex - .clone() - .transform_with_cache(transform, objects, cache) - }); + let start_vertex = self + .start_vertex() + .clone() + .transform_with_cache(transform, objects, cache); + let end_vertex = self + .end_vertex() + .clone() + .transform_with_cache(transform, objects, cache); let global_form = self .global_form() .clone() .transform_with_cache(transform, objects, cache); - Self::new(curve, boundary, surface_vertices, global_form) + Self::new(curve, boundary, start_vertex, end_vertex, global_form) } } diff --git a/crates/fj-kernel/src/builder/cycle.rs b/crates/fj-kernel/src/builder/cycle.rs index 50ea25b31..2247f9c1e 100644 --- a/crates/fj-kernel/src/builder/cycle.rs +++ b/crates/fj-kernel/src/builder/cycle.rs @@ -1,4 +1,5 @@ use fj_math::Point; +use itertools::Itertools; use crate::{ geometry::surface::SurfaceGeometry, @@ -102,29 +103,23 @@ impl CycleBuilder for PartialCycle { }; { - let shared_surface_vertex = { - let [vertex, _] = &new_half_edge.read().surface_vertices; - vertex.clone() - }; + let shared_surface_vertex = + new_half_edge.read().start_vertex.clone(); let mut last_half_edge = last_half_edge.write(); - let [_, vertex] = &mut last_half_edge.surface_vertices; - *vertex = shared_surface_vertex; - last_half_edge.infer_global_form(); + last_half_edge.end_vertex = shared_surface_vertex.clone(); + last_half_edge.infer_global_form(shared_surface_vertex); } { - let shared_surface_vertex = { - let [vertex, _] = &first_half_edge.read().surface_vertices; - vertex.clone() - }; + let shared_surface_vertex = + first_half_edge.read().start_vertex.clone(); let mut new_half_edge = new_half_edge.write(); - let [_, vertex] = &mut new_half_edge.surface_vertices; - *vertex = shared_surface_vertex; - new_half_edge.infer_global_form(); + new_half_edge.end_vertex = shared_surface_vertex.clone(); + new_half_edge.infer_global_form(shared_surface_vertex); } self.half_edges.push(new_half_edge.clone()); @@ -136,12 +131,7 @@ impl CycleBuilder for PartialCycle { point: impl Into>, ) -> Partial { let mut half_edge = self.add_half_edge(); - - { - let [vertex, _] = &mut half_edge.write().surface_vertices; - vertex.write().position = Some(point.into()); - } - + half_edge.write().start_vertex.write().position = Some(point.into()); half_edge } @@ -150,12 +140,13 @@ impl CycleBuilder for PartialCycle { point: impl Into>, ) -> Partial { let mut half_edge = self.add_half_edge(); - - { - let [vertex, _] = &mut half_edge.write().surface_vertices; - vertex.write().global_form.write().position = Some(point.into()); - } - + half_edge + .write() + .start_vertex + .write() + .global_form + .write() + .position = Some(point.into()); half_edge } @@ -174,8 +165,12 @@ impl CycleBuilder for PartialCycle { } fn update_as_polygon(&mut self) { - for half_edge in &mut self.half_edges { - half_edge.write().update_as_line_segment(); + for (mut half_edge, next) in + self.half_edges.iter().cloned().circular_tuple_windows() + { + half_edge + .write() + .update_as_line_segment(next.read().start_vertex.clone()); } } @@ -198,10 +193,13 @@ impl CycleBuilder for PartialCycle { &mut self, surface: &SurfaceGeometry, ) { - for half_edge in &mut self.half_edges { + for (mut half_edge, next_half_edge) in + self.half_edges.iter().cloned().circular_tuple_windows() + { + let next_vertex = next_half_edge.read().start_vertex.clone(); half_edge .write() - .infer_vertex_positions_if_necessary(surface); + .infer_vertex_positions_if_necessary(surface, next_vertex); } } } diff --git a/crates/fj-kernel/src/builder/edge.rs b/crates/fj-kernel/src/builder/edge.rs index a8740be4f..499ef479a 100644 --- a/crates/fj-kernel/src/builder/edge.rs +++ b/crates/fj-kernel/src/builder/edge.rs @@ -6,7 +6,7 @@ use crate::{ curve::{Curve, GlobalPath}, surface::SurfaceGeometry, }, - objects::{GlobalEdge, HalfEdge}, + objects::{GlobalEdge, HalfEdge, SurfaceVertex}, partial::{MaybeCurve, Partial, PartialGlobalEdge, PartialHalfEdge}, }; @@ -34,27 +34,39 @@ pub trait HalfEdgeBuilder { /// # Panics /// /// Panics if the given angle is not within the range (-2pi, 2pi) radians. - fn update_as_arc(&mut self, angle_rad: impl Into); + fn update_as_arc( + &mut self, + angle_rad: impl Into, + next_vertex: Partial, + ); /// Update partial half-edge to be a line segment, from the given points fn update_as_line_segment_from_points( &mut self, points: [impl Into>; 2], + next_vertex: Partial, ) -> Curve; /// Update partial half-edge to be a line segment - fn update_as_line_segment(&mut self) -> Curve; + fn update_as_line_segment( + &mut self, + next_vertex: Partial, + ) -> Curve; /// Infer the global form of the half-edge /// /// Updates the global form referenced by this half-edge, and also returns /// it. - fn infer_global_form(&mut self) -> Partial; + fn infer_global_form( + &mut self, + next_vertex: Partial, + ) -> Partial; /// Infer the vertex positions (surface and global), if not already set fn infer_vertex_positions_if_necessary( &mut self, surface: &SurfaceGeometry, + next_vertex: Partial, ); /// Update this edge from another @@ -96,34 +108,31 @@ impl HalfEdgeBuilder for PartialHalfEdge { let [a_curve, b_curve] = [Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord])); - let mut surface_vertex = { - let [vertex, _] = &mut self.surface_vertices; - vertex.clone() - }; - surface_vertex.write().position = + self.start_vertex.write().position = Some(path.point_from_path_coords(a_curve)); + self.end_vertex = self.start_vertex.clone(); - for (vertex, point_curve) in self - .boundary - .each_mut_ext() - .zip_ext(self.surface_vertices.each_mut_ext()) - .zip_ext([a_curve, b_curve]) + for (point_boundary, point_curve) in + self.boundary.each_mut_ext().zip_ext([a_curve, b_curve]) { - *vertex.0 = Some(point_curve); - *vertex.1 = surface_vertex.clone(); + *point_boundary = Some(point_curve); } - self.infer_global_form(); + self.infer_global_form(self.start_vertex.clone()); path } - fn update_as_arc(&mut self, angle_rad: impl Into) { + fn update_as_arc( + &mut self, + angle_rad: impl Into, + mut next_vertex: 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 [start, end] = self.surface_vertices.each_ref_ext().map(|vertex| { + let [start, end] = [&self.start_vertex, &next_vertex].map(|vertex| { vertex .read() .position @@ -138,43 +147,46 @@ impl HalfEdgeBuilder for PartialHalfEdge { let [a_curve, b_curve] = [arc.start_angle, arc.end_angle].map(|coord| Point::from([coord])); - for (vertex, point_curve) in self + for ((point_boundary, surface_vertex), point_curve) in self .boundary .each_mut_ext() - .zip_ext(self.surface_vertices.each_mut_ext()) + .zip_ext([&mut self.start_vertex, &mut next_vertex]) .zip_ext([a_curve, b_curve]) { - *vertex.0 = Some(point_curve); - vertex.1.write().position = + *point_boundary = Some(point_curve); + surface_vertex.write().position = Some(path.point_from_path_coords(point_curve)); } - self.infer_global_form(); + self.infer_global_form(next_vertex); } fn update_as_line_segment_from_points( &mut self, points: [impl Into>; 2], + mut next_vertex: Partial, ) -> Curve { for (vertex, point) in - self.surface_vertices.each_mut_ext().zip_ext(points) + [&mut self.start_vertex, &mut next_vertex].zip_ext(points) { let mut surface_form = vertex.write(); surface_form.position = Some(point.into()); } - self.update_as_line_segment() + self.update_as_line_segment(next_vertex) } - fn update_as_line_segment(&mut self) -> Curve { + fn update_as_line_segment( + &mut self, + next_vertex: Partial, + ) -> Curve { let boundary = self.boundary; - let points_surface = - self.surface_vertices.each_ref_ext().map(|vertex| { - vertex - .read() - .position - .expect("Can't infer line segment without surface position") - }); + let points_surface = [&self.start_vertex, &next_vertex].map(|vertex| { + vertex + .read() + .position + .expect("Can't infer line segment without surface position") + }); let path = if let [Some(start), Some(end)] = boundary { let points = [start, end].zip_ext(points_surface); @@ -196,15 +208,16 @@ impl HalfEdgeBuilder for PartialHalfEdge { path }; - self.infer_global_form(); + self.infer_global_form(next_vertex); path } - fn infer_global_form(&mut self) -> Partial { - self.global_form.write().vertices = self - .surface_vertices - .each_ref_ext() + fn infer_global_form( + &mut self, + next_vertex: Partial, + ) -> Partial { + self.global_form.write().vertices = [&self.start_vertex, &next_vertex] .map(|vertex| vertex.read().global_form.clone()); self.global_form.clone() @@ -213,6 +226,7 @@ impl HalfEdgeBuilder for PartialHalfEdge { fn infer_vertex_positions_if_necessary( &mut self, surface: &SurfaceGeometry, + mut next_vertex: Partial, ) { let path = self .curve @@ -221,16 +235,14 @@ impl HalfEdgeBuilder for PartialHalfEdge { panic!("Can't infer vertex positions with undefined path"); }; - for vertex in self + for (boundary_point, surface_vertex) in self .boundary - .each_mut_ext() - .zip_ext(self.surface_vertices.each_mut_ext()) + .zip_ext([&mut self.start_vertex, &mut next_vertex]) { - let position_curve = vertex - .0 + let position_curve = boundary_point .expect("Can't infer surface position without curve position"); - let position_surface = vertex.1.read().position; + let position_surface = surface_vertex.read().position; // Infer surface position, if not available. let position_surface = match position_surface { @@ -239,18 +251,19 @@ impl HalfEdgeBuilder for PartialHalfEdge { let position_surface = path.point_from_path_coords(position_curve); - vertex.1.write().position = Some(position_surface); + surface_vertex.write().position = Some(position_surface); position_surface } }; // Infer global position, if not available. - let position_global = vertex.1.read().global_form.read().position; + let position_global = + surface_vertex.read().global_form.read().position; if position_global.is_none() { let position_global = surface.point_from_surface_coords(position_surface); - vertex.1.write().global_form.write().position = + surface_vertex.write().global_form.write().position = Some(position_global); } } @@ -335,10 +348,11 @@ impl HalfEdgeBuilder for PartialHalfEdge { } }); - for (this, other) in self - .surface_vertices + let other = other.read(); + + for (this, other) in [&mut self.start_vertex, &mut self.end_vertex] .iter_mut() - .zip(other.read().surface_vertices.iter().rev()) + .zip([&other.end_vertex, &other.start_vertex]) { this.write().global_form.write().position = other.read().global_form.read().position; diff --git a/crates/fj-kernel/src/builder/face.rs b/crates/fj-kernel/src/builder/face.rs index 02f52a667..89037ca7f 100644 --- a/crates/fj-kernel/src/builder/face.rs +++ b/crates/fj-kernel/src/builder/face.rs @@ -1,6 +1,7 @@ use std::{array, collections::VecDeque}; use fj_interop::ext::ArrayExt; +use itertools::Itertools; use crate::{ geometry::curve::Curve, @@ -44,7 +45,7 @@ impl FaceBuilder for PartialFace { .half_edges .iter() .map(|half_edge| { - let [surface_vertex, _] = &half_edge.read().surface_vertices; + let surface_vertex = &half_edge.read().start_vertex; let global_position = surface_vertex .read() .global_form @@ -93,7 +94,14 @@ impl FaceBuilder for PartialFace { } fn infer_curves(&mut self) { - for half_edge in &mut self.exterior.write().half_edges { + for (mut half_edge, next_half_edge) in self + .exterior + .read() + .half_edges + .iter() + .cloned() + .circular_tuple_windows() + { let mut half_edge = half_edge.write(); let mut curve = half_edge.curve; @@ -107,14 +115,15 @@ impl FaceBuilder for PartialFace { "Inferring undefined circles is not supported yet" ), MaybeCurve::UndefinedLine => { - let points_surface = half_edge - .surface_vertices - .each_ref_ext() - .map(|vertex| { - vertex.read().position.expect( - "Can't infer curve without surface points", - ) - }); + let points_surface = [ + &half_edge.start_vertex, + &next_half_edge.read().start_vertex, + ] + .map(|vertex| { + vertex.read().position.expect( + "Can't infer curve without surface points", + ) + }); let (line, points_curve) = Curve::line_from_points(points_surface); diff --git a/crates/fj-kernel/src/objects/full/edge.rs b/crates/fj-kernel/src/objects/full/edge.rs index c023738cc..f0a0a4915 100644 --- a/crates/fj-kernel/src/objects/full/edge.rs +++ b/crates/fj-kernel/src/objects/full/edge.rs @@ -1,4 +1,3 @@ -use fj_interop::ext::ArrayExt; use fj_math::Point; use crate::{ @@ -47,7 +46,8 @@ use crate::{ pub struct HalfEdge { curve: Curve, boundary: [Point<1>; 2], - surface_vertices: [Handle; 2], + start_vertex: Handle, + end_vertex: Handle, global_form: Handle, } @@ -56,13 +56,15 @@ impl HalfEdge { pub fn new( curve: Curve, boundary: [Point<1>; 2], - surface_vertices: [Handle; 2], + start_vertex: Handle, + end_vertex: Handle, global_form: Handle, ) -> Self { Self { curve, boundary, - surface_vertices, + start_vertex, + end_vertex, global_form, } } @@ -79,13 +81,12 @@ impl HalfEdge { /// Access the vertex from where this half-edge starts pub fn start_vertex(&self) -> &Handle { - let [vertex, _] = self.surface_vertices.each_ref_ext(); - vertex + &self.start_vertex } - /// Access the surface vertices that bound the half-edge - pub fn surface_vertices(&self) -> [&Handle; 2] { - self.surface_vertices.each_ref_ext() + /// Access the vertex from where this half-edge ends + pub fn end_vertex(&self) -> &Handle { + &self.end_vertex } /// Access the global form of the half-edge @@ -182,15 +183,27 @@ mod tests { let a_to_b = { let mut half_edge = PartialHalfEdge::default(); - half_edge.update_as_line_segment_from_points([a, b]); - half_edge.infer_vertex_positions_if_necessary(&surface.geometry()); + half_edge.update_as_line_segment_from_points( + [a, b], + half_edge.end_vertex.clone(), + ); + half_edge.infer_vertex_positions_if_necessary( + &surface.geometry(), + half_edge.end_vertex.clone(), + ); half_edge.build(&mut services.objects) }; let b_to_a = { let mut half_edge = PartialHalfEdge::default(); - half_edge.update_as_line_segment_from_points([b, a]); - half_edge.infer_vertex_positions_if_necessary(&surface.geometry()); + half_edge.update_as_line_segment_from_points( + [b, a], + half_edge.end_vertex.clone(), + ); + half_edge.infer_vertex_positions_if_necessary( + &surface.geometry(), + half_edge.end_vertex.clone(), + ); half_edge.build(&mut services.objects) }; diff --git a/crates/fj-kernel/src/partial/objects/edge.rs b/crates/fj-kernel/src/partial/objects/edge.rs index 694ec5789..5c4ad3c8b 100644 --- a/crates/fj-kernel/src/partial/objects/edge.rs +++ b/crates/fj-kernel/src/partial/objects/edge.rs @@ -1,6 +1,3 @@ -use std::array; - -use fj_interop::ext::ArrayExt; use fj_math::Point; use crate::{ @@ -18,8 +15,11 @@ pub struct PartialHalfEdge { /// The boundary of the half-edge on the curve pub boundary: [Option>; 2], - /// The surface vertices that bound the half-edge - pub surface_vertices: [Partial; 2], + /// The surface vertex where the half-edge starts + pub start_vertex: Partial, + + /// The surface vertex where the half-edge ends + pub end_vertex: Partial, /// The global form of the half-edge pub global_form: Partial, @@ -35,10 +35,13 @@ impl PartialObject for PartialHalfEdge { Self { curve: Some(half_edge.curve().into()), boundary: half_edge.boundary().map(Some), - surface_vertices: half_edge.surface_vertices().map( - |surface_vertex| { - Partial::from_full(surface_vertex.clone(), cache) - }, + start_vertex: Partial::from_full( + half_edge.start_vertex().clone(), + cache, + ), + end_vertex: Partial::from_full( + half_edge.end_vertex().clone(), + cache, ), global_form: Partial::from_full( half_edge.global_form().clone(), @@ -59,21 +62,21 @@ impl PartialObject for PartialHalfEdge { let boundary = self.boundary.map(|point| { point.expect("Can't build `HalfEdge` without boundary positions") }); - let surface_vertices = self - .surface_vertices - .map(|surface_vertex| surface_vertex.build(objects)); + let start_vertex = self.start_vertex.build(objects); + let end_vertex = self.end_vertex.build(objects); let global_form = self.global_form.build(objects); - HalfEdge::new(curve, boundary, surface_vertices, global_form) + HalfEdge::new(curve, boundary, start_vertex, end_vertex, global_form) } } impl Default for PartialHalfEdge { fn default() -> Self { let curve = None; - let surface_vertices = array::from_fn(|_| Partial::default()); + let start_vertex = Partial::default(); + let end_vertex = Partial::default(); - let global_vertices = surface_vertices.each_ref_ext().map( + let global_vertices = [&start_vertex, &end_vertex].map( |vertex: &Partial| { let surface_vertex = vertex.clone(); let global_vertex = surface_vertex.read().global_form.clone(); @@ -88,7 +91,8 @@ impl Default for PartialHalfEdge { Self { curve, boundary: [None; 2], - surface_vertices, + start_vertex, + end_vertex, global_form, } } diff --git a/crates/fj-kernel/src/validate/cycle.rs b/crates/fj-kernel/src/validate/cycle.rs index 247c72d23..5996824fc 100644 --- a/crates/fj-kernel/src/validate/cycle.rs +++ b/crates/fj-kernel/src/validate/cycle.rs @@ -74,8 +74,8 @@ impl CycleValidationError { errors: &mut Vec, ) { for (a, b) in cycle.half_edges().circular_tuple_windows() { - let [_, prev] = a.surface_vertices(); - let [next, _] = b.surface_vertices(); + let prev = a.end_vertex(); + let next = b.start_vertex(); if prev.id() != next.id() { errors.push( @@ -160,8 +160,7 @@ mod tests { // cycle. { let first_half_edge = half_edges.first_mut().unwrap(); - let [first_vertex, _] = - &mut first_half_edge.write().surface_vertices; + let first_vertex = &mut first_half_edge.write().start_vertex; let surface_vertex = Partial::from_partial(first_vertex.read().clone()); *first_vertex = surface_vertex; diff --git a/crates/fj-kernel/src/validate/edge.rs b/crates/fj-kernel/src/validate/edge.rs index fef6db70f..cd8709ad1 100644 --- a/crates/fj-kernel/src/validate/edge.rs +++ b/crates/fj-kernel/src/validate/edge.rs @@ -158,8 +158,14 @@ mod tests { let surface = services.objects.surfaces.xy_plane(); let mut half_edge = PartialHalfEdge::default(); - half_edge.update_as_line_segment_from_points([[0., 0.], [1., 0.]]); - half_edge.infer_vertex_positions_if_necessary(&surface.geometry()); + half_edge.update_as_line_segment_from_points( + [[0., 0.], [1., 0.]], + half_edge.end_vertex.clone(), + ); + half_edge.infer_vertex_positions_if_necessary( + &surface.geometry(), + half_edge.end_vertex.clone(), + ); half_edge.build(&mut services.objects) }; @@ -183,7 +189,8 @@ mod tests { HalfEdge::new( valid.curve(), valid.boundary(), - valid.surface_vertices().map(Clone::clone), + valid.start_vertex().clone(), + valid.end_vertex().clone(), global_form, ) }; @@ -202,8 +209,14 @@ mod tests { let surface = services.objects.surfaces.xy_plane(); let mut half_edge = PartialHalfEdge::default(); - half_edge.update_as_line_segment_from_points([[0., 0.], [1., 0.]]); - half_edge.infer_vertex_positions_if_necessary(&surface.geometry()); + half_edge.update_as_line_segment_from_points( + [[0., 0.], [1., 0.]], + half_edge.end_vertex.clone(), + ); + half_edge.infer_vertex_positions_if_necessary( + &surface.geometry(), + half_edge.end_vertex.clone(), + ); half_edge.build(&mut services.objects) }; @@ -213,7 +226,8 @@ mod tests { HalfEdge::new( valid.curve(), boundary, - valid.surface_vertices().map(Clone::clone), + valid.start_vertex().clone(), + valid.end_vertex().clone(), valid.global_form().clone(), ) }; diff --git a/crates/fj-kernel/src/validate/face.rs b/crates/fj-kernel/src/validate/face.rs index 70bdd2c98..b8b38002f 100644 --- a/crates/fj-kernel/src/validate/face.rs +++ b/crates/fj-kernel/src/validate/face.rs @@ -112,7 +112,9 @@ impl FaceValidationError { ) { for cycle in face.all_cycles() { for half_edge in cycle.half_edges() { - for surface_vertex in half_edge.surface_vertices() { + for surface_vertex in + [half_edge.start_vertex(), half_edge.end_vertex()] + { let surface_position_as_global = face .surface() .geometry() @@ -259,9 +261,11 @@ mod tests { let mut half_edge = face.exterior.write().add_half_edge(); half_edge.write().update_as_circle_from_radius(1.); - half_edge - .write() - .infer_vertex_positions_if_necessary(&surface.geometry()); + let next_vertex = half_edge.read().end_vertex.clone(); + half_edge.write().infer_vertex_positions_if_necessary( + &surface.geometry(), + next_vertex, + ); face.build(&mut services.objects) }; @@ -274,7 +278,8 @@ mod tests { .map(|point| point + Vector::from([Scalar::PI / 2.])); let mut surface_vertices = - half_edge.surface_vertices().map(Clone::clone); + [half_edge.start_vertex(), half_edge.end_vertex()] + .map(Clone::clone); let mut invalid = None; for surface_vertex in surface_vertices.each_mut_ext() { @@ -288,10 +293,13 @@ mod tests { *surface_vertex = invalid.clone(); } + let [start_vertex, end_vertex] = surface_vertices; + HalfEdge::new( half_edge.curve(), boundary, - surface_vertices, + start_vertex, + end_vertex, half_edge.global_form().clone(), ) .insert(&mut services.objects) diff --git a/crates/fj-operations/Cargo.toml b/crates/fj-operations/Cargo.toml index 7cc1a9cb4..cdefd2e5f 100644 --- a/crates/fj-operations/Cargo.toml +++ b/crates/fj-operations/Cargo.toml @@ -15,4 +15,5 @@ fj.workspace = true fj-interop.workspace = true fj-kernel.workspace = true fj-math.workspace = true +itertools = "0.10.5" thiserror = "1.0.35" diff --git a/crates/fj-operations/src/sketch.rs b/crates/fj-operations/src/sketch.rs index 6754fb6ec..6a1acbec2 100644 --- a/crates/fj-operations/src/sketch.rs +++ b/crates/fj-operations/src/sketch.rs @@ -12,6 +12,7 @@ use fj_kernel::{ services::Service, }; use fj_math::{Aabb, Point}; +use itertools::Itertools; use super::Shape; @@ -57,38 +58,38 @@ impl Shape for fj::Sketch { let exterior = { let mut cycle = PartialCycle::default(); - let mut line_segments = vec![]; - let mut arcs = vec![]; - poly_chain.to_segments().into_iter().for_each( - |fj::SketchSegment { endpoint, route }| { + + let half_edges = poly_chain + .to_segments() + .into_iter() + .map(|fj::SketchSegment { endpoint, route }| { let endpoint = Point::from(endpoint); - match route { - fj::SketchSegmentRoute::Direct => { - line_segments.push( - cycle - .add_half_edge_from_point_to_start( - endpoint, - ), - ); - } - fj::SketchSegmentRoute::Arc { angle } => { - arcs.push(( - cycle - .add_half_edge_from_point_to_start( - endpoint, - ), - angle, - )); - } + let half_edge = cycle + .add_half_edge_from_point_to_start(endpoint); + (half_edge, route) + }) + .collect::>(); + + for ((mut half_edge, route), (next_half_edge, _)) in + half_edges.into_iter().circular_tuple_windows() + { + let next_vertex = + next_half_edge.read().start_vertex.clone(); + + match route { + fj::SketchSegmentRoute::Direct => { + half_edge + .write() + .update_as_line_segment(next_vertex); + } + fj::SketchSegmentRoute::Arc { angle } => { + half_edge + .write() + .update_as_arc(angle.rad(), next_vertex); } - }, - ); - line_segments.into_iter().for_each(|mut half_edge| { - half_edge.write().update_as_line_segment(); - }); - arcs.into_iter().for_each(|(mut half_edge, angle)| { - half_edge.write().update_as_arc(angle.rad()) - }); + } + } + Partial::from_partial(cycle) };