diff --git a/crates/fj-kernel/src/builder/cycle.rs b/crates/fj-kernel/src/builder/cycle.rs new file mode 100644 index 000000000..5d5a62e87 --- /dev/null +++ b/crates/fj-kernel/src/builder/cycle.rs @@ -0,0 +1,134 @@ +use fj_math::Point; + +use crate::{ + objects::{Curve, HalfEdge, SurfaceVertex, Vertex}, + partial::{HasPartial, MaybePartial, PartialCycle}, +}; + +use super::{CurveBuilder, HalfEdgeBuilder}; + +/// Builder API for [`PartialCycle`] +pub trait CycleBuilder { + /// Update the partial cycle with a polygonal chain from the provided points + fn with_poly_chain( + self, + vertices: impl IntoIterator>, + ) -> Self; + + /// Update the partial cycle with a polygonal chain from the provided points + fn with_poly_chain_from_points( + self, + points: impl IntoIterator>>, + ) -> Self; + + /// Update the partial cycle by closing it with a line segment + /// + /// Builds a line segment from the last and first vertex, closing the cycle. + fn close_with_line_segment(self) -> Self; +} + +impl CycleBuilder for PartialCycle { + fn with_poly_chain( + self, + vertices: impl IntoIterator>, + ) -> Self { + let iter = self + .half_edges() + .last() + .map(|half_edge| { + let [_, last] = half_edge.vertices(); + last.surface_form() + }) + .into_iter() + .chain(vertices); + + let mut previous: Option> = None; + + let mut half_edges = Vec::new(); + for vertex_next in iter { + if let Some(vertex_prev) = previous { + let surface = self + .surface() + .clone() + .expect("Need surface to extend cycle with poly-chain"); + + let position_prev = vertex_prev + .position() + .expect("Need surface position to extend cycle"); + let position_next = vertex_next + .position() + .expect("Need surface position to extend cycle"); + + let from = vertex_prev.update_partial(|partial| { + partial.with_surface(Some(surface.clone())) + }); + let to = vertex_next.update_partial(|partial| { + partial.with_surface(Some(surface.clone())) + }); + + previous = Some(to.clone()); + + let curve = Curve::partial() + .with_surface(Some(surface.clone())) + .update_as_line_from_points([position_prev, position_next]); + + let [from, to] = + [(0., from), (1., to)].map(|(position, surface_form)| { + Vertex::partial() + .with_curve(Some(curve.clone())) + .with_position(Some([position])) + .with_surface_form(Some(surface_form)) + }); + + half_edges.push( + HalfEdge::partial() + .with_curve(Some(curve)) + .with_vertices(Some([from, to])), + ); + + continue; + } + + previous = Some(vertex_next); + } + + self.with_half_edges(half_edges) + } + + fn with_poly_chain_from_points( + self, + points: impl IntoIterator>>, + ) -> Self { + self.with_poly_chain(points.into_iter().map(|position| { + SurfaceVertex::partial() + .with_position(Some(position)) + .into() + })) + } + + fn close_with_line_segment(self) -> Self { + let first = self.half_edges().next(); + let last = self.half_edges().last(); + + let vertices = [first, last] + .map(|option| option.map(|half_edge| half_edge.vertices())); + + let [Some([first, _]), Some([_, last])] = vertices else { + return self; + }; + + let vertices = [last, first].map(|vertex| { + vertex + .surface_form() + .position() + .expect("Need surface position to close cycle") + }); + let surface = self.surface().expect("Need surface to close cycle"); + + self.with_half_edges(Some( + HalfEdge::partial() + .with_surface(Some(surface)) + .update_as_line_segment_from_points(vertices), + )) + } +} diff --git a/crates/fj-kernel/src/builder/face.rs b/crates/fj-kernel/src/builder/face.rs index d68c4f88e..bb34c3768 100644 --- a/crates/fj-kernel/src/builder/face.rs +++ b/crates/fj-kernel/src/builder/face.rs @@ -7,6 +7,8 @@ use crate::{ storage::Handle, }; +use super::CycleBuilder; + /// API for building a [`Face`] /// /// Also see [`Face::builder`]. diff --git a/crates/fj-kernel/src/builder/mod.rs b/crates/fj-kernel/src/builder/mod.rs index 4cce10b4f..9dafd5326 100644 --- a/crates/fj-kernel/src/builder/mod.rs +++ b/crates/fj-kernel/src/builder/mod.rs @@ -10,11 +10,13 @@ mod solid; // These are new-style builders that build on top of the partial object API. mod curve; +mod cycle; mod edge; mod vertex; pub use self::{ curve::CurveBuilder, + cycle::CycleBuilder, edge::{GlobalEdgeBuilder, HalfEdgeBuilder}, face::FaceBuilder, shell::ShellBuilder, diff --git a/crates/fj-kernel/src/iter.rs b/crates/fj-kernel/src/iter.rs index 05ca0b96d..718f7c5f0 100644 --- a/crates/fj-kernel/src/iter.rs +++ b/crates/fj-kernel/src/iter.rs @@ -360,7 +360,7 @@ impl Iterator for Iter { #[cfg(test)] mod tests { use crate::{ - builder::{CurveBuilder, HalfEdgeBuilder}, + builder::{CurveBuilder, CycleBuilder, HalfEdgeBuilder}, objects::{ Curve, Cycle, Face, GlobalCurve, GlobalVertex, HalfEdge, Objects, Shell, Sketch, Solid, SurfaceVertex, Vertex, diff --git a/crates/fj-kernel/src/partial/objects/cycle.rs b/crates/fj-kernel/src/partial/objects/cycle.rs index 0887f8d70..fe2163112 100644 --- a/crates/fj-kernel/src/partial/objects/cycle.rs +++ b/crates/fj-kernel/src/partial/objects/cycle.rs @@ -1,11 +1,6 @@ -use fj_math::Point; - use crate::{ - builder::{CurveBuilder, HalfEdgeBuilder}, - objects::{ - Curve, Cycle, HalfEdge, Objects, Surface, SurfaceVertex, Vertex, - }, - partial::{HasPartial, MaybePartial}, + objects::{Cycle, HalfEdge, Objects, Surface}, + partial::MaybePartial, storage::Handle, validate::ValidationError, }; @@ -48,115 +43,6 @@ impl PartialCycle { self } - /// Update the partial cycle with a polygonal chain from the provided points - pub fn with_poly_chain( - self, - vertices: impl IntoIterator>, - ) -> Self { - let iter = self - .half_edges() - .last() - .map(|half_edge| { - let [_, last] = half_edge.vertices(); - last.surface_form() - }) - .into_iter() - .chain(vertices); - - let mut previous: Option> = None; - - let mut half_edges = Vec::new(); - for vertex_next in iter { - if let Some(vertex_prev) = previous { - let surface = self - .surface() - .clone() - .expect("Need surface to extend cycle with poly-chain"); - - let position_prev = vertex_prev - .position() - .expect("Need surface position to extend cycle"); - let position_next = vertex_next - .position() - .expect("Need surface position to extend cycle"); - - let from = vertex_prev.update_partial(|partial| { - partial.with_surface(Some(surface.clone())) - }); - let to = vertex_next.update_partial(|partial| { - partial.with_surface(Some(surface.clone())) - }); - - previous = Some(to.clone()); - - let curve = Curve::partial() - .with_surface(Some(surface.clone())) - .update_as_line_from_points([position_prev, position_next]); - - let [from, to] = - [(0., from), (1., to)].map(|(position, surface_form)| { - Vertex::partial() - .with_curve(Some(curve.clone())) - .with_position(Some([position])) - .with_surface_form(Some(surface_form)) - }); - - half_edges.push( - HalfEdge::partial() - .with_curve(Some(curve)) - .with_vertices(Some([from, to])), - ); - - continue; - } - - previous = Some(vertex_next); - } - - self.with_half_edges(half_edges) - } - - /// Update the partial cycle with a polygonal chain from the provided points - pub fn with_poly_chain_from_points( - self, - points: impl IntoIterator>>, - ) -> Self { - self.with_poly_chain(points.into_iter().map(|position| { - SurfaceVertex::partial() - .with_position(Some(position)) - .into() - })) - } - - /// Update the partial cycle by closing it with a line segment - /// - /// Builds a line segment from the last and first vertex, closing the cycle. - pub fn close_with_line_segment(self) -> Self { - let first = self.half_edges().next(); - let last = self.half_edges().last(); - - let vertices = [first, last] - .map(|option| option.map(|half_edge| half_edge.vertices())); - - let [Some([first, _]), Some([_, last])] = vertices else { - return self; - }; - - let vertices = [last, first].map(|vertex| { - vertex - .surface_form() - .position() - .expect("Need surface position to close cycle") - }); - let surface = self.surface().expect("Need surface to close cycle"); - - self.with_half_edges(Some( - HalfEdge::partial() - .with_surface(Some(surface)) - .update_as_line_segment_from_points(vertices), - )) - } - /// Build a full [`Cycle`] from the partial cycle pub fn build( mut self,