diff --git a/crates/fj-kernel/src/algorithms/sweep/curve.rs b/crates/fj-kernel/src/algorithms/sweep/curve.rs index 6b6acdcea..0775ccc86 100644 --- a/crates/fj-kernel/src/algorithms/sweep/curve.rs +++ b/crates/fj-kernel/src/algorithms/sweep/curve.rs @@ -6,14 +6,15 @@ use crate::{ storage::Handle, }; -use super::Sweep; +use super::{Sweep, SweepCache}; impl Sweep for Handle { type Swept = Handle; - fn sweep( + fn sweep_with_cache( self, path: impl Into>, + _: &mut SweepCache, objects: &Objects, ) -> Self::Swept { match self.surface().u() { diff --git a/crates/fj-kernel/src/algorithms/sweep/edge.rs b/crates/fj-kernel/src/algorithms/sweep/edge.rs index ceac38a01..8170faa34 100644 --- a/crates/fj-kernel/src/algorithms/sweep/edge.rs +++ b/crates/fj-kernel/src/algorithms/sweep/edge.rs @@ -10,20 +10,22 @@ use crate::{ path::SurfacePath, }; -use super::Sweep; +use super::{Sweep, SweepCache}; impl Sweep for (HalfEdge, Color) { type Swept = Face; - fn sweep( + fn sweep_with_cache( self, path: impl Into>, + cache: &mut SweepCache, objects: &Objects, ) -> Self::Swept { let (edge, color) = self; let path = path.into(); - let surface = edge.curve().clone().sweep(path, objects); + let surface = + edge.curve().clone().sweep_with_cache(path, cache, objects); // We can't use the edge we're sweeping from as the bottom edge, as that // is not defined in the right surface. Let's create a new bottom edge, @@ -82,10 +84,9 @@ impl Sweep for (HalfEdge, Color) { HalfEdge::new(vertices, edge.global_form().clone()) }; - let side_edges = bottom_edge - .vertices() - .clone() - .map(|vertex| (vertex, surface.clone()).sweep(path, objects)); + let side_edges = bottom_edge.vertices().clone().map(|vertex| { + (vertex, surface.clone()).sweep_with_cache(path, cache, objects) + }); let top_edge = { let bottom_vertices = bottom_edge.vertices(); diff --git a/crates/fj-kernel/src/algorithms/sweep/face.rs b/crates/fj-kernel/src/algorithms/sweep/face.rs index 52c0e8c32..cccf61b62 100644 --- a/crates/fj-kernel/src/algorithms/sweep/face.rs +++ b/crates/fj-kernel/src/algorithms/sweep/face.rs @@ -6,14 +6,15 @@ use crate::{ path::GlobalPath, }; -use super::Sweep; +use super::{Sweep, SweepCache}; impl Sweep for Face { type Swept = Shell; - fn sweep( + fn sweep_with_cache( self, path: impl Into>, + cache: &mut SweepCache, objects: &Objects, ) -> Self::Swept { let path = path.into(); @@ -64,7 +65,8 @@ impl Sweep for Face { half_edge.clone() }; - let face = (half_edge, self.color()).sweep(path, objects); + let face = (half_edge, self.color()) + .sweep_with_cache(path, cache, objects); faces.push(face); } diff --git a/crates/fj-kernel/src/algorithms/sweep/mod.rs b/crates/fj-kernel/src/algorithms/sweep/mod.rs index 70b009d1d..d7ba6fc1a 100644 --- a/crates/fj-kernel/src/algorithms/sweep/mod.rs +++ b/crates/fj-kernel/src/algorithms/sweep/mod.rs @@ -6,12 +6,17 @@ mod face; mod sketch; mod vertex; +use std::collections::BTreeMap; + use fj_math::Vector; -use crate::objects::Objects; +use crate::{ + objects::{GlobalVertex, Objects}, + storage::{Handle, ObjectId}, +}; /// Sweep an object along a path to create another object -pub trait Sweep { +pub trait Sweep: Sized { /// The object that is created by sweeping the implementing object type Swept; @@ -20,5 +25,25 @@ pub trait Sweep { self, path: impl Into>, objects: &Objects, + ) -> Self::Swept { + let mut cache = SweepCache::default(); + self.sweep_with_cache(path, &mut cache, objects) + } + + /// Sweep the object along the given path, using the provided cache + fn sweep_with_cache( + self, + path: impl Into>, + cache: &mut SweepCache, + objects: &Objects, ) -> Self::Swept; } + +/// A cache used for sweeping +/// +/// See [`Sweep`]. +#[derive(Default)] +pub struct SweepCache { + /// Cache for global vertices + pub global_vertex: BTreeMap>, +} diff --git a/crates/fj-kernel/src/algorithms/sweep/sketch.rs b/crates/fj-kernel/src/algorithms/sweep/sketch.rs index ff124af18..8c293be55 100644 --- a/crates/fj-kernel/src/algorithms/sweep/sketch.rs +++ b/crates/fj-kernel/src/algorithms/sweep/sketch.rs @@ -2,21 +2,22 @@ use fj_math::Vector; use crate::objects::{Objects, Sketch, Solid}; -use super::Sweep; +use super::{Sweep, SweepCache}; impl Sweep for Sketch { type Swept = Solid; - fn sweep( + fn sweep_with_cache( self, path: impl Into>, + cache: &mut SweepCache, objects: &Objects, ) -> Self::Swept { let path = path.into(); let mut shells = Vec::new(); for face in self.into_faces() { - let shell = face.sweep(path, objects); + let shell = face.sweep_with_cache(path, cache, objects); shells.push(shell); } diff --git a/crates/fj-kernel/src/algorithms/sweep/vertex.rs b/crates/fj-kernel/src/algorithms/sweep/vertex.rs index 8d96aa216..24c35f3ca 100644 --- a/crates/fj-kernel/src/algorithms/sweep/vertex.rs +++ b/crates/fj-kernel/src/algorithms/sweep/vertex.rs @@ -9,14 +9,15 @@ use crate::{ storage::Handle, }; -use super::Sweep; +use super::{Sweep, SweepCache}; impl Sweep for (Vertex, Handle) { type Swept = HalfEdge; - fn sweep( + fn sweep_with_cache( self, path: impl Into>, + cache: &mut SweepCache, objects: &Objects, ) -> Self::Swept { let (vertex, surface) = self; @@ -57,8 +58,10 @@ impl Sweep for (Vertex, Handle) { // With that out of the way, let's start by creating the `GlobalEdge`, // as that is the most straight-forward part of this operations, and // we're going to need it soon anyway. - let (edge_global, vertices_global) = - vertex.global_form().clone().sweep(path, objects); + let (edge_global, vertices_global) = vertex + .global_form() + .clone() + .sweep_with_cache(path, cache, objects); // Next, let's compute the surface coordinates of the two vertices of // the output `Edge`, as we're going to need these for the rest of this @@ -120,16 +123,25 @@ impl Sweep for (Vertex, Handle) { impl Sweep for Handle { type Swept = (GlobalEdge, [Handle; 2]); - fn sweep( + fn sweep_with_cache( self, path: impl Into>, + cache: &mut SweepCache, objects: &Objects, ) -> Self::Swept { let curve = GlobalCurve::new(objects); let a = self.clone(); - let b = - GlobalVertex::from_position(self.position() + path.into(), objects); + let b = cache + .global_vertex + .entry(self.id()) + .or_insert_with(|| { + GlobalVertex::from_position( + self.position() + path.into(), + objects, + ) + }) + .clone(); let vertices = [a, b]; let global_edge = GlobalEdge::new(curve, vertices.clone()); diff --git a/crates/fj-kernel/src/objects/edge.rs b/crates/fj-kernel/src/objects/edge.rs index 8df8d823d..cea9abbc4 100644 --- a/crates/fj-kernel/src/objects/edge.rs +++ b/crates/fj-kernel/src/objects/edge.rs @@ -39,6 +39,10 @@ impl HalfEdge { let curve = a.curve(); + let (vertices_in_normalized_order, _) = VerticesInNormalizedOrder::new( + [&a, &b].map(|vertex| vertex.global_form().clone()), + ); + // Make sure `curve` and `vertices` match `global_form`. assert_eq!( curve.global_form().id(), @@ -47,10 +51,15 @@ impl HalfEdge { the half-edge's global form" ); assert_eq!( - &VerticesInNormalizedOrder::new( - [&a, &b].map(|vertex| vertex.global_form().clone()) - ), - global_form.vertices(), + vertices_in_normalized_order + .access_in_normalized_order() + .clone() + .map(|global_vertex| global_vertex.id()), + global_form + .vertices() + .access_in_normalized_order() + .clone() + .map(|global_vertex| global_vertex.id()), "The global forms of a half-edge's vertices must match the \ vertices of the half-edge's global form" ); @@ -130,7 +139,7 @@ impl GlobalEdge { vertices: [Handle; 2], ) -> Self { let curve = curve.into(); - let vertices = VerticesInNormalizedOrder::new(vertices); + let (vertices, _) = VerticesInNormalizedOrder::new(vertices); Self { curve, vertices } } @@ -166,10 +175,14 @@ pub struct VerticesInNormalizedOrder([Handle; 2]); impl VerticesInNormalizedOrder { /// Construct a new instance of `VerticesInNormalizedOrder` /// - /// The provided vertices can be in any order. - pub fn new([a, b]: [Handle; 2]) -> Self { - let vertices = if a < b { [a, b] } else { [b, a] }; - Self(vertices) + /// The provided vertices can be in any order. The returned `bool` value + /// indicates whether the normalization changed the order of the vertices. + pub fn new([a, b]: [Handle; 2]) -> (Self, bool) { + if a < b { + (Self([a, b]), false) + } else { + (Self([b, a]), true) + } } /// Access the vertices diff --git a/crates/fj-kernel/src/partial/maybe_partial.rs b/crates/fj-kernel/src/partial/maybe_partial.rs index dc8edd741..30716d789 100644 --- a/crates/fj-kernel/src/partial/maybe_partial.rs +++ b/crates/fj-kernel/src/partial/maybe_partial.rs @@ -2,8 +2,8 @@ use fj_math::Point; use crate::{ objects::{ - Curve, GlobalCurve, GlobalEdge, HalfEdge, Objects, Surface, - SurfaceVertex, Vertex, + Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects, + Surface, SurfaceVertex, Vertex, }, storage::Handle, }; @@ -102,6 +102,16 @@ impl MaybePartial { } } } + + /// Access the vertices + pub fn vertices(&self) -> Option<&[Handle; 2]> { + match self { + Self::Full(full) => { + Some(full.vertices().access_in_normalized_order()) + } + Self::Partial(partial) => partial.vertices.as_ref(), + } + } } impl MaybePartial { diff --git a/crates/fj-kernel/src/partial/objects/edge.rs b/crates/fj-kernel/src/partial/objects/edge.rs index b5c2793e0..072c713ed 100644 --- a/crates/fj-kernel/src/partial/objects/edge.rs +++ b/crates/fj-kernel/src/partial/objects/edge.rs @@ -3,7 +3,7 @@ use fj_math::{Point, Scalar}; use crate::{ objects::{ Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects, - Surface, SurfaceVertex, Vertex, + Surface, SurfaceVertex, Vertex, VerticesInNormalizedOrder, }, partial::{HasPartial, MaybePartial}, storage::{Handle, HandleWrapper}, @@ -36,11 +36,16 @@ impl PartialHalfEdge { pub fn extract_global_curve(&self) -> Option> { let global_curve_from_curve = || self.curve.as_ref()?.global_form(); let global_curve_from_global_form = - || Some(self.global_form.as_ref()?.curve()?.clone()); + || self.global_form.as_ref()?.curve().cloned(); global_curve_from_curve().or_else(global_curve_from_global_form) } + /// Access the vertices of the global form, if available + pub fn extract_global_vertices(&self) -> Option<[Handle; 2]> { + self.global_form.as_ref()?.vertices().cloned() + } + /// Update the partial half-edge with the given surface pub fn with_surface(mut self, surface: Option>) -> Self { if let Some(surface) = surface { @@ -124,28 +129,33 @@ impl PartialHalfEdge { .with_surface(self.surface.clone()) .as_circle_from_radius(radius); - let [back, front] = { - let [a_curve, b_curve] = - [Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord])); + let path = curve.path.expect("Expected path that was just created"); - let global_form = Handle::::partial() - .from_curve_and_position(curve.clone(), a_curve); + let [a_curve, b_curve] = + [Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord])); - let path = curve.path.expect("Expected path that was just created"); - let surface_form = Handle::::partial() - .with_position(Some(path.point_from_path_coords(a_curve))) - .with_surface(self.surface.clone()) - .with_global_form(Some(global_form)) - .build(objects); - - [a_curve, b_curve].map(|point_curve| { - Vertex::partial() - .with_position(Some(point_curve)) - .with_curve(Some(curve.clone())) - .with_surface_form(Some(surface_form.clone())) + let global_vertex = self + .extract_global_vertices() + .map(|[global_form, _]| MaybePartial::from(global_form)) + .unwrap_or_else(|| { + Handle::::partial() + .from_curve_and_position(curve.clone(), a_curve) .into() - }) - }; + }); + + let surface_vertex = Handle::::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 = Some(curve.into()); self.vertices = [Some(back), Some(front)]; @@ -163,6 +173,7 @@ impl PartialHalfEdge { let surface_form = Handle::::partial() .with_surface(surface.clone()) .with_position(Some(point)); + Vertex::partial().with_surface_form(Some(surface_form)) }); @@ -198,13 +209,55 @@ impl PartialHalfEdge { .with_surface(Some(surface)) .as_line_from_points(points); - let [back, front] = [(from, 0.), (to, 1.)].map(|(vertex, position)| { - vertex.update_partial(|vertex| { - vertex - .with_position(Some([position])) - .with_curve(Some(curve.clone())) + 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).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]) + }; + + // Can be cleaned up, once `zip` is stable: + // https://doc.rust-lang.org/std/primitive.array.html#method.zip + let [a, b] = vertices; + let [a_global, b_global] = global_forms; + [(a, a_global), (b, b_global)].map(|(vertex, global_form)| { + vertex.update_partial(|partial| { + partial.with_global_form(global_form) + }) }) - }); + }; self.curve = Some(curve.into()); self.vertices = [Some(back), Some(front)];