diff --git a/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs b/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs index 55486f2ee..065e7f44c 100644 --- a/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs +++ b/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs @@ -75,7 +75,7 @@ mod tests { use fj_math::Point; use crate::{ - builder::CurveBuilder, + builder::{CurveBuilder, HalfEdgeBuilder}, objects::{Curve, HalfEdge, Objects}, partial::HasPartial, }; @@ -93,7 +93,7 @@ mod tests { .build(&objects)?; let half_edge = HalfEdge::partial() .with_surface(Some(surface)) - .as_line_segment_from_points([[1., -1.], [1., 1.]]) + .update_as_line_segment_from_points([[1., -1.], [1., 1.]]) .build(&objects)?; let intersection = CurveEdgeIntersection::compute(&curve, &half_edge); @@ -118,7 +118,7 @@ mod tests { .build(&objects)?; let half_edge = HalfEdge::partial() .with_surface(Some(surface)) - .as_line_segment_from_points([[-1., -1.], [-1., 1.]]) + .update_as_line_segment_from_points([[-1., -1.], [-1., 1.]]) .build(&objects)?; let intersection = CurveEdgeIntersection::compute(&curve, &half_edge); @@ -143,7 +143,7 @@ mod tests { .build(&objects)?; let half_edge = HalfEdge::partial() .with_surface(Some(surface)) - .as_line_segment_from_points([[-1., -1.], [1., -1.]]) + .update_as_line_segment_from_points([[-1., -1.], [1., -1.]]) .build(&objects)?; let intersection = CurveEdgeIntersection::compute(&curve, &half_edge); @@ -163,7 +163,7 @@ mod tests { .build(&objects)?; let half_edge = HalfEdge::partial() .with_surface(Some(surface)) - .as_line_segment_from_points([[-1., 0.], [1., 0.]]) + .update_as_line_segment_from_points([[-1., 0.], [1., 0.]]) .build(&objects)?; let intersection = CurveEdgeIntersection::compute(&curve, &half_edge); diff --git a/crates/fj-kernel/src/algorithms/sweep/edge.rs b/crates/fj-kernel/src/algorithms/sweep/edge.rs index 6aeeafe3c..40b44d62d 100644 --- a/crates/fj-kernel/src/algorithms/sweep/edge.rs +++ b/crates/fj-kernel/src/algorithms/sweep/edge.rs @@ -189,6 +189,7 @@ mod tests { use crate::{ algorithms::{reverse::Reverse, sweep::Sweep}, + builder::HalfEdgeBuilder, objects::{Cycle, Face, HalfEdge, Objects, SurfaceVertex, Vertex}, partial::HasPartial, }; @@ -199,7 +200,7 @@ mod tests { let half_edge = HalfEdge::partial() .with_surface(Some(objects.surfaces.xy_plane())) - .as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) .build(&objects)?; let face = @@ -210,7 +211,7 @@ mod tests { let bottom = HalfEdge::partial() .with_surface(Some(surface.clone())) - .as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) .build(&objects)?; let side_up = HalfEdge::partial() .with_surface(Some(surface.clone())) @@ -222,7 +223,7 @@ mod tests { SurfaceVertex::partial().with_position(Some([1., 1.])), ), ))) - .as_line_segment() + .update_as_line_segment() .build(&objects)?; let top = HalfEdge::partial() .with_surface(Some(surface.clone())) @@ -234,7 +235,7 @@ mod tests { .with_front_vertex(Some(Vertex::partial().with_surface_form( Some(side_up.front().surface_form().clone()), ))) - .as_line_segment() + .update_as_line_segment() .build(&objects)? .reverse(&objects)?; let side_down = HalfEdge::partial() @@ -245,7 +246,7 @@ mod tests { .with_front_vertex(Some(Vertex::partial().with_surface_form( Some(top.front().surface_form().clone()), ))) - .as_line_segment() + .update_as_line_segment() .build(&objects)? .reverse(&objects)?; diff --git a/crates/fj-kernel/src/algorithms/sweep/face.rs b/crates/fj-kernel/src/algorithms/sweep/face.rs index ef79f1a1b..2a95a7438 100644 --- a/crates/fj-kernel/src/algorithms/sweep/face.rs +++ b/crates/fj-kernel/src/algorithms/sweep/face.rs @@ -84,6 +84,7 @@ mod tests { use crate::{ algorithms::{reverse::Reverse, transform::TransformObject}, + builder::HalfEdgeBuilder, objects::{Face, HalfEdge, Objects, Sketch}, partial::HasPartial, }; @@ -125,7 +126,7 @@ mod tests { .map(|&[a, b]| { let half_edge = HalfEdge::partial() .with_surface(Some(objects.surfaces.xy_plane())) - .as_line_segment_from_points([a, b]) + .update_as_line_segment_from_points([a, b]) .build(&objects)?; (half_edge, Color::default()).sweep(UP, &objects) }) @@ -167,7 +168,7 @@ mod tests { .map(|&[a, b]| { let half_edge = HalfEdge::partial() .with_surface(Some(objects.surfaces.xy_plane())) - .as_line_segment_from_points([a, b]) + .update_as_line_segment_from_points([a, b]) .build(&objects)? .reverse(&objects)?; (half_edge, Color::default()).sweep(DOWN, &objects) diff --git a/crates/fj-kernel/src/algorithms/sweep/vertex.rs b/crates/fj-kernel/src/algorithms/sweep/vertex.rs index 8b55c5d46..9946dc987 100644 --- a/crates/fj-kernel/src/algorithms/sweep/vertex.rs +++ b/crates/fj-kernel/src/algorithms/sweep/vertex.rs @@ -168,7 +168,7 @@ mod tests { use crate::{ algorithms::sweep::Sweep, - builder::CurveBuilder, + builder::{CurveBuilder, HalfEdgeBuilder}, objects::{Curve, HalfEdge, Objects, Vertex}, partial::HasPartial, }; @@ -192,7 +192,7 @@ mod tests { let expected_half_edge = HalfEdge::partial() .with_surface(Some(surface)) - .as_line_segment_from_points([[0., 0.], [0., 1.]]) + .update_as_line_segment_from_points([[0., 0.], [0., 1.]]) .build(&objects)?; assert_eq!(half_edge, expected_half_edge); Ok(()) diff --git a/crates/fj-kernel/src/algorithms/transform/cycle.rs b/crates/fj-kernel/src/algorithms/transform/cycle.rs index c515fb807..1cbf50694 100644 --- a/crates/fj-kernel/src/algorithms/transform/cycle.rs +++ b/crates/fj-kernel/src/algorithms/transform/cycle.rs @@ -13,25 +13,21 @@ impl TransformObject for PartialCycle { objects: &Objects, ) -> Result { let surface = self - .surface - .clone() + .surface() .map(|surface| surface.transform(transform, objects)) .transpose()?; let half_edges = self - .half_edges - .into_iter() + .half_edges() .map(|edge| { Ok(edge .into_partial() .transform(transform, objects)? - .with_surface(surface.clone()) - .into()) + .with_surface(surface.clone())) }) - .collect::>()?; + .collect::, ValidationError>>()?; - Ok(Self { - surface, - half_edges, - }) + Ok(Self::default() + .with_surface(surface) + .with_half_edges(half_edges)) } } diff --git a/crates/fj-kernel/src/algorithms/transform/edge.rs b/crates/fj-kernel/src/algorithms/transform/edge.rs index 2d9b3b95f..3e7dac5ae 100644 --- a/crates/fj-kernel/src/algorithms/transform/edge.rs +++ b/crates/fj-kernel/src/algorithms/transform/edge.rs @@ -16,38 +16,35 @@ impl TransformObject for PartialHalfEdge { objects: &Objects, ) -> Result { let surface = self - .surface + .surface() .map(|surface| surface.transform(transform, objects)) .transpose()?; let curve: MaybePartial<_> = self - .curve - .clone() + .curve() .into_partial() .transform(transform, objects)? .with_surface(surface.clone()) .into(); - let vertices = self.vertices.clone().try_map_ext( + let vertices = self.vertices().try_map_ext( |vertex| -> Result<_, ValidationError> { Ok(vertex .into_partial() .transform(transform, objects)? - .with_curve(Some(curve.clone())) - .into()) + .with_curve(Some(curve.clone()))) }, )?; let global_form = self - .global_form + .global_form() .into_partial() .transform(transform, objects)? .with_curve(curve.global_form()) .into(); - Ok(Self { - surface, - curve, - vertices, - global_form, - }) + Ok(Self::default() + .with_surface(surface) + .with_curve(Some(curve)) + .with_vertices(Some(vertices)) + .with_global_form(global_form)) } } @@ -57,9 +54,9 @@ impl TransformObject for PartialGlobalEdge { transform: &Transform, objects: &Objects, ) -> Result { - let curve = self.curve.transform(transform, objects)?; + let curve = self.curve().transform(transform, objects)?; let vertices = self - .vertices + .vertices() .map(|vertices| { vertices.try_map_ext(|vertex| -> Result<_, ValidationError> { vertex.transform(transform, objects) @@ -67,6 +64,8 @@ impl TransformObject for PartialGlobalEdge { }) .transpose()?; - Ok(Self { curve, vertices }) + Ok(Self::default() + .with_curve(Some(curve)) + .with_vertices(vertices)) } } diff --git a/crates/fj-kernel/src/builder/cycle.rs b/crates/fj-kernel/src/builder/cycle.rs new file mode 100644 index 000000000..04251a2fc --- /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 vertices = vertices.into_iter().map(Into::into); + + 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)) + })) + } + + 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/edge.rs b/crates/fj-kernel/src/builder/edge.rs new file mode 100644 index 000000000..e23263f9c --- /dev/null +++ b/crates/fj-kernel/src/builder/edge.rs @@ -0,0 +1,203 @@ +use fj_interop::ext::ArrayExt; +use fj_math::{Point, Scalar}; + +use crate::{ + objects::{ + Curve, GlobalVertex, Objects, SurfaceVertex, Vertex, + VerticesInNormalizedOrder, + }, + partial::{HasPartial, PartialGlobalEdge, PartialHalfEdge}, + storage::Handle, + validate::ValidationError, +}; + +use super::{CurveBuilder, GlobalVertexBuilder}; + +/// Builder API for [`PartialHalfEdge`] +pub trait HalfEdgeBuilder: Sized { + /// Update partial half-edge as a circle, from the given radius + /// + /// # Implementation Note + /// + /// In principle, only the `build` method should take a reference to + /// [`Objects`]. As of this writing, this method is the only one that + /// deviates from that. I couldn't think of a way to do it better. + fn update_as_circle_from_radius( + self, + radius: impl Into, + objects: &Objects, + ) -> Result; + + /// Update partial half-edge as a line segment, from the given points + fn update_as_line_segment_from_points( + self, + points: [impl Into>; 2], + ) -> Self; + + /// Update partial half-edge as a line segment, reusing existing vertices + fn update_as_line_segment(self) -> Self; +} + +impl HalfEdgeBuilder for PartialHalfEdge { + fn update_as_circle_from_radius( + self, + radius: impl Into, + objects: &Objects, + ) -> Result { + let curve = Curve::partial() + .with_global_form(Some(self.extract_global_curve())) + .with_surface(self.surface()) + .update_as_circle_from_radius(radius); + + let path = curve.path().expect("Expected path that was just created"); + + let [a_curve, b_curve] = + [Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord])); + + let global_vertex = self + .extract_global_vertices() + .map(|[global_form, _]| global_form) + .unwrap_or_else(|| { + GlobalVertex::partial() + .update_from_curve_and_position(curve.clone(), a_curve) + .into() + }); + + let surface_vertex = SurfaceVertex::partial() + .with_position(Some(path.point_from_path_coords(a_curve))) + .with_surface(self.surface()) + .with_global_form(Some(global_vertex)) + .build(objects)?; + + let [back, front] = [a_curve, b_curve].map(|point_curve| { + Vertex::partial() + .with_position(Some(point_curve)) + .with_curve(Some(curve.clone())) + .with_surface_form(Some(surface_vertex.clone())) + }); + + Ok(self + .with_curve(Some(curve)) + .with_vertices(Some([back, front]))) + } + + fn update_as_line_segment_from_points( + self, + points: [impl Into>; 2], + ) -> Self { + let surface = self.surface(); + let vertices = points.map(|point| { + let surface_form = SurfaceVertex::partial() + .with_surface(surface.clone()) + .with_position(Some(point)); + + Vertex::partial().with_surface_form(Some(surface_form)) + }); + + self.with_vertices(Some(vertices)).update_as_line_segment() + } + + fn update_as_line_segment(self) -> Self { + let [from, to] = self.vertices(); + let [from_surface, to_surface] = + [&from, &to].map(|vertex| vertex.surface_form()); + + let surface = self + .surface() + .or_else(|| from_surface.surface()) + .or_else(|| to_surface.surface()) + .expect("Can't infer line segment without a surface"); + let points = [&from_surface, &to_surface].map(|vertex| { + vertex + .position() + .expect("Can't infer line segment without surface position") + }); + + let curve = Curve::partial() + .with_global_form(Some(self.extract_global_curve())) + .with_surface(Some(surface)) + .update_as_line_from_points(points); + + let [back, front] = { + let vertices = [(from, 0.), (to, 1.)].map(|(vertex, position)| { + vertex.update_partial(|vertex| { + vertex + .with_position(Some([position])) + .with_curve(Some(curve.clone())) + }) + }); + + // The global vertices we extracted are in normalized order, which + // means we might need to switch their order here. This is a bit of + // a hack, but I can't think of something better. + let global_forms = { + let must_switch_order = { + let objects = Objects::new(); + let vertices = vertices.clone().map(|vertex| { + vertex + .into_full(&objects) + .unwrap() + .global_form() + .clone() + }); + + let (_, must_switch_order) = + VerticesInNormalizedOrder::new(vertices); + + must_switch_order + }; + + self.extract_global_vertices() + .map( + |[a, b]| { + if must_switch_order { + [b, a] + } else { + [a, b] + } + }, + ) + .map(|[a, b]| [Some(a), Some(b)]) + .unwrap_or([None, None]) + }; + + vertices.zip_ext(global_forms).map(|(vertex, global_form)| { + vertex.update_partial(|vertex| { + vertex.clone().with_surface_form(Some( + vertex.surface_form().update_partial( + |surface_vertex| { + surface_vertex.with_global_form(global_form) + }, + ), + )) + }) + }) + }; + + self.with_curve(Some(curve)) + .with_vertices(Some([back, front])) + } +} + +/// Builder API for [`PartialGlobalEdge`] +pub trait GlobalEdgeBuilder { + /// Update partial global edge from the given curve and vertices + fn update_from_curve_and_vertices( + self, + curve: &Curve, + vertices: &[Handle; 2], + ) -> Self; +} + +impl GlobalEdgeBuilder for PartialGlobalEdge { + fn update_from_curve_and_vertices( + self, + curve: &Curve, + vertices: &[Handle; 2], + ) -> Self { + self.with_curve(Some(curve.global_form().clone())) + .with_vertices(Some( + vertices.clone().map(|vertex| vertex.global_form().clone()), + )) + } +} 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 ab4abf95d..9dafd5326 100644 --- a/crates/fj-kernel/src/builder/mod.rs +++ b/crates/fj-kernel/src/builder/mod.rs @@ -10,10 +10,14 @@ 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, sketch::SketchBuilder, diff --git a/crates/fj-kernel/src/builder/shell.rs b/crates/fj-kernel/src/builder/shell.rs index 029284398..83c73cf29 100644 --- a/crates/fj-kernel/src/builder/shell.rs +++ b/crates/fj-kernel/src/builder/shell.rs @@ -5,6 +5,7 @@ use fj_math::Scalar; use crate::{ algorithms::transform::TransformObject, + builder::HalfEdgeBuilder, objects::{ Curve, Cycle, Face, FaceSet, HalfEdge, Objects, Shell, Surface, SurfaceVertex, Vertex, @@ -90,7 +91,10 @@ impl<'a> ShellBuilder<'a> { HalfEdge::partial() .with_surface(Some(surface.clone())) .with_global_form(Some(half_edge.global_form().clone())) - .as_line_segment_from_points([[Z, Z], [edge_length, Z]]) + .update_as_line_segment_from_points([ + [Z, Z], + [edge_length, Z], + ]) .build(self.objects) .unwrap() }) @@ -113,7 +117,7 @@ impl<'a> ShellBuilder<'a> { Vertex::partial().with_surface_form(Some(from)), Vertex::partial().with_surface_form(Some(to)), ])) - .as_line_segment() + .update_as_line_segment() .build(self.objects) .unwrap() }) @@ -150,7 +154,7 @@ impl<'a> ShellBuilder<'a> { Vertex::partial().with_surface_form(Some(from)), Vertex::partial().with_surface_form(Some(to)), ])) - .as_line_segment() + .update_as_line_segment() .build(self.objects) .unwrap() }) @@ -173,7 +177,7 @@ impl<'a> ShellBuilder<'a> { HalfEdge::partial() .with_vertices(Some([from, to])) - .as_line_segment() + .update_as_line_segment() .build(self.objects) .unwrap() }) @@ -252,7 +256,7 @@ impl<'a> ShellBuilder<'a> { HalfEdge::partial() .with_vertices(Some(vertices)) .with_global_form(Some(edge.global_form().clone())) - .as_line_segment() + .update_as_line_segment() .build(self.objects) .unwrap(), ); diff --git a/crates/fj-kernel/src/iter.rs b/crates/fj-kernel/src/iter.rs index 2538869c6..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, + builder::{CurveBuilder, CycleBuilder, HalfEdgeBuilder}, objects::{ Curve, Cycle, Face, GlobalCurve, GlobalVertex, HalfEdge, Objects, Shell, Sketch, Solid, SurfaceVertex, Vertex, @@ -486,7 +486,7 @@ mod tests { let object = HalfEdge::partial() .with_surface(Some(objects.surfaces.xy_plane())) - .as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) .build(&objects); assert_eq!(1, object.curve_iter().count()); diff --git a/crates/fj-kernel/src/objects/edge.rs b/crates/fj-kernel/src/objects/edge.rs index 2eb6e1bcd..5e5dc45fb 100644 --- a/crates/fj-kernel/src/objects/edge.rs +++ b/crates/fj-kernel/src/objects/edge.rs @@ -149,7 +149,9 @@ impl VerticesInNormalizedOrder { mod tests { use pretty_assertions::assert_eq; - use crate::{objects::Objects, partial::HasPartial}; + use crate::{ + builder::HalfEdgeBuilder, objects::Objects, partial::HasPartial, + }; use super::HalfEdge; @@ -164,11 +166,11 @@ mod tests { let a_to_b = HalfEdge::partial() .with_surface(Some(surface.clone())) - .as_line_segment_from_points([a, b]) + .update_as_line_segment_from_points([a, b]) .build(&objects)?; let b_to_a = HalfEdge::partial() .with_surface(Some(surface)) - .as_line_segment_from_points([b, a]) + .update_as_line_segment_from_points([b, a]) .build(&objects)?; assert_eq!(a_to_b.global_form(), b_to_a.global_form()); diff --git a/crates/fj-kernel/src/partial/maybe_partial.rs b/crates/fj-kernel/src/partial/maybe_partial.rs index 5b5e43988..7431df451 100644 --- a/crates/fj-kernel/src/partial/maybe_partial.rs +++ b/crates/fj-kernel/src/partial/maybe_partial.rs @@ -144,7 +144,7 @@ impl MaybePartial { pub fn curve(&self) -> MaybePartial { match self { Self::Full(full) => full.curve().clone().into(), - Self::Partial(partial) => partial.curve.clone(), + Self::Partial(partial) => partial.curve(), } } @@ -154,7 +154,7 @@ impl MaybePartial { Self::Full(full) => Some( full.vertices().access_in_normalized_order().map(Into::into), ), - Self::Partial(partial) => partial.vertices.clone(), + Self::Partial(partial) => partial.vertices(), } } } @@ -165,7 +165,7 @@ impl MaybePartial { match self { Self::Full(full) => full.front().clone().into(), Self::Partial(partial) => { - let [_, front] = &partial.vertices; + let [_, front] = &partial.vertices(); front.clone() } } @@ -175,7 +175,7 @@ impl MaybePartial { pub fn vertices(&self) -> [MaybePartial; 2] { match self { Self::Full(full) => full.vertices().clone().map(Into::into), - Self::Partial(partial) => partial.vertices.clone(), + Self::Partial(partial) => partial.vertices(), } } } diff --git a/crates/fj-kernel/src/partial/objects/cycle.rs b/crates/fj-kernel/src/partial/objects/cycle.rs index 65c16f6d4..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, - objects::{ - Curve, Cycle, HalfEdge, Objects, Surface, SurfaceVertex, Vertex, - }, - partial::{HasPartial, MaybePartial}, + objects::{Cycle, HalfEdge, Objects, Surface}, + partial::MaybePartial, storage::Handle, validate::ValidationError, }; @@ -15,14 +10,21 @@ use crate::{ /// See [`crate::partial`] for more information. #[derive(Clone, Debug, Default)] 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>, + surface: Option>, + half_edges: Vec>, } impl PartialCycle { + /// Access the surface that the [`Cycle`] is defined in + pub fn surface(&self) -> Option> { + self.surface.clone() + } + + /// Access the half-edges that make up the [`Cycle`] + pub fn half_edges(&self) -> impl Iterator> { + self.half_edges.clone().into_iter() + } + /// Update the partial cycle with the given surface pub fn with_surface(mut self, surface: Option>) -> Self { if let Some(surface) = surface { @@ -34,121 +36,10 @@ impl PartialCycle { /// Update the partial cycle with the given half-edges pub fn with_half_edges( mut self, - half_edge: impl IntoIterator>>, + half_edges: 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( - mut 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; - - 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)) - }); - - self.half_edges.push( - HalfEdge::partial() - .with_curve(Some(curve)) - .with_vertices(Some([from, to])) - .into(), - ); - - continue; - } - - previous = Some(vertex_next); - } - - self - } - - /// 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(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())); - - if let [Some([first, _]), Some([_, last])] = vertices { - let vertices = [last, first].map(|vertex| { - vertex - .surface_form() - .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() - .with_surface(Some(surface)) - .as_line_segment_from_points(vertices) - .into(), - ); - } - + .extend(half_edges.into_iter().map(Into::into)); self } @@ -202,7 +93,7 @@ impl PartialCycle { let half_edge = half_edge .update_partial(|half_edge| { - let [back, _] = half_edge.vertices.clone(); + let [back, _] = half_edge.vertices(); let back = back.update_partial(|partial| { partial.with_surface_form(previous_vertex) }); diff --git a/crates/fj-kernel/src/partial/objects/edge.rs b/crates/fj-kernel/src/partial/objects/edge.rs index 3df61f925..4265d158c 100644 --- a/crates/fj-kernel/src/partial/objects/edge.rs +++ b/crates/fj-kernel/src/partial/objects/edge.rs @@ -1,13 +1,12 @@ use fj_interop::ext::ArrayExt; -use fj_math::{Point, Scalar}; use crate::{ - builder::{CurveBuilder, GlobalVertexBuilder}, + builder::GlobalEdgeBuilder, objects::{ Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects, - Surface, SurfaceVertex, Vertex, VerticesInNormalizedOrder, + Surface, Vertex, }, - partial::{HasPartial, MaybePartial}, + partial::MaybePartial, storage::Handle, validate::ValidationError, }; @@ -17,22 +16,33 @@ use crate::{ /// See [`crate::partial`] for more information. #[derive(Clone, Debug, Default)] pub struct PartialHalfEdge { - /// The surface that the [`HalfEdge`]'s [`Curve`] is defined in - pub surface: Option>, - - /// The curve that the [`HalfEdge`] is defined in - pub curve: MaybePartial, - - /// The vertices that bound this [`HalfEdge`] in the [`Curve`] - pub vertices: [MaybePartial; 2], - - /// The global form of the [`HalfEdge`] - /// - /// Can be computed by [`PartialHalfEdge::build`], if not available. - pub global_form: MaybePartial, + surface: Option>, + curve: MaybePartial, + vertices: [MaybePartial; 2], + global_form: MaybePartial, } impl PartialHalfEdge { + /// Access the surface that the [`HalfEdge`]'s [`Curve`] is defined in + pub fn surface(&self) -> Option> { + self.surface.clone() + } + + /// Access the curve that the [`HalfEdge`] is defined in + pub fn curve(&self) -> MaybePartial { + self.curve.clone() + } + + /// Access the vertices that bound this [`HalfEdge`] in the [`Curve`] + pub fn vertices(&self) -> [MaybePartial; 2] { + self.vertices.clone() + } + + /// Access the global form of the [`HalfEdge`] + pub fn global_form(&self) -> MaybePartial { + self.global_form.clone() + } + /// Extract the global curve from either the curve or global form /// /// If a global curve is available through both, the curve is preferred. @@ -115,159 +125,6 @@ impl PartialHalfEdge { self } - /// Update partial half-edge as a circle, from the given radius - /// - /// # Implementation Note - /// - /// In principle, only the `build` method should take a reference to - /// [`Objects`]. As of this writing, this method is the only one that - /// deviates from that. I couldn't think of a way to do it better. - pub fn as_circle_from_radius( - mut self, - radius: impl Into, - objects: &Objects, - ) -> Result { - let curve = Curve::partial() - .with_global_form(Some(self.extract_global_curve())) - .with_surface(self.surface.clone()) - .update_as_circle_from_radius(radius); - - let path = curve.path().expect("Expected path that was just created"); - - let [a_curve, b_curve] = - [Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord])); - - let global_vertex = self - .extract_global_vertices() - .map(|[global_form, _]| global_form) - .unwrap_or_else(|| { - GlobalVertex::partial() - .update_from_curve_and_position(curve.clone(), a_curve) - .into() - }); - - let surface_vertex = SurfaceVertex::partial() - .with_position(Some(path.point_from_path_coords(a_curve))) - .with_surface(self.surface.clone()) - .with_global_form(Some(global_vertex)) - .build(objects)?; - - let [back, front] = [a_curve, b_curve].map(|point_curve| { - Vertex::partial() - .with_position(Some(point_curve)) - .with_curve(Some(curve.clone())) - .with_surface_form(Some(surface_vertex.clone())) - .into() - }); - - self.curve = curve.into(); - self.vertices = [back, front]; - - Ok(self) - } - - /// Update partial half-edge as a line segment, from the given points - pub fn as_line_segment_from_points( - self, - points: [impl Into>; 2], - ) -> Self { - let surface = self.surface.clone(); - let vertices = points.map(|point| { - let surface_form = SurfaceVertex::partial() - .with_surface(surface.clone()) - .with_position(Some(point)); - - Vertex::partial().with_surface_form(Some(surface_form)) - }); - - self.with_vertices(Some(vertices)).as_line_segment() - } - - /// Update partial half-edge as a line segment, reusing existing vertices - pub fn as_line_segment(mut self) -> Self { - let [from, to] = self.vertices.clone(); - let [from_surface, to_surface] = - [&from, &to].map(|vertex| vertex.surface_form()); - - let surface = self - .surface - .clone() - .or_else(|| from_surface.surface()) - .or_else(|| to_surface.surface()) - .expect("Can't infer line segment without a surface"); - let points = [&from_surface, &to_surface].map(|vertex| { - vertex - .position() - .expect("Can't infer line segment without surface position") - }); - - let curve = Curve::partial() - .with_global_form(Some(self.extract_global_curve())) - .with_surface(Some(surface)) - .update_as_line_from_points(points); - - let [back, front] = { - let vertices = [(from, 0.), (to, 1.)].map(|(vertex, position)| { - vertex.update_partial(|vertex| { - vertex - .with_position(Some([position])) - .with_curve(Some(curve.clone())) - }) - }); - - // The global vertices we extracted are in normalized order, which - // means we might need to switch their order here. This is a bit of - // a hack, but I can't think of something better. - let global_forms = { - let must_switch_order = { - let objects = Objects::new(); - let vertices = vertices.clone().map(|vertex| { - vertex - .into_full(&objects) - .unwrap() - .global_form() - .clone() - }); - - let (_, must_switch_order) = - VerticesInNormalizedOrder::new(vertices); - - must_switch_order - }; - - self.extract_global_vertices() - .map( - |[a, b]| { - if must_switch_order { - [b, a] - } else { - [a, b] - } - }, - ) - .map(|[a, b]| [Some(a), Some(b)]) - .unwrap_or([None, None]) - }; - - vertices.zip_ext(global_forms).map(|(vertex, global_form)| { - vertex.update_partial(|vertex| { - vertex.clone().with_surface_form(Some( - vertex.surface_form().update_partial( - |surface_vertex| { - surface_vertex.with_global_form(global_form) - }, - ), - )) - }) - }) - }; - - self.curve = curve.into(); - self.vertices = [back, front]; - - self - } - /// Build a full [`HalfEdge`] from the partial half-edge pub fn build( self, @@ -287,7 +144,7 @@ impl PartialHalfEdge { let global_form = self .global_form .update_partial(|partial| { - partial.from_curve_and_vertices(&curve, &vertices) + partial.update_from_curve_and_vertices(&curve, &vertices) }) .into_full(objects)?; @@ -316,18 +173,21 @@ impl From<&HalfEdge> for PartialHalfEdge { /// See [`crate::partial`] for more information. #[derive(Clone, Debug, Default)] pub struct PartialGlobalEdge { - /// The curve that the [`GlobalEdge`] is defined in - /// - /// Must be provided before [`PartialGlobalEdge::build`] is called. - pub curve: MaybePartial, - - /// The vertices that bound the [`GlobalEdge`] in the curve - /// - /// Must be provided before [`PartialGlobalEdge::build`] is called. - pub vertices: Option<[MaybePartial; 2]>, + curve: MaybePartial, + vertices: Option<[MaybePartial; 2]>, } impl PartialGlobalEdge { + /// Access the curve that the [`GlobalEdge`] is defined in + pub fn curve(&self) -> MaybePartial { + self.curve.clone() + } + + /// Access the vertices that bound the [`GlobalEdge`] in the curve + pub fn vertices(&self) -> Option<[MaybePartial; 2]> { + self.vertices.clone() + } + /// Update the partial global edge with the given curve pub fn with_curve( mut self, @@ -350,18 +210,6 @@ impl PartialGlobalEdge { self } - /// Update partial global edge from the given curve and vertices - pub fn from_curve_and_vertices( - self, - curve: &Curve, - vertices: &[Handle; 2], - ) -> Self { - self.with_curve(Some(curve.global_form().clone())) - .with_vertices(Some( - vertices.clone().map(|vertex| vertex.global_form().clone()), - )) - } - /// Build a full [`GlobalEdge`] from the partial global edge pub fn build( self, diff --git a/crates/fj-kernel/src/validate/edge.rs b/crates/fj-kernel/src/validate/edge.rs index fddd9a618..3f2f2e3ad 100644 --- a/crates/fj-kernel/src/validate/edge.rs +++ b/crates/fj-kernel/src/validate/edge.rs @@ -196,7 +196,7 @@ mod tests { use fj_interop::ext::ArrayExt; use crate::{ - builder::VertexBuilder, + builder::{HalfEdgeBuilder, VertexBuilder}, objects::{GlobalCurve, HalfEdge, Objects}, partial::HasPartial, validate::Validate2, @@ -208,7 +208,7 @@ mod tests { let valid = HalfEdge::partial() .with_surface(Some(objects.surfaces.xy_plane())) - .as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) .build(&objects)?; let invalid = { let mut vertices = valid.vertices().clone(); @@ -233,7 +233,7 @@ mod tests { let valid = HalfEdge::partial() .with_surface(Some(objects.surfaces.xy_plane())) - .as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) .build(&objects)?; let invalid = HalfEdge::new( valid.vertices().clone(), @@ -256,7 +256,7 @@ mod tests { let valid = HalfEdge::partial() .with_surface(Some(objects.surfaces.xy_plane())) - .as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) .build(&objects)?; let invalid = HalfEdge::new( valid.vertices().clone(), @@ -286,7 +286,7 @@ mod tests { let valid = HalfEdge::partial() .with_surface(Some(objects.surfaces.xy_plane())) - .as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) .build(&objects)?; let invalid = HalfEdge::new( valid.vertices().clone().try_map_ext(|vertex| { diff --git a/crates/fj-kernel/src/validate/vertex.rs b/crates/fj-kernel/src/validate/vertex.rs index 2257510e0..075c57cf1 100644 --- a/crates/fj-kernel/src/validate/vertex.rs +++ b/crates/fj-kernel/src/validate/vertex.rs @@ -51,8 +51,8 @@ pub enum VertexValidationError { /// Mismatch between the surface's of the curve and surface form #[error( "Surface form of vertex must be defined on same surface as curve\n\ - `- Surface` of curve: {curve_surface:#?}\n\ - `- Surface` of surface form: {surface_form_surface:#?}" + - `Surface` of curve: {curve_surface:#?}\n\ + - `Surface` of surface form: {surface_form_surface:#?}" )] SurfaceMismatch { /// The surface of the vertex' curve diff --git a/crates/fj-operations/src/sketch.rs b/crates/fj-operations/src/sketch.rs index 3f91514ca..0250d1184 100644 --- a/crates/fj-operations/src/sketch.rs +++ b/crates/fj-operations/src/sketch.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use fj_interop::{debug::DebugInfo, mesh::Color}; use fj_kernel::{ + builder::HalfEdgeBuilder, objects::{Cycle, Face, HalfEdge, Objects, Sketch}, partial::HasPartial, validate::{Validate, Validated, ValidationConfig, ValidationError}, @@ -28,7 +29,7 @@ impl Shape for fj::Sketch { let half_edge = HalfEdge::partial() .with_surface(Some(surface)) - .as_circle_from_radius(circle.radius(), objects)? + .update_as_circle_from_radius(circle.radius(), objects)? .build(objects)?; let cycle = objects.cycles.insert(Cycle::new([half_edge]))?;