diff --git a/Cargo.lock b/Cargo.lock index 07a8c1ea9..56ea4b700 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1214,6 +1214,7 @@ dependencies = [ "robust-predicates", "spade", "thiserror", + "type-map", ] [[package]] diff --git a/crates/fj-kernel/Cargo.toml b/crates/fj-kernel/Cargo.toml index b92feac30..d4bc5dcde 100644 --- a/crates/fj-kernel/Cargo.toml +++ b/crates/fj-kernel/Cargo.toml @@ -20,6 +20,7 @@ pretty_assertions = "1.3.0" robust-predicates = "0.1.4" spade = "2.0.0" thiserror = "1.0.35" +type-map = "0.5.0" [dev-dependencies] anyhow = "1.0.66" diff --git a/crates/fj-kernel/src/algorithms/transform/curve.rs b/crates/fj-kernel/src/algorithms/transform/curve.rs index 368a8d00b..9beb760c0 100644 --- a/crates/fj-kernel/src/algorithms/transform/curve.rs +++ b/crates/fj-kernel/src/algorithms/transform/curve.rs @@ -1,40 +1,47 @@ use fj_math::Transform; use crate::{ - objects::Objects, - partial::{PartialCurve, PartialGlobalCurve}, + objects::{Curve, GlobalCurve, Objects}, services::Service, }; -use super::TransformObject; +use super::{TransformCache, TransformObject}; -impl TransformObject for PartialGlobalCurve { - fn transform(self, _: &Transform, _: &mut Service) -> Self { - // `GlobalCurve` doesn't contain any internal geometry. If it did, that - // would just be redundant with the geometry of other objects, and this - // other geometry is already being transformed by other implementations - // of this trait. - self - } -} - -impl TransformObject for PartialCurve { - fn transform( +impl TransformObject for Curve { + fn transform_with_cache( self, transform: &Transform, objects: &mut Service, + cache: &mut TransformCache, ) -> Self { + // Don't need to transform path, as that's defined in surface + // coordinates, and thus transforming `surface` takes care of it. + let path = self.path(); + let surface = self - .surface - .map(|surface| surface.transform(transform, objects)); - let global_form = self.global_form.transform(transform, objects); + .surface() + .clone() + .transform_with_cache(transform, objects, cache); + let global_form = self + .global_form() + .clone() + .transform_with_cache(transform, objects, cache); - // Don't need to transform `self.path`, as that's defined in surface - // coordinates, and thus transforming `surface` takes care of it. - PartialCurve { - path: self.path, - surface, - global_form, - } + Self::new(surface, path, global_form) + } +} + +impl TransformObject for GlobalCurve { + fn transform_with_cache( + self, + _: &Transform, + _: &mut Service, + _: &mut TransformCache, + ) -> Self { + // `GlobalCurve` doesn't contain any internal geometry. If it did, that + // would just be redundant with the geometry of other objects, and this + // other geometry is already being transformed by other implementations + // of this trait. + self } } diff --git a/crates/fj-kernel/src/algorithms/transform/cycle.rs b/crates/fj-kernel/src/algorithms/transform/cycle.rs index 53eba31c9..5fbc6eb32 100644 --- a/crates/fj-kernel/src/algorithms/transform/cycle.rs +++ b/crates/fj-kernel/src/algorithms/transform/cycle.rs @@ -1,19 +1,25 @@ use fj_math::Transform; -use crate::{objects::Objects, partial::PartialCycle, services::Service}; +use crate::{ + objects::{Cycle, Objects}, + services::Service, +}; -use super::TransformObject; +use super::{TransformCache, TransformObject}; -impl TransformObject for PartialCycle { - fn transform( +impl TransformObject for Cycle { + fn transform_with_cache( self, transform: &Transform, objects: &mut Service, + cache: &mut TransformCache, ) -> Self { - let half_edges = self - .half_edges() - .map(|edge| edge.into_partial().transform(transform, objects)); + let half_edges = self.half_edges().map(|half_edge| { + half_edge + .clone() + .transform_with_cache(transform, objects, cache) + }); - Self::default().with_half_edges(half_edges) + Self::new(half_edges) } } diff --git a/crates/fj-kernel/src/algorithms/transform/edge.rs b/crates/fj-kernel/src/algorithms/transform/edge.rs index 3075fe87f..5c0f2b3c8 100644 --- a/crates/fj-kernel/src/algorithms/transform/edge.rs +++ b/crates/fj-kernel/src/algorithms/transform/edge.rs @@ -1,44 +1,47 @@ use fj_math::Transform; use crate::{ - objects::Objects, - partial::{PartialGlobalEdge, PartialHalfEdge}, + objects::{GlobalEdge, HalfEdge, Objects}, services::Service, }; -use super::TransformObject; +use super::{TransformCache, TransformObject}; -impl TransformObject for PartialHalfEdge { - fn transform( +impl TransformObject for HalfEdge { + fn transform_with_cache( self, transform: &Transform, objects: &mut Service, + cache: &mut TransformCache, ) -> Self { - let curve = self.curve.transform(transform, objects); - let vertices = self - .vertices - .map(|vertex| vertex.transform(transform, objects)); - let global_form = self.global_form.transform(transform, objects); + let vertices = self.vertices().clone().map(|vertex| { + vertex.transform_with_cache(transform, objects, cache) + }); + let global_form = self + .global_form() + .clone() + .transform_with_cache(transform, objects, cache); - Self { - curve, - vertices, - global_form, - } + Self::new(vertices, global_form) } } -impl TransformObject for PartialGlobalEdge { - fn transform( +impl TransformObject for GlobalEdge { + fn transform_with_cache( self, transform: &Transform, objects: &mut Service, + cache: &mut TransformCache, ) -> Self { - let curve = self.curve.transform(transform, objects); - let vertices = self - .vertices - .map(|vertex| vertex.transform(transform, objects)); + let curve = self + .curve() + .clone() + .transform_with_cache(transform, objects, cache); + let vertices = + self.vertices().access_in_normalized_order().map(|vertex| { + vertex.transform_with_cache(transform, objects, cache) + }); - Self { curve, vertices } + Self::new(curve, vertices) } } diff --git a/crates/fj-kernel/src/algorithms/transform/face.rs b/crates/fj-kernel/src/algorithms/transform/face.rs index 27f3e2c31..5d600f7c1 100644 --- a/crates/fj-kernel/src/algorithms/transform/face.rs +++ b/crates/fj-kernel/src/algorithms/transform/face.rs @@ -1,63 +1,46 @@ use fj_math::Transform; use crate::{ - insert::Insert, objects::{Face, FaceSet, Objects}, - partial::{HasPartial, PartialFace}, services::Service, }; -use super::TransformObject; +use super::{TransformCache, TransformObject}; -impl TransformObject for PartialFace { - fn transform( +impl TransformObject for Face { + fn transform_with_cache( self, transform: &Transform, objects: &mut Service, + cache: &mut TransformCache, ) -> Self { - let surface = self - .surface() - .map(|surface| surface.transform(transform, objects)); + // Color does not need to be transformed. + let color = self.color(); + let exterior = self .exterior() - .into_partial() - .transform(transform, objects) - .with_surface(surface.clone()); - let interiors = self.interiors().map(|cycle| { - cycle - .into_partial() - .transform(transform, objects) - .with_surface(surface.clone()) - .build(objects) - .insert(objects) + .clone() + .transform_with_cache(transform, objects, cache); + let interiors = self.interiors().cloned().map(|interior| { + interior.transform_with_cache(transform, objects, cache) }); - let color = self.color(); - - let mut face = Face::partial() - .with_exterior(exterior) - .with_interiors(interiors); - if let Some(surface) = surface { - face = face.with_surface(surface); - } - if let Some(color) = color { - face = face.with_color(color); - } - - face + Self::new(exterior, interiors, color) } } impl TransformObject for FaceSet { - fn transform( + fn transform_with_cache( self, transform: &Transform, objects: &mut Service, + cache: &mut TransformCache, ) -> Self { let mut faces = FaceSet::new(); faces.extend( - self.into_iter() - .map(|face| face.transform(transform, objects)), + self.into_iter().map(|face| { + face.transform_with_cache(transform, objects, cache) + }), ); faces } diff --git a/crates/fj-kernel/src/algorithms/transform/mod.rs b/crates/fj-kernel/src/algorithms/transform/mod.rs index 92aebfaa2..e980d5685 100644 --- a/crates/fj-kernel/src/algorithms/transform/mod.rs +++ b/crates/fj-kernel/src/algorithms/transform/mod.rs @@ -10,15 +10,16 @@ mod solid; mod surface; mod vertex; +use std::collections::BTreeMap; + use fj_math::{Transform, Vector}; +use type_map::TypeMap; use crate::{ insert::Insert, objects::Objects, - partial::{HasPartial, MaybePartial, Partial}, services::Service, - storage::Handle, - validate::{Validate, ValidationError}, + storage::{Handle, ObjectId}, }; /// Transform an object @@ -36,6 +37,17 @@ pub trait TransformObject: Sized { self, transform: &Transform, objects: &mut Service, + ) -> Self { + let mut cache = TransformCache::default(); + self.transform_with_cache(transform, objects, &mut cache) + } + + /// Transform the object using the provided cache + fn transform_with_cache( + self, + transform: &Transform, + objects: &mut Service, + cache: &mut TransformCache, ) -> Self; /// Translate the object @@ -63,42 +75,51 @@ pub trait TransformObject: Sized { impl TransformObject for Handle where - T: HasPartial + Insert, - T::Partial: TransformObject, - ValidationError: From<::Error>, + T: Clone + Insert + TransformObject + 'static, { - fn transform( + fn transform_with_cache( self, transform: &Transform, objects: &mut Service, + cache: &mut TransformCache, ) -> Self { - self.to_partial() - .transform(transform, objects) - .build(objects) - .insert(objects) + if let Some(object) = cache.get(&self) { + return object.clone(); + } + + let transformed = self + .clone_object() + .transform_with_cache(transform, objects, cache) + .insert(objects); + + cache.insert(self.clone(), transformed.clone()); + + transformed } } -impl TransformObject for MaybePartial -where - T: HasPartial, - Handle: TransformObject, - T::Partial: TransformObject, -{ - fn transform( - self, - transform: &Transform, - objects: &mut Service, - ) -> Self { - let transformed = match self { - Self::Full(full) => full.to_partial().transform(transform, objects), - Self::Partial(partial) => partial.transform(transform, objects), - }; - - // Transforming a `MaybePartial` *always* results in a partial object. - // This provides the most flexibility to the caller, who might want to - // use the transformed partial object for merging or whatever else, - // before building it themselves. - Self::Partial(transformed) +/// A cache for transformed objects +/// +/// See [`TransformObject`]. +#[derive(Default)] +pub struct TransformCache(TypeMap); + +impl TransformCache { + fn get(&mut self, key: &Handle) -> Option<&Handle> { + let map = self + .0 + .entry::>>() + .or_insert_with(BTreeMap::new); + + map.get(&key.id()) + } + + fn insert(&mut self, key: Handle, value: Handle) { + let map = self + .0 + .entry::>>() + .or_insert_with(BTreeMap::new); + + map.insert(key.id(), value); } } diff --git a/crates/fj-kernel/src/algorithms/transform/shell.rs b/crates/fj-kernel/src/algorithms/transform/shell.rs index 6e70008bc..22994b13d 100644 --- a/crates/fj-kernel/src/algorithms/transform/shell.rs +++ b/crates/fj-kernel/src/algorithms/transform/shell.rs @@ -3,22 +3,22 @@ use fj_math::Transform; use crate::{ objects::{Objects, Shell}, services::Service, - storage::Handle, }; -use super::TransformObject; +use super::{TransformCache, TransformObject}; -impl TransformObject for Handle { - fn transform( +impl TransformObject for Shell { + fn transform_with_cache( self, transform: &Transform, objects: &mut Service, + cache: &mut TransformCache, ) -> Self { - let faces = self - .faces() - .clone() - .into_iter() - .map(|face| face.transform(transform, objects)); - Shell::builder().with_faces(faces).build(objects) + let faces = + self.faces().clone().into_iter().map(|face| { + face.transform_with_cache(transform, objects, cache) + }); + + Shell::new(faces) } } diff --git a/crates/fj-kernel/src/algorithms/transform/sketch.rs b/crates/fj-kernel/src/algorithms/transform/sketch.rs index 0910f6d1c..7249dd6a1 100644 --- a/crates/fj-kernel/src/algorithms/transform/sketch.rs +++ b/crates/fj-kernel/src/algorithms/transform/sketch.rs @@ -3,22 +3,22 @@ use fj_math::Transform; use crate::{ objects::{Objects, Sketch}, services::Service, - storage::Handle, }; -use super::TransformObject; +use super::{TransformCache, TransformObject}; -impl TransformObject for Handle { - fn transform( +impl TransformObject for Sketch { + fn transform_with_cache( self, transform: &Transform, objects: &mut Service, + cache: &mut TransformCache, ) -> Self { - let faces = self - .faces() - .into_iter() - .cloned() - .map(|face| face.transform(transform, objects)); - Sketch::builder().with_faces(faces).build(objects) + let faces = + self.faces().into_iter().cloned().map(|face| { + face.transform_with_cache(transform, objects, cache) + }); + + Sketch::new(faces) } } diff --git a/crates/fj-kernel/src/algorithms/transform/solid.rs b/crates/fj-kernel/src/algorithms/transform/solid.rs index f44f62025..92b8ab869 100644 --- a/crates/fj-kernel/src/algorithms/transform/solid.rs +++ b/crates/fj-kernel/src/algorithms/transform/solid.rs @@ -3,21 +3,22 @@ use fj_math::Transform; use crate::{ objects::{Objects, Solid}, services::Service, - storage::Handle, }; -use super::TransformObject; +use super::{TransformCache, TransformObject}; -impl TransformObject for Handle { - fn transform( +impl TransformObject for Solid { + fn transform_with_cache( self, transform: &Transform, objects: &mut Service, + cache: &mut TransformCache, ) -> Self { - let faces = self + let shells = self .shells() .cloned() - .map(|shell| shell.transform(transform, objects)); - Solid::builder().with_shells(faces).build(objects) + .map(|shell| shell.transform_with_cache(transform, objects, cache)); + + Solid::new(shells) } } diff --git a/crates/fj-kernel/src/algorithms/transform/surface.rs b/crates/fj-kernel/src/algorithms/transform/surface.rs index ebf68bb55..369ab538f 100644 --- a/crates/fj-kernel/src/algorithms/transform/surface.rs +++ b/crates/fj-kernel/src/algorithms/transform/surface.rs @@ -1,25 +1,20 @@ use fj_math::Transform; use crate::{ - geometry::surface::SurfaceGeometry, objects::Objects, - partial::PartialSurface, services::Service, + objects::{Objects, Surface}, + services::Service, }; -use super::TransformObject; +use super::{TransformCache, TransformObject}; -impl TransformObject for PartialSurface { - fn transform( +impl TransformObject for Surface { + fn transform_with_cache( self, transform: &Transform, _: &mut Service, + _: &mut TransformCache, ) -> Self { - let geometry = self.geometry.map(|geometry| { - let u = geometry.u.transform(transform); - let v = transform.transform_vector(&geometry.v); - - SurfaceGeometry { u, v } - }); - - Self { geometry } + let geometry = self.geometry().transform(transform); + Self::new(geometry) } } diff --git a/crates/fj-kernel/src/algorithms/transform/vertex.rs b/crates/fj-kernel/src/algorithms/transform/vertex.rs index 7b1e716be..06ed43428 100644 --- a/crates/fj-kernel/src/algorithms/transform/vertex.rs +++ b/crates/fj-kernel/src/algorithms/transform/vertex.rs @@ -1,67 +1,68 @@ use fj_math::Transform; use crate::{ - objects::Objects, - partial::{PartialGlobalVertex, PartialSurfaceVertex, PartialVertex}, + objects::{GlobalVertex, Objects, SurfaceVertex, Vertex}, services::Service, }; -use super::TransformObject; +use super::{TransformCache, TransformObject}; -impl TransformObject for PartialVertex { - fn transform( +impl TransformObject for Vertex { + fn transform_with_cache( self, transform: &Transform, objects: &mut Service, + cache: &mut TransformCache, ) -> Self { - let curve = self.curve.transform(transform, objects); + // Don't need to transform position, as that is defined in curve + // coordinates and thus transforming the curve takes care of it. + let position = self.position(); + + let curve = self + .curve() + .clone() + .transform_with_cache(transform, objects, cache); let surface_form = self - .surface_form - .into_partial() - .transform(transform, objects); + .surface_form() + .clone() + .transform_with_cache(transform, objects, cache); - // Don't need to transform `self.position`, as that is in curve - // coordinates and thus transforming the curve takes care of it. - Self { - position: self.position, - curve, - surface_form: surface_form.into(), - } + Self::new(position, curve, surface_form) } } -impl TransformObject for PartialSurfaceVertex { - fn transform( +impl TransformObject for SurfaceVertex { + fn transform_with_cache( self, transform: &Transform, objects: &mut Service, + cache: &mut TransformCache, ) -> Self { + // Don't need to transform position, as that is defined in surface + // coordinates and thus transforming the surface takes care of it. + let position = self.position(); + let surface = self - .surface + .surface() + .clone() + .transform_with_cache(transform, objects, cache); + let global_form = self + .global_form() .clone() - .map(|surface| surface.transform(transform, objects)); - let global_form = self.global_form.transform(transform, objects); + .transform_with_cache(transform, objects, cache); - // Don't need to transform `self.position`, as that is in surface - // coordinates and thus transforming the surface takes care of it. - Self { - position: self.position, - surface, - global_form, - } + Self::new(position, surface, global_form) } } -impl TransformObject for PartialGlobalVertex { - fn transform( +impl TransformObject for GlobalVertex { + fn transform_with_cache( self, transform: &Transform, _: &mut Service, + _: &mut TransformCache, ) -> Self { - let position = self - .position - .map(|position| transform.transform_point(&position)); - - Self { position } + let position = transform.transform_point(&self.position()); + Self::from_position(position) } } diff --git a/crates/fj-kernel/src/geometry/surface.rs b/crates/fj-kernel/src/geometry/surface.rs index a17abaaa5..c99a4fc02 100644 --- a/crates/fj-kernel/src/geometry/surface.rs +++ b/crates/fj-kernel/src/geometry/surface.rs @@ -1,6 +1,6 @@ //! The geometry that defines a surface -use fj_math::{Line, Point, Vector}; +use fj_math::{Line, Point, Transform, Vector}; use super::path::GlobalPath; @@ -38,6 +38,14 @@ impl SurfaceGeometry { fn path_to_line(&self) -> Line<3> { Line::from_origin_and_direction(self.u.origin(), self.v) } + + /// Transform the surface geometry + #[must_use] + pub fn transform(self, transform: &Transform) -> Self { + let u = self.u.transform(transform); + let v = transform.transform_vector(&self.v); + Self { u, v } + } } #[cfg(test)]