diff --git a/crates/fj-kernel/src/builder/cycle.rs b/crates/fj-kernel/src/builder/cycle.rs deleted file mode 100644 index 726db2716..000000000 --- a/crates/fj-kernel/src/builder/cycle.rs +++ /dev/null @@ -1,127 +0,0 @@ -use fj_math::Point; - -use crate::{ - objects::{Curve, Cycle, HalfEdge, Surface, SurfaceVertex, Vertex}, - partial::HasPartial, - stores::{Handle, Stores}, -}; - -/// API for building a [`Cycle`] -/// -/// Also see [`Cycle::builder`]. -pub struct CycleBuilder<'a> { - /// The stores that the created objects are put in - pub stores: &'a Stores, - - /// The surface that the [`Cycle`] is defined in - pub surface: Handle, - - /// The half-edges that make up the [`Cycle`] - pub half_edges: Vec, -} - -impl<'a> CycleBuilder<'a> { - /// Build the [`Cycle`] with the given half-edge - pub fn with_half_edges( - mut self, - half_edge: impl IntoIterator, - ) -> Self { - self.half_edges.extend(half_edge); - self - } - - /// Build the [`Cycle`] with a polygonal chain from the provided points - pub fn with_poly_chain_from_points( - mut self, - points: impl IntoIterator>>, - ) -> Self { - let iter = self - .half_edges - .last() - .map(|half_edge| { - let [_, last] = half_edge.vertices(); - - let vertex = last.surface_form().clone(); - let position = last.surface_form().position(); - - (position, Some(vertex)) - }) - .into_iter() - .chain(points.into_iter().map(|point| (point.into(), None))); - - let mut previous: Option<(Point<2>, Option)> = None; - - for (position, vertex) in iter { - if let Some((previous_position, previous_vertex)) = previous { - let from = previous_vertex.unwrap_or_else(|| { - SurfaceVertex::partial() - .with_surface(self.surface.clone()) - .with_position(previous_position) - .build(self.stores) - }); - let to = vertex.unwrap_or_else(|| { - SurfaceVertex::partial() - .with_surface(self.surface.clone()) - .with_position(position) - .build(self.stores) - }); - - previous = Some((position, Some(to.clone()))); - - let curve = Curve::partial() - .with_surface(self.surface.clone()) - .as_line_from_points([previous_position, position]) - .build(self.stores); - - let [from, to] = - [(0., from), (1., to)].map(|(position, surface_form)| { - Vertex::partial() - .with_curve(curve.clone()) - .with_position([position]) - .with_surface_form(surface_form) - .build(self.stores) - }); - - self.half_edges.push( - HalfEdge::partial() - .with_curve(curve) - .with_vertices([from, to]) - .build(self.stores), - ); - - continue; - } - - previous = Some((position, vertex)); - } - - self - } - - /// Close the [`Cycle`] with a line segment - /// - /// Builds a line segment from the last and first vertex, closing the cycle. - pub fn close_with_line_segment(mut self) -> Self { - let first = self.half_edges.first(); - let last = self.half_edges.last(); - - if let [Some([first, _]), Some([_, last])] = [first, last] - .map(|option| option.map(|half_edge| half_edge.vertices())) - { - let vertices = - [last, first].map(|vertex| vertex.surface_form().position()); - self.half_edges.push( - HalfEdge::partial() - .as_line_segment_from_points(self.surface.clone(), vertices) - .build(self.stores), - ); - } - - self - } - - /// Finish building the [`Cycle`] - pub fn build(self) -> Cycle { - Cycle::new(self.surface, self.half_edges) - } -} diff --git a/crates/fj-kernel/src/builder/face.rs b/crates/fj-kernel/src/builder/face.rs index 547728cc9..fd0a9858d 100644 --- a/crates/fj-kernel/src/builder/face.rs +++ b/crates/fj-kernel/src/builder/face.rs @@ -2,6 +2,7 @@ use fj_math::Point; use crate::{ objects::{Cycle, Face, Surface}, + partial::HasPartial, stores::{Handle, Stores}, }; @@ -32,10 +33,11 @@ impl<'a> FaceBuilder<'a> { points: impl IntoIterator>>, ) -> Self { self.exterior = Some( - Cycle::builder(self.stores, self.surface.clone()) + Cycle::partial() + .with_surface(self.surface.clone()) .with_poly_chain_from_points(points) .close_with_line_segment() - .build(), + .build(self.stores), ); self } @@ -46,10 +48,11 @@ impl<'a> FaceBuilder<'a> { points: impl IntoIterator>>, ) -> Self { self.interiors.push( - Cycle::builder(self.stores, self.surface.clone()) + Cycle::partial() + .with_surface(self.surface.clone()) .with_poly_chain_from_points(points) .close_with_line_segment() - .build(), + .build(self.stores), ); self } diff --git a/crates/fj-kernel/src/builder/mod.rs b/crates/fj-kernel/src/builder/mod.rs index f8e4aa1fb..70b8e6fcf 100644 --- a/crates/fj-kernel/src/builder/mod.rs +++ b/crates/fj-kernel/src/builder/mod.rs @@ -1,12 +1,11 @@ //! API for building objects -mod cycle; mod face; mod shell; mod sketch; mod solid; pub use self::{ - cycle::CycleBuilder, face::FaceBuilder, shell::ShellBuilder, - sketch::SketchBuilder, solid::SolidBuilder, + face::FaceBuilder, shell::ShellBuilder, sketch::SketchBuilder, + solid::SolidBuilder, }; diff --git a/crates/fj-kernel/src/builder/shell.rs b/crates/fj-kernel/src/builder/shell.rs index b2873d77c..cfa6997fc 100644 --- a/crates/fj-kernel/src/builder/shell.rs +++ b/crates/fj-kernel/src/builder/shell.rs @@ -167,9 +167,10 @@ impl<'a> ShellBuilder<'a> { .zip(sides_down) .zip(surfaces) .map(|((((bottom, side_up), top), side_down), surface)| { - let cycle = Cycle::builder(self.stores, surface) + let cycle = Cycle::partial() + .with_surface(surface) .with_half_edges([bottom, side_up, top, side_down]) - .build(); + .build(self.stores); Face::from_exterior(cycle) }); diff --git a/crates/fj-kernel/src/iter.rs b/crates/fj-kernel/src/iter.rs index 671f75e6c..4b82d404c 100644 --- a/crates/fj-kernel/src/iter.rs +++ b/crates/fj-kernel/src/iter.rs @@ -398,10 +398,11 @@ mod tests { let stores = Stores::new(); let surface = stores.surfaces.insert(Surface::xy_plane()); - let object = Cycle::builder(&stores, surface) + let object = Cycle::partial() + .with_surface(surface) .with_poly_chain_from_points([[0., 0.], [1., 0.], [0., 1.]]) .close_with_line_segment() - .build(); + .build(&stores); assert_eq!(3, object.curve_iter().count()); assert_eq!(1, object.cycle_iter().count()); diff --git a/crates/fj-kernel/src/objects/cycle.rs b/crates/fj-kernel/src/objects/cycle.rs index 6b1316709..29f37b2e0 100644 --- a/crates/fj-kernel/src/objects/cycle.rs +++ b/crates/fj-kernel/src/objects/cycle.rs @@ -1,11 +1,7 @@ use fj_math::{Scalar, Winding}; use pretty_assertions::assert_eq; -use crate::{ - builder::CycleBuilder, - path::SurfacePath, - stores::{Handle, Stores}, -}; +use crate::{path::SurfacePath, stores::Handle}; use super::{HalfEdge, Surface}; @@ -17,15 +13,6 @@ pub struct Cycle { } impl Cycle { - /// Build a `Cycle` using [`CycleBuilder`] - pub fn builder(stores: &Stores, surface: Handle) -> CycleBuilder { - CycleBuilder { - stores, - surface, - half_edges: Vec::new(), - } - } - /// Create a new cycle /// /// # Panics diff --git a/crates/fj-kernel/src/partial/maybe_partial.rs b/crates/fj-kernel/src/partial/maybe_partial.rs index 6d72c1d3e..16fb45f0b 100644 --- a/crates/fj-kernel/src/partial/maybe_partial.rs +++ b/crates/fj-kernel/src/partial/maybe_partial.rs @@ -1,7 +1,10 @@ use fj_math::Point; use crate::{ - objects::{Curve, GlobalCurve, GlobalEdge, Surface, SurfaceVertex, Vertex}, + objects::{ + Curve, GlobalCurve, GlobalEdge, HalfEdge, Surface, SurfaceVertex, + Vertex, + }, stores::{Handle, Stores}, }; @@ -101,6 +104,16 @@ impl MaybePartial { } } +impl MaybePartial { + /// Access the vertices + pub fn vertices(&self) -> Option<[MaybePartial; 2]> { + match self { + Self::Full(full) => Some(full.vertices().clone().map(Into::into)), + Self::Partial(partial) => partial.vertices.clone(), + } + } +} + impl MaybePartial { /// Access the position pub fn position(&self) -> Option> { diff --git a/crates/fj-kernel/src/partial/mod.rs b/crates/fj-kernel/src/partial/mod.rs index f3f5fc076..87567f44d 100644 --- a/crates/fj-kernel/src/partial/mod.rs +++ b/crates/fj-kernel/src/partial/mod.rs @@ -42,6 +42,7 @@ pub use self::{ maybe_partial::MaybePartial, objects::{ curve::PartialCurve, + cycle::PartialCycle, edge::{PartialGlobalEdge, PartialHalfEdge}, vertex::{PartialGlobalVertex, PartialSurfaceVertex, PartialVertex}, }, diff --git a/crates/fj-kernel/src/partial/objects/cycle.rs b/crates/fj-kernel/src/partial/objects/cycle.rs new file mode 100644 index 000000000..40a1a74e2 --- /dev/null +++ b/crates/fj-kernel/src/partial/objects/cycle.rs @@ -0,0 +1,172 @@ +use fj_math::Point; + +use crate::{ + objects::{Curve, Cycle, HalfEdge, Surface, SurfaceVertex, Vertex}, + partial::{HasPartial, MaybePartial}, + stores::{Handle, Stores}, +}; + +/// A partial [`Cycle`] +/// +/// See [`crate::partial`] for more information. +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct PartialCycle { + /// The surface that the [`Cycle`] is defined in + pub surface: Option>, + + /// The half-edges that make up the [`Cycle`] + pub half_edges: Vec>, +} + +impl PartialCycle { + /// Update the partial cycle with the given surface + pub fn with_surface(mut self, surface: Handle) -> Self { + self.surface = Some(surface); + self + } + + /// Update the partial cycle with the given half-edges + pub fn with_half_edges( + mut self, + half_edge: impl IntoIterator>>, + ) -> Self { + self.half_edges + .extend(half_edge.into_iter().map(Into::into)); + self + } + + /// Update the partial cycle with a polygonal chain from the provided points + pub fn with_poly_chain_from_points( + mut self, + points: impl IntoIterator>>, + ) -> Self { + let iter = self + .half_edges + .last() + .map(|half_edge| { + let [_, last] = half_edge.vertices().expect( + "Need half-edge vertices to extend cycle with poly-chain", + ); + let last = last.surface_form().expect( + "Need surface vertex to extend cycle with poly-chain", + ); + + let vertex = last.clone(); + let position = last.position().expect( + "Need surface position to extend cycle with poly-chain", + ); + + (position, Some(vertex)) + }) + .into_iter() + .chain(points.into_iter().map(|point| (point.into(), None))); + + let mut previous: Option<( + Point<2>, + Option>, + )> = None; + + for (position, vertex) in iter { + if let Some((previous_position, previous_vertex)) = previous { + let surface = self + .surface + .clone() + .expect("Need surface to extend cycle with poly-chain"); + + let from = previous_vertex.unwrap_or_else(|| { + SurfaceVertex::partial() + .with_surface(surface.clone()) + .with_position(previous_position) + .into() + }); + let to = vertex.unwrap_or_else(|| { + SurfaceVertex::partial() + .with_surface(surface.clone()) + .with_position(position) + .into() + }); + + previous = Some((position, Some(to.clone()))); + + let curve = Curve::partial() + .with_surface(surface.clone()) + .as_line_from_points([previous_position, position]); + + let [from, to] = + [(0., from), (1., to)].map(|(position, surface_form)| { + Vertex::partial() + .with_curve(curve.clone()) + .with_position([position]) + .with_surface_form(surface_form) + }); + + self.half_edges.push( + HalfEdge::partial() + .with_curve(curve) + .with_vertices([from, to]) + .into(), + ); + + continue; + } + + previous = Some((position, vertex)); + } + + 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. + pub fn close_with_line_segment(mut self) -> Self { + let first = self.half_edges.first(); + let last = self.half_edges.last(); + + let vertices = [first, last].map(|option| { + option.map(|half_edge| { + half_edge.vertices().expect("Need vertices to close cycle") + }) + }); + + if let [Some([first, _]), Some([_, last])] = vertices { + let vertices = [last, first].map(|vertex| { + vertex + .surface_form() + .expect("Need surface vertex to close cycle") + .position() + .expect("Need surface position to close cycle") + }); + let surface = + self.surface.clone().expect("Need surface to close cycle"); + + self.half_edges.push( + HalfEdge::partial() + .as_line_segment_from_points(surface, vertices) + .into(), + ); + } + + self + } + + /// Build a full [`Cycle`] from the partial cycle + pub fn build(self, stores: &Stores) -> Cycle { + let surface = self.surface.expect("Need surface to build `Cycle`"); + let half_edges = self + .half_edges + .into_iter() + .map(|half_edge| half_edge.into_full(stores)); + + Cycle::new(surface, half_edges) + } +} + +impl From<&Cycle> for PartialCycle { + fn from(cycle: &Cycle) -> Self { + Self { + surface: Some(cycle.surface().clone()), + half_edges: cycle.half_edges().cloned().map(Into::into).collect(), + } + } +} diff --git a/crates/fj-kernel/src/partial/objects/mod.rs b/crates/fj-kernel/src/partial/objects/mod.rs index 1270703f0..8b1411c93 100644 --- a/crates/fj-kernel/src/partial/objects/mod.rs +++ b/crates/fj-kernel/src/partial/objects/mod.rs @@ -1,17 +1,19 @@ pub mod curve; +pub mod cycle; pub mod edge; pub mod vertex; use crate::{ objects::{ - Curve, GlobalEdge, GlobalVertex, HalfEdge, SurfaceVertex, Vertex, + Curve, Cycle, GlobalEdge, GlobalVertex, HalfEdge, SurfaceVertex, Vertex, }, stores::Stores, }; use super::{ - HasPartial, MaybePartial, Partial, PartialCurve, PartialGlobalEdge, - PartialGlobalVertex, PartialHalfEdge, PartialSurfaceVertex, PartialVertex, + HasPartial, MaybePartial, Partial, PartialCurve, PartialCycle, + PartialGlobalEdge, PartialGlobalVertex, PartialHalfEdge, + PartialSurfaceVertex, PartialVertex, }; macro_rules! impl_traits { @@ -40,6 +42,7 @@ macro_rules! impl_traits { impl_traits!( Curve, PartialCurve; + Cycle, PartialCycle; GlobalEdge, PartialGlobalEdge; GlobalVertex, PartialGlobalVertex; HalfEdge, PartialHalfEdge;