diff --git a/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs b/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs index 065e7f44c..4b6376820 100644 --- a/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs +++ b/crates/fj-kernel/src/algorithms/intersect/curve_edge.rs @@ -92,8 +92,7 @@ mod tests { .update_as_u_axis() .build(&objects)?; let half_edge = HalfEdge::partial() - .with_surface(Some(surface)) - .update_as_line_segment_from_points([[1., -1.], [1., 1.]]) + .update_as_line_segment_from_points(surface, [[1., -1.], [1., 1.]]) .build(&objects)?; let intersection = CurveEdgeIntersection::compute(&curve, &half_edge); @@ -117,8 +116,10 @@ mod tests { .update_as_u_axis() .build(&objects)?; let half_edge = HalfEdge::partial() - .with_surface(Some(surface)) - .update_as_line_segment_from_points([[-1., -1.], [-1., 1.]]) + .update_as_line_segment_from_points( + surface, + [[-1., -1.], [-1., 1.]], + ) .build(&objects)?; let intersection = CurveEdgeIntersection::compute(&curve, &half_edge); @@ -142,8 +143,10 @@ mod tests { .update_as_u_axis() .build(&objects)?; let half_edge = HalfEdge::partial() - .with_surface(Some(surface)) - .update_as_line_segment_from_points([[-1., -1.], [1., -1.]]) + .update_as_line_segment_from_points( + surface, + [[-1., -1.], [1., -1.]], + ) .build(&objects)?; let intersection = CurveEdgeIntersection::compute(&curve, &half_edge); @@ -162,8 +165,7 @@ mod tests { .update_as_u_axis() .build(&objects)?; let half_edge = HalfEdge::partial() - .with_surface(Some(surface)) - .update_as_line_segment_from_points([[-1., 0.], [1., 0.]]) + .update_as_line_segment_from_points(surface, [[-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 40b44d62d..2f3cce225 100644 --- a/crates/fj-kernel/src/algorithms/sweep/edge.rs +++ b/crates/fj-kernel/src/algorithms/sweep/edge.rs @@ -199,8 +199,10 @@ mod tests { let objects = Objects::new(); let half_edge = HalfEdge::partial() - .with_surface(Some(objects.surfaces.xy_plane())) - .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points( + objects.surfaces.xy_plane(), + [[0., 0.], [1., 0.]], + ) .build(&objects)?; let face = @@ -210,8 +212,10 @@ mod tests { let surface = objects.surfaces.xz_plane(); let bottom = HalfEdge::partial() - .with_surface(Some(surface.clone())) - .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points( + surface.clone(), + [[0., 0.], [1., 0.]], + ) .build(&objects)?; let side_up = HalfEdge::partial() .with_surface(Some(surface.clone())) diff --git a/crates/fj-kernel/src/algorithms/sweep/face.rs b/crates/fj-kernel/src/algorithms/sweep/face.rs index 2a95a7438..eb44fc87c 100644 --- a/crates/fj-kernel/src/algorithms/sweep/face.rs +++ b/crates/fj-kernel/src/algorithms/sweep/face.rs @@ -125,8 +125,10 @@ mod tests { .array_windows_ext() .map(|&[a, b]| { let half_edge = HalfEdge::partial() - .with_surface(Some(objects.surfaces.xy_plane())) - .update_as_line_segment_from_points([a, b]) + .update_as_line_segment_from_points( + objects.surfaces.xy_plane(), + [a, b], + ) .build(&objects)?; (half_edge, Color::default()).sweep(UP, &objects) }) @@ -167,8 +169,10 @@ mod tests { .array_windows_ext() .map(|&[a, b]| { let half_edge = HalfEdge::partial() - .with_surface(Some(objects.surfaces.xy_plane())) - .update_as_line_segment_from_points([a, b]) + .update_as_line_segment_from_points( + objects.surfaces.xy_plane(), + [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 9946dc987..f348b561d 100644 --- a/crates/fj-kernel/src/algorithms/sweep/vertex.rs +++ b/crates/fj-kernel/src/algorithms/sweep/vertex.rs @@ -191,8 +191,7 @@ mod tests { (vertex, surface.clone()).sweep([0., 0., 1.], &objects)?; let expected_half_edge = HalfEdge::partial() - .with_surface(Some(surface)) - .update_as_line_segment_from_points([[0., 0.], [0., 1.]]) + .update_as_line_segment_from_points(surface, [[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 1cbf50694..5f222af69 100644 --- a/crates/fj-kernel/src/algorithms/transform/cycle.rs +++ b/crates/fj-kernel/src/algorithms/transform/cycle.rs @@ -12,22 +12,11 @@ impl TransformObject for PartialCycle { transform: &Transform, objects: &Objects, ) -> Result { - let surface = self - .surface() - .map(|surface| surface.transform(transform, objects)) - .transpose()?; let half_edges = self .half_edges() - .map(|edge| { - Ok(edge - .into_partial() - .transform(transform, objects)? - .with_surface(surface.clone())) - }) + .map(|edge| edge.into_partial().transform(transform, objects)) .collect::, ValidationError>>()?; - Ok(Self::default() - .with_surface(surface) - .with_half_edges(half_edges)) + Ok(Self::default().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 3e7dac5ae..71c72a8d1 100644 --- a/crates/fj-kernel/src/algorithms/transform/edge.rs +++ b/crates/fj-kernel/src/algorithms/transform/edge.rs @@ -15,15 +15,10 @@ impl TransformObject for PartialHalfEdge { transform: &Transform, objects: &Objects, ) -> Result { - let surface = self - .surface() - .map(|surface| surface.transform(transform, objects)) - .transpose()?; let curve: MaybePartial<_> = self .curve() .into_partial() .transform(transform, objects)? - .with_surface(surface.clone()) .into(); let vertices = self.vertices().try_map_ext( |vertex| -> Result<_, ValidationError> { @@ -41,7 +36,6 @@ impl TransformObject for PartialHalfEdge { .into(); Ok(Self::default() - .with_surface(surface) .with_curve(Some(curve)) .with_vertices(Some(vertices)) .with_global_form(global_form)) diff --git a/crates/fj-kernel/src/builder/cycle.rs b/crates/fj-kernel/src/builder/cycle.rs index 04251a2fc..86103d627 100644 --- a/crates/fj-kernel/src/builder/cycle.rs +++ b/crates/fj-kernel/src/builder/cycle.rs @@ -1,8 +1,9 @@ use fj_math::Point; use crate::{ - objects::{Curve, HalfEdge, SurfaceVertex, Vertex}, + objects::{Curve, HalfEdge, Surface, SurfaceVertex, Vertex}, partial::{HasPartial, MaybePartial, PartialCycle}, + storage::Handle, }; use super::{CurveBuilder, HalfEdgeBuilder}; @@ -18,6 +19,7 @@ pub trait CycleBuilder { /// Update the partial cycle with a polygonal chain from the provided points fn with_poly_chain_from_points( self, + surface: Handle, points: impl IntoIterator>>, ) -> Self; @@ -49,9 +51,8 @@ impl CycleBuilder for PartialCycle { let mut half_edges = Vec::new(); for vertex_next in iter { if let Some(vertex_prev) = previous { - let surface = self + let surface = vertex_prev .surface() - .clone() .expect("Need surface to extend cycle with poly-chain"); let position_prev = vertex_prev @@ -61,12 +62,8 @@ impl CycleBuilder for PartialCycle { .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())) - }); + let from = vertex_prev; + let to = vertex_next; previous = Some(to.clone()); @@ -99,10 +96,13 @@ impl CycleBuilder for PartialCycle { fn with_poly_chain_from_points( self, + surface: Handle, points: impl IntoIterator>>, ) -> Self { self.with_poly_chain(points.into_iter().map(|position| { - SurfaceVertex::partial().with_position(Some(position)) + SurfaceVertex::partial() + .with_surface(Some(surface.clone())) + .with_position(Some(position)) })) } @@ -127,8 +127,7 @@ impl CycleBuilder for PartialCycle { self.with_half_edges(Some( HalfEdge::partial() - .with_surface(Some(surface)) - .update_as_line_segment_from_points(vertices), + .update_as_line_segment_from_points(surface, vertices), )) } } diff --git a/crates/fj-kernel/src/builder/edge.rs b/crates/fj-kernel/src/builder/edge.rs index e23263f9c..65c20f4a0 100644 --- a/crates/fj-kernel/src/builder/edge.rs +++ b/crates/fj-kernel/src/builder/edge.rs @@ -3,7 +3,7 @@ use fj_math::{Point, Scalar}; use crate::{ objects::{ - Curve, GlobalVertex, Objects, SurfaceVertex, Vertex, + Curve, GlobalVertex, Objects, Surface, SurfaceVertex, Vertex, VerticesInNormalizedOrder, }, partial::{HasPartial, PartialGlobalEdge, PartialHalfEdge}, @@ -31,6 +31,7 @@ pub trait HalfEdgeBuilder: Sized { /// Update partial half-edge as a line segment, from the given points fn update_as_line_segment_from_points( self, + surface: Handle, points: [impl Into>; 2], ) -> Self; @@ -44,9 +45,10 @@ impl HalfEdgeBuilder for PartialHalfEdge { radius: impl Into, objects: &Objects, ) -> Result { - let curve = Curve::partial() + let curve = self + .curve() + .into_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"); @@ -55,7 +57,8 @@ impl HalfEdgeBuilder for PartialHalfEdge { [Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord])); let global_vertex = self - .extract_global_vertices() + .global_form() + .vertices() .map(|[global_form, _]| global_form) .unwrap_or_else(|| { GlobalVertex::partial() @@ -65,7 +68,7 @@ impl HalfEdgeBuilder for PartialHalfEdge { let surface_vertex = SurfaceVertex::partial() .with_position(Some(path.point_from_path_coords(a_curve))) - .with_surface(self.surface()) + .with_surface(curve.surface()) .with_global_form(Some(global_vertex)) .build(objects)?; @@ -83,18 +86,20 @@ impl HalfEdgeBuilder for PartialHalfEdge { fn update_as_line_segment_from_points( self, + surface: Handle, points: [impl Into>; 2], ) -> Self { - let surface = self.surface(); let vertices = points.map(|point| { let surface_form = SurfaceVertex::partial() - .with_surface(surface.clone()) + .with_surface(Some(surface.clone())) .with_position(Some(point)); Vertex::partial().with_surface_form(Some(surface_form)) }); - self.with_vertices(Some(vertices)).update_as_line_segment() + self.with_surface(Some(surface)) + .with_vertices(Some(vertices)) + .update_as_line_segment() } fn update_as_line_segment(self) -> Self { @@ -103,6 +108,7 @@ impl HalfEdgeBuilder for PartialHalfEdge { [&from, &to].map(|vertex| vertex.surface_form()); let surface = self + .curve() .surface() .or_else(|| from_surface.surface()) .or_else(|| to_surface.surface()) @@ -147,7 +153,8 @@ impl HalfEdgeBuilder for PartialHalfEdge { must_switch_order }; - self.extract_global_vertices() + self.global_form() + .vertices() .map( |[a, b]| { if must_switch_order { diff --git a/crates/fj-kernel/src/builder/face.rs b/crates/fj-kernel/src/builder/face.rs index bb34c3768..0f217d409 100644 --- a/crates/fj-kernel/src/builder/face.rs +++ b/crates/fj-kernel/src/builder/face.rs @@ -50,10 +50,14 @@ impl<'a> FaceBuilder<'a> { mut self, points: impl IntoIterator>>, ) -> Self { + let surface = self + .surface + .as_ref() + .expect("Need surface to create polygon"); + self.exterior = Some( Cycle::partial() - .with_surface(self.surface.clone()) - .with_poly_chain_from_points(points) + .with_poly_chain_from_points(surface.clone(), points) .close_with_line_segment() .build(self.objects) .unwrap(), @@ -75,10 +79,14 @@ impl<'a> FaceBuilder<'a> { mut self, points: impl IntoIterator>>, ) -> Self { + let surface = self + .surface + .as_ref() + .expect("Need surface to build polygon."); + self.interiors.push( Cycle::partial() - .with_surface(self.surface.clone()) - .with_poly_chain_from_points(points) + .with_poly_chain_from_points(surface.clone(), points) .close_with_line_segment() .build(self.objects) .unwrap(), diff --git a/crates/fj-kernel/src/builder/shell.rs b/crates/fj-kernel/src/builder/shell.rs index 83c73cf29..1785af97b 100644 --- a/crates/fj-kernel/src/builder/shell.rs +++ b/crates/fj-kernel/src/builder/shell.rs @@ -89,12 +89,11 @@ impl<'a> ShellBuilder<'a> { .zip(&surfaces) .map(|(half_edge, surface)| { HalfEdge::partial() - .with_surface(Some(surface.clone())) .with_global_form(Some(half_edge.global_form().clone())) - .update_as_line_segment_from_points([ - [Z, Z], - [edge_length, Z], - ]) + .update_as_line_segment_from_points( + surface.clone(), + [[Z, Z], [edge_length, Z]], + ) .build(self.objects) .unwrap() }) @@ -188,10 +187,8 @@ impl<'a> ShellBuilder<'a> { .zip(sides_up) .zip(tops.clone()) .zip(sides_down) - .zip(surfaces) - .map(|((((bottom, side_up), top), side_down), surface)| { + .map(|(((bottom, side_up), top), side_down)| { let cycle = Cycle::partial() - .with_surface(Some(surface)) .with_half_edges([bottom, side_up, top, side_down]) .build(self.objects) .unwrap(); diff --git a/crates/fj-kernel/src/iter.rs b/crates/fj-kernel/src/iter.rs index 718f7c5f0..c27d3fbaf 100644 --- a/crates/fj-kernel/src/iter.rs +++ b/crates/fj-kernel/src/iter.rs @@ -399,8 +399,10 @@ mod tests { let surface = objects.surfaces.xy_plane(); let object = Cycle::partial() - .with_surface(Some(surface)) - .with_poly_chain_from_points([[0., 0.], [1., 0.], [0., 1.]]) + .with_poly_chain_from_points( + surface, + [[0., 0.], [1., 0.], [0., 1.]], + ) .close_with_line_segment() .build(&objects); @@ -485,8 +487,10 @@ mod tests { let objects = Objects::new(); let object = HalfEdge::partial() - .with_surface(Some(objects.surfaces.xy_plane())) - .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points( + objects.surfaces.xy_plane(), + [[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 5e5dc45fb..ea20d4e7d 100644 --- a/crates/fj-kernel/src/objects/edge.rs +++ b/crates/fj-kernel/src/objects/edge.rs @@ -165,12 +165,10 @@ mod tests { let b = [1., 0.]; let a_to_b = HalfEdge::partial() - .with_surface(Some(surface.clone())) - .update_as_line_segment_from_points([a, b]) + .update_as_line_segment_from_points(surface.clone(), [a, b]) .build(&objects)?; let b_to_a = HalfEdge::partial() - .with_surface(Some(surface)) - .update_as_line_segment_from_points([b, a]) + .update_as_line_segment_from_points(surface, [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 7431df451..5e7e7c1da 100644 --- a/crates/fj-kernel/src/partial/maybe_partial.rs +++ b/crates/fj-kernel/src/partial/maybe_partial.rs @@ -64,6 +64,20 @@ impl MaybePartial { } } + /// Merge this `MaybePartial` with another of the same type + pub fn merge_with(self, other: Self) -> Self { + match (self, other) { + (Self::Full(_), Self::Full(_)) => { + panic!("Can't merge two full objects") + } + (Self::Full(full), Self::Partial(_)) + | (Self::Partial(_), Self::Full(full)) => Self::Full(full), + (Self::Partial(a), Self::Partial(b)) => { + Self::Partial(a.merge_with(b)) + } + } + } + /// Return or build a full object /// /// If this already is a full object, it is returned. If this is a partial @@ -160,6 +174,14 @@ impl MaybePartial { } impl MaybePartial { + /// Access the curve + pub fn curve(&self) -> MaybePartial { + match self { + Self::Full(full) => full.curve().clone().into(), + Self::Partial(partial) => partial.curve(), + } + } + /// Access the front vertex pub fn front(&self) -> MaybePartial { match self { diff --git a/crates/fj-kernel/src/partial/mod.rs b/crates/fj-kernel/src/partial/mod.rs index 87ac53f2d..172c05cef 100644 --- a/crates/fj-kernel/src/partial/mod.rs +++ b/crates/fj-kernel/src/partial/mod.rs @@ -37,6 +37,7 @@ mod maybe_partial; mod objects; mod traits; +mod util; pub use self::{ maybe_partial::MaybePartial, diff --git a/crates/fj-kernel/src/partial/objects/curve.rs b/crates/fj-kernel/src/partial/objects/curve.rs index f55b97924..f8840e1a0 100644 --- a/crates/fj-kernel/src/partial/objects/curve.rs +++ b/crates/fj-kernel/src/partial/objects/curve.rs @@ -1,6 +1,6 @@ use crate::{ objects::{Curve, GlobalCurve, Objects, Surface}, - partial::MaybePartial, + partial::{util::merge_options, MaybePartial}, path::SurfacePath, storage::Handle, validate::ValidationError, @@ -59,6 +59,26 @@ impl PartialCurve { self } + /// Merge this partial object with another + pub fn merge_with(self, other: Self) -> Self { + // This is harder than it should be, as `global_form` uses the redundant + // `Option>` representation. There's some code relying + // on that though, so we have to live with it for now. + let global_form = match (self.global_form, other.global_form) { + (Some(a), Some(b)) => Some(a.merge_with(b)), + (Some(global_form), None) | (None, Some(global_form)) => { + Some(global_form) + } + (None, None) => None, + }; + + Self { + path: merge_options(self.path, other.path), + surface: merge_options(self.surface, other.surface), + global_form, + } + } + /// Build a full [`Curve`] from the partial curve pub fn build( self, @@ -102,6 +122,11 @@ impl From<&Curve> for PartialCurve { pub struct PartialGlobalCurve; impl PartialGlobalCurve { + /// Merge this partial object with another + pub fn merge_with(self, _: Self) -> Self { + Self + } + /// Build a full [`GlobalCurve`] from the partial global curve pub fn build( self, diff --git a/crates/fj-kernel/src/partial/objects/cycle.rs b/crates/fj-kernel/src/partial/objects/cycle.rs index fe2163112..7d2c146a3 100644 --- a/crates/fj-kernel/src/partial/objects/cycle.rs +++ b/crates/fj-kernel/src/partial/objects/cycle.rs @@ -1,6 +1,6 @@ use crate::{ objects::{Cycle, HalfEdge, Objects, Surface}, - partial::MaybePartial, + partial::{util::merge_options, MaybePartial}, storage::Handle, validate::ValidationError, }; @@ -10,46 +10,80 @@ use crate::{ /// See [`crate::partial`] for more information. #[derive(Clone, Debug, Default)] pub struct PartialCycle { - 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 { - self.surface = Some(surface); - } - self + /// Access the surface that the [`Cycle`]'s [`HalfEdge`]s are defined in + pub fn surface(&self) -> Option> { + self.half_edges + .first() + .and_then(|half_edge| half_edge.curve().surface()) } - /// Update the partial cycle with the given half-edges + /// Add the provided half-edges to the partial cycle + /// + /// This will merge all the surfaces of the added half-edges. All added + /// half-edges will end up with the same merged surface. + /// + /// # Panics + /// + /// Panics, if the surfaces can't be merged. pub fn with_half_edges( mut self, half_edges: impl IntoIterator>>, ) -> Self { - self.half_edges - .extend(half_edges.into_iter().map(Into::into)); + let half_edges = half_edges.into_iter().map(Into::into); + + let mut surface = self.surface(); + for half_edge in half_edges { + surface = merge_options(surface, half_edge.curve().surface()); + self.half_edges.push(half_edge); + } + + self.with_surface(surface) + } + + /// Update the partial cycle with the provided surface + /// + /// All [`HalfEdge`]s will be updated with this surface. + pub fn with_surface(mut self, surface: Option>) -> Self { + if let Some(surface) = surface { + for half_edge in &mut self.half_edges { + *half_edge = half_edge.clone().update_partial(|half_edge| { + half_edge.with_surface(Some(surface.clone())) + }); + } + } self } + /// Merge this partial object with another + pub fn merge_with(self, other: Self) -> Self { + let a_is_empty = self.half_edges.is_empty(); + let b_is_empty = other.half_edges.is_empty(); + let half_edges = match (a_is_empty, b_is_empty) { + (true, true) => { + panic!("Can't merge `PartialHalfEdge`, if both have half-edges") + } + (true, false) => self.half_edges, + (false, true) => other.half_edges, + (false, false) => self.half_edges, // doesn't matter which we use + }; + + Self { half_edges } + } + /// Build a full [`Cycle`] from the partial cycle pub fn build( mut self, objects: &Objects, ) -> Result, ValidationError> { - let surface = self.surface.expect("Need surface to build `Cycle`"); - let surface_for_edges = surface.clone(); let half_edges = { let last_vertex = self .half_edges @@ -65,11 +99,7 @@ impl PartialCycle { .map(|(half_edge, vertex, surface_vertex)| -> Result<_, ValidationError> { - let surface_vertex = surface_vertex - .update_partial(|surface_vertex| { - surface_vertex.with_surface(Some(surface.clone())) - }) - .into_full(objects)?; + let surface_vertex = surface_vertex.into_full(objects)?; *half_edge = half_edge.clone().update_partial(|half_edge| { @@ -98,9 +128,7 @@ impl PartialCycle { partial.with_surface_form(previous_vertex) }); - half_edge - .with_surface(Some(surface_for_edges.clone())) - .with_back_vertex(Some(back)) + half_edge.with_back_vertex(Some(back)) }) .into_full(objects)?; @@ -121,7 +149,6 @@ impl PartialCycle { 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/edge.rs b/crates/fj-kernel/src/partial/objects/edge.rs index 4265d158c..2aadf3bb4 100644 --- a/crates/fj-kernel/src/partial/objects/edge.rs +++ b/crates/fj-kernel/src/partial/objects/edge.rs @@ -6,7 +6,7 @@ use crate::{ Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects, Surface, Vertex, }, - partial::MaybePartial, + partial::{util::merge_arrays, MaybePartial}, storage::Handle, validate::ValidationError, }; @@ -16,18 +16,12 @@ use crate::{ /// See [`crate::partial`] for more information. #[derive(Clone, Debug, Default)] pub struct PartialHalfEdge { - 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() @@ -52,17 +46,24 @@ impl PartialHalfEdge { .unwrap_or_else(|| self.global_form.curve()) } - /// Access the vertices of the global form, if available - pub fn extract_global_vertices( - &self, - ) -> Option<[MaybePartial; 2]> { - self.global_form.vertices() - } - /// Update the partial half-edge with the given surface pub fn with_surface(mut self, surface: Option>) -> Self { if let Some(surface) = surface { - self.surface = Some(surface); + self.curve = self.curve.update_partial(|curve| { + curve.with_surface(Some(surface.clone())) + }); + + self.vertices = self.vertices.map(|vertex| { + vertex.update_partial(|vertex| { + let surface_form = vertex.surface_form().update_partial( + |surface_vertex| { + surface_vertex.with_surface(Some(surface.clone())) + }, + ); + + vertex.with_surface_form(Some(surface_form)) + }) + }); } self } @@ -78,7 +79,7 @@ impl PartialHalfEdge { self } - /// Update the partial half-edge with the given from vertex + /// Update the partial half-edge with the given back vertex pub fn with_back_vertex( mut self, vertex: Option>>, @@ -90,7 +91,7 @@ impl PartialHalfEdge { self } - /// Update the partial half-edge with the given from vertex + /// Update the partial half-edge with the given front vertex pub fn with_front_vertex( mut self, vertex: Option>>, @@ -125,16 +126,21 @@ impl PartialHalfEdge { self } + /// Merge this partial object with another + pub fn merge_with(self, other: Self) -> Self { + Self { + curve: self.curve.merge_with(other.curve), + vertices: merge_arrays(self.vertices, other.vertices), + global_form: self.global_form.merge_with(other.global_form), + } + } + /// Build a full [`HalfEdge`] from the partial half-edge pub fn build( self, objects: &Objects, ) -> Result, ValidationError> { - let surface = self.surface; - let curve = self - .curve - .update_partial(|curve| curve.with_surface(surface)) - .into_full(objects)?; + let curve = self.curve.into_full(objects)?; let vertices = self.vertices.try_map_ext(|vertex| { vertex .update_partial(|vertex| vertex.with_curve(Some(curve.clone()))) @@ -160,7 +166,6 @@ impl From<&HalfEdge> for PartialHalfEdge { half_edge.vertices().clone().map(Into::into); Self { - surface: Some(half_edge.curve().surface().clone()), curve: half_edge.curve().clone().into(), vertices: [back_vertex, front_vertex], global_form: half_edge.global_form().clone().into(), @@ -210,6 +215,23 @@ impl PartialGlobalEdge { self } + /// Merge this partial object with another + pub fn merge_with(self, other: Self) -> Self { + // This is harder than it needs to be, because `vertices` uses the + // redundant combination of `Option` and `MaybePartial`. There's some + // code relying on that, however, so we have to live with it for now. + let vertices = match (self.vertices, other.vertices) { + (Some(a), Some(b)) => Some(merge_arrays(a, b)), + (Some(vertices), None) | (None, Some(vertices)) => Some(vertices), + (None, None) => None, + }; + + Self { + curve: self.curve.merge_with(other.curve), + vertices, + } + } + /// Build a full [`GlobalEdge`] from the partial global edge pub fn build( self, diff --git a/crates/fj-kernel/src/partial/objects/mod.rs b/crates/fj-kernel/src/partial/objects/mod.rs index 4032519a7..751063c23 100644 --- a/crates/fj-kernel/src/partial/objects/mod.rs +++ b/crates/fj-kernel/src/partial/objects/mod.rs @@ -27,6 +27,10 @@ macro_rules! impl_traits { impl Partial for $partial { type Full = $full; + fn merge_with(self, other: Self) -> Self { + self.merge_with(other) + } + fn build(self, objects: &Objects) -> Result< Handle, diff --git a/crates/fj-kernel/src/partial/objects/vertex.rs b/crates/fj-kernel/src/partial/objects/vertex.rs index 778d9b068..b04a67b02 100644 --- a/crates/fj-kernel/src/partial/objects/vertex.rs +++ b/crates/fj-kernel/src/partial/objects/vertex.rs @@ -3,7 +3,7 @@ use fj_math::Point; use crate::{ builder::GlobalVertexBuilder, objects::{Curve, GlobalVertex, Objects, Surface, SurfaceVertex, Vertex}, - partial::MaybePartial, + partial::{util::merge_options, MaybePartial}, storage::Handle, validate::ValidationError, }; @@ -67,6 +67,15 @@ impl PartialVertex { self } + /// Merge this partial object with another + pub fn merge_with(self, other: Self) -> Self { + Self { + position: merge_options(self.position, other.position), + curve: self.curve.merge_with(other.curve), + surface_form: self.surface_form.merge_with(other.surface_form), + } + } + /// Build a full [`Vertex`] from the partial vertex /// /// # Panics @@ -170,6 +179,15 @@ impl PartialSurfaceVertex { self } + /// Merge this partial object with another + pub fn merge_with(self, other: Self) -> Self { + Self { + position: merge_options(self.position, other.position), + surface: merge_options(self.surface, other.surface), + global_form: self.global_form.merge_with(other.global_form), + } + } + /// Build a full [`SurfaceVertex`] from the partial surface vertex pub fn build( self, @@ -232,6 +250,13 @@ impl PartialGlobalVertex { self } + /// Merge this partial object with another + pub fn merge_with(self, other: Self) -> Self { + Self { + position: merge_options(self.position, other.position), + } + } + /// Build a full [`GlobalVertex`] from the partial global vertex pub fn build( self, diff --git a/crates/fj-kernel/src/partial/traits.rs b/crates/fj-kernel/src/partial/traits.rs index 6a15f8b48..e18bf1c4a 100644 --- a/crates/fj-kernel/src/partial/traits.rs +++ b/crates/fj-kernel/src/partial/traits.rs @@ -68,6 +68,9 @@ pub trait Partial: Default + for<'a> From<&'a Self::Full> { /// The type representing the full variant of this object type Full; + /// Merge another partial object of the same type into this one + fn merge_with(self, other: Self) -> Self; + /// Build a full object from this partial one /// /// Implementations of this method will typically try to infer any missing diff --git a/crates/fj-kernel/src/partial/util.rs b/crates/fj-kernel/src/partial/util.rs new file mode 100644 index 000000000..e0605ee1e --- /dev/null +++ b/crates/fj-kernel/src/partial/util.rs @@ -0,0 +1,26 @@ +use fj_interop::ext::ArrayExt; + +use super::{HasPartial, MaybePartial}; + +pub fn merge_options(a: Option, b: Option) -> Option +where + T: Eq, +{ + if a == b { + return a; + } + + // We know that `a != b`, or we wouldn't have made it here. + if a.is_some() && b.is_some() { + panic!("Can't merge `Option`s if both are defined"); + } + + a.xor(b) +} + +pub fn merge_arrays( + a: [MaybePartial; 2], + b: [MaybePartial; 2], +) -> [MaybePartial; 2] { + a.zip_ext(b).map(|(a, b)| a.merge_with(b)) +} diff --git a/crates/fj-kernel/src/validate/edge.rs b/crates/fj-kernel/src/validate/edge.rs index 3f2f2e3ad..ffc50c29f 100644 --- a/crates/fj-kernel/src/validate/edge.rs +++ b/crates/fj-kernel/src/validate/edge.rs @@ -207,8 +207,10 @@ mod tests { let objects = Objects::new(); let valid = HalfEdge::partial() - .with_surface(Some(objects.surfaces.xy_plane())) - .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points( + objects.surfaces.xy_plane(), + [[0., 0.], [1., 0.]], + ) .build(&objects)?; let invalid = { let mut vertices = valid.vertices().clone(); @@ -232,8 +234,10 @@ mod tests { let objects = Objects::new(); let valid = HalfEdge::partial() - .with_surface(Some(objects.surfaces.xy_plane())) - .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points( + objects.surfaces.xy_plane(), + [[0., 0.], [1., 0.]], + ) .build(&objects)?; let invalid = HalfEdge::new( valid.vertices().clone(), @@ -255,8 +259,10 @@ mod tests { let objects = Objects::new(); let valid = HalfEdge::partial() - .with_surface(Some(objects.surfaces.xy_plane())) - .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points( + objects.surfaces.xy_plane(), + [[0., 0.], [1., 0.]], + ) .build(&objects)?; let invalid = HalfEdge::new( valid.vertices().clone(), @@ -285,8 +291,10 @@ mod tests { let objects = Objects::new(); let valid = HalfEdge::partial() - .with_surface(Some(objects.surfaces.xy_plane())) - .update_as_line_segment_from_points([[0., 0.], [1., 0.]]) + .update_as_line_segment_from_points( + objects.surfaces.xy_plane(), + [[0., 0.], [1., 0.]], + ) .build(&objects)?; let invalid = HalfEdge::new( valid.vertices().clone().try_map_ext(|vertex| {