diff --git a/crates/fj-kernel/src/algorithms/approx/curve.rs b/crates/fj-kernel/src/algorithms/approx/curve.rs index 19c2465c4..970c3f788 100644 --- a/crates/fj-kernel/src/algorithms/approx/curve.rs +++ b/crates/fj-kernel/src/algorithms/approx/curve.rs @@ -18,16 +18,21 @@ use crate::{ path::GlobalPath, }; -use super::{Approx, ApproxPoint, Tolerance}; +use super::{Approx, ApproxCache, ApproxPoint, Tolerance}; impl Approx for (&Curve, RangeOnCurve) { type Approximation = CurveApprox; - fn approx(self, tolerance: Tolerance) -> Self::Approximation { + fn approx_with_cache( + self, + tolerance: Tolerance, + cache: &mut ApproxCache, + ) -> Self::Approximation { let (curve, range) = self; let points = (curve.global_form(), range) - .approx(tolerance) + .approx_with_cache(tolerance, cache) + .points .into_iter() .map(|point| { let point_surface = @@ -42,17 +47,27 @@ impl Approx for (&Curve, RangeOnCurve) { } impl Approx for (&GlobalCurve, RangeOnCurve) { - type Approximation = Vec>; + type Approximation = GlobalCurveApprox; - fn approx(self, tolerance: Tolerance) -> Self::Approximation { + fn approx_with_cache( + self, + tolerance: Tolerance, + cache: &mut ApproxCache, + ) -> Self::Approximation { let (curve, range) = self; - match curve.path() { + if let Some(approx) = cache.global_curve(curve) { + return approx; + } + + let points = match curve.path() { GlobalPath::Circle(circle) => { approx_circle(&circle, range, tolerance) } GlobalPath::Line(_) => vec![], - } + }; + + cache.insert_global_curve(curve, GlobalCurveApprox { points }) } } @@ -182,6 +197,13 @@ pub struct CurveApprox { pub points: Vec>, } +/// An approximation of a [`GlobalCurve`] +#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct GlobalCurveApprox { + /// The points that approximate the curve + pub points: Vec>, +} + #[cfg(test)] mod tests { use fj_math::Scalar; diff --git a/crates/fj-kernel/src/algorithms/approx/cycle.rs b/crates/fj-kernel/src/algorithms/approx/cycle.rs index 33556a794..57eff6520 100644 --- a/crates/fj-kernel/src/algorithms/approx/cycle.rs +++ b/crates/fj-kernel/src/algorithms/approx/cycle.rs @@ -6,15 +6,21 @@ use fj_math::Segment; use crate::objects::Cycle; -use super::{edge::HalfEdgeApprox, Approx, ApproxPoint, Tolerance}; +use super::{ + edge::HalfEdgeApprox, Approx, ApproxCache, ApproxPoint, Tolerance, +}; impl Approx for &Cycle { type Approximation = CycleApprox; - fn approx(self, tolerance: Tolerance) -> Self::Approximation { + fn approx_with_cache( + self, + tolerance: Tolerance, + cache: &mut ApproxCache, + ) -> Self::Approximation { let half_edges = self .half_edges() - .map(|half_edge| half_edge.approx(tolerance)) + .map(|half_edge| half_edge.approx_with_cache(tolerance, cache)) .collect(); CycleApprox { half_edges } } diff --git a/crates/fj-kernel/src/algorithms/approx/edge.rs b/crates/fj-kernel/src/algorithms/approx/edge.rs index fdfa73bcb..c30f65721 100644 --- a/crates/fj-kernel/src/algorithms/approx/edge.rs +++ b/crates/fj-kernel/src/algorithms/approx/edge.rs @@ -9,13 +9,17 @@ use crate::objects::HalfEdge; use super::{ curve::{CurveApprox, RangeOnCurve}, - Approx, ApproxPoint, + Approx, ApproxCache, ApproxPoint, Tolerance, }; impl Approx for &HalfEdge { type Approximation = HalfEdgeApprox; - fn approx(self, tolerance: super::Tolerance) -> Self::Approximation { + fn approx_with_cache( + self, + tolerance: Tolerance, + cache: &mut ApproxCache, + ) -> Self::Approximation { let &[a, b] = self.vertices(); let range = RangeOnCurve::new([a, b]); @@ -23,7 +27,8 @@ impl Approx for &HalfEdge { a.surface_form().position(), a.global_form().position(), ); - let curve_approx = (self.curve(), range).approx(tolerance); + let curve_approx = + (self.curve(), range).approx_with_cache(tolerance, cache); HalfEdgeApprox { first, diff --git a/crates/fj-kernel/src/algorithms/approx/face.rs b/crates/fj-kernel/src/algorithms/approx/face.rs index 1906a0613..911062d7e 100644 --- a/crates/fj-kernel/src/algorithms/approx/face.rs +++ b/crates/fj-kernel/src/algorithms/approx/face.rs @@ -11,15 +11,19 @@ use crate::{ objects::{Face, Faces, Handedness}, }; -use super::{cycle::CycleApprox, Approx, ApproxPoint, Tolerance}; +use super::{cycle::CycleApprox, Approx, ApproxCache, ApproxPoint, Tolerance}; impl Approx for &Faces { type Approximation = BTreeSet; - fn approx(self, tolerance: Tolerance) -> Self::Approximation { + fn approx_with_cache( + self, + tolerance: Tolerance, + cache: &mut ApproxCache, + ) -> Self::Approximation { let approx = self .into_iter() - .map(|face| face.approx(tolerance)) + .map(|face| face.approx_with_cache(tolerance, cache)) .collect(); let min_distance = ValidationConfig::default().distinct_min_distance; @@ -61,7 +65,11 @@ impl Approx for &Faces { impl Approx for &Face { type Approximation = FaceApprox; - fn approx(self, tolerance: Tolerance) -> Self::Approximation { + fn approx_with_cache( + self, + tolerance: Tolerance, + cache: &mut ApproxCache, + ) -> Self::Approximation { // Curved faces whose curvature is not fully defined by their edges // are not supported yet. For that reason, we can fully ignore `face`'s // `surface` field and just pass the edges to `Self::for_edges`. @@ -75,11 +83,11 @@ impl Approx for &Face { // would need to provide its own approximation, as the edges that bound // it have nothing to do with its curvature. - let exterior = self.exterior().approx(tolerance); + let exterior = self.exterior().approx_with_cache(tolerance, cache); let mut interiors = BTreeSet::new(); for cycle in self.interiors() { - let cycle = cycle.approx(tolerance); + let cycle = cycle.approx_with_cache(tolerance, cache); interiors.insert(cycle); } diff --git a/crates/fj-kernel/src/algorithms/approx/mod.rs b/crates/fj-kernel/src/algorithms/approx/mod.rs index 0284b2b51..49729fa33 100644 --- a/crates/fj-kernel/src/algorithms/approx/mod.rs +++ b/crates/fj-kernel/src/algorithms/approx/mod.rs @@ -12,6 +12,7 @@ pub mod tolerance; use std::{ any::Any, cmp::Ordering, + collections::BTreeMap, fmt::Debug, hash::{Hash, Hasher}, rc::Rc, @@ -19,12 +20,13 @@ use std::{ use fj_math::Point; -use crate::objects::Curve; +use crate::objects::{Curve, GlobalCurve}; +use self::curve::GlobalCurveApprox; pub use self::tolerance::{InvalidTolerance, Tolerance}; /// Approximate an object -pub trait Approx { +pub trait Approx: Sized { /// The approximation of the object type Approximation; @@ -32,7 +34,48 @@ pub trait Approx { /// /// `tolerance` defines how far the approximation is allowed to deviate from /// the actual object. - fn approx(self, tolerance: Tolerance) -> Self::Approximation; + fn approx(self, tolerance: Tolerance) -> Self::Approximation { + let mut cache = ApproxCache::new(); + self.approx_with_cache(tolerance, &mut cache) + } + + /// Approximate the object, using the provided cache + fn approx_with_cache( + self, + tolerance: Tolerance, + cache: &mut ApproxCache, + ) -> Self::Approximation; +} + +/// A cache for results of an approximation +#[derive(Default)] +pub struct ApproxCache { + global_curves: BTreeMap, +} + +impl ApproxCache { + /// Create an empty cache + pub fn new() -> Self { + Self::default() + } + + /// Insert the approximation of a [`GlobalCurve`] + pub fn insert_global_curve( + &mut self, + object: &GlobalCurve, + approx: GlobalCurveApprox, + ) -> GlobalCurveApprox { + self.global_curves.insert(*object, approx.clone()); + approx + } + + /// Access the approximation for the given [`GlobalCurve`], if available + pub fn global_curve( + &self, + object: &GlobalCurve, + ) -> Option { + self.global_curves.get(object).cloned() + } } /// A point from an approximation, with local and global forms diff --git a/crates/fj-kernel/src/algorithms/approx/shell.rs b/crates/fj-kernel/src/algorithms/approx/shell.rs index fcce75511..00f772b42 100644 --- a/crates/fj-kernel/src/algorithms/approx/shell.rs +++ b/crates/fj-kernel/src/algorithms/approx/shell.rs @@ -4,12 +4,16 @@ use std::collections::BTreeSet; use crate::objects::Shell; -use super::{face::FaceApprox, Approx, Tolerance}; +use super::{face::FaceApprox, Approx, ApproxCache, Tolerance}; impl Approx for &Shell { type Approximation = BTreeSet; - fn approx(self, tolerance: Tolerance) -> Self::Approximation { - self.faces().approx(tolerance) + fn approx_with_cache( + self, + tolerance: Tolerance, + cache: &mut ApproxCache, + ) -> Self::Approximation { + self.faces().approx_with_cache(tolerance, cache) } } diff --git a/crates/fj-kernel/src/algorithms/approx/sketch.rs b/crates/fj-kernel/src/algorithms/approx/sketch.rs index 43938b4b4..d3edb5177 100644 --- a/crates/fj-kernel/src/algorithms/approx/sketch.rs +++ b/crates/fj-kernel/src/algorithms/approx/sketch.rs @@ -4,12 +4,16 @@ use std::collections::BTreeSet; use crate::objects::Sketch; -use super::{face::FaceApprox, Approx, Tolerance}; +use super::{face::FaceApprox, Approx, ApproxCache, Tolerance}; impl Approx for &Sketch { type Approximation = BTreeSet; - fn approx(self, tolerance: Tolerance) -> Self::Approximation { - self.faces().approx(tolerance) + fn approx_with_cache( + self, + tolerance: Tolerance, + cache: &mut ApproxCache, + ) -> Self::Approximation { + self.faces().approx_with_cache(tolerance, cache) } } diff --git a/crates/fj-kernel/src/algorithms/approx/solid.rs b/crates/fj-kernel/src/algorithms/approx/solid.rs index 34126f82c..7c26396c0 100644 --- a/crates/fj-kernel/src/algorithms/approx/solid.rs +++ b/crates/fj-kernel/src/algorithms/approx/solid.rs @@ -4,14 +4,18 @@ use std::collections::BTreeSet; use crate::objects::Solid; -use super::{face::FaceApprox, Approx, Tolerance}; +use super::{face::FaceApprox, Approx, ApproxCache, Tolerance}; impl Approx for &Solid { type Approximation = BTreeSet; - fn approx(self, tolerance: Tolerance) -> Self::Approximation { + fn approx_with_cache( + self, + tolerance: Tolerance, + cache: &mut ApproxCache, + ) -> Self::Approximation { self.shells() - .flat_map(|shell| shell.approx(tolerance)) + .flat_map(|shell| shell.approx_with_cache(tolerance, cache)) .collect() } }