diff --git a/crates/fj-core/src/algorithms/approx/curve.rs b/crates/fj-core/src/algorithms/approx/curve.rs new file mode 100644 index 000000000..71d01d6ff --- /dev/null +++ b/crates/fj-core/src/algorithms/approx/curve.rs @@ -0,0 +1,171 @@ +//! Curve approximation + +use std::collections::BTreeMap; + +use fj_math::Point; + +use crate::{ + geometry::{CurveBoundary, GlobalPath, SurfacePath}, + objects::{Curve, Surface}, + storage::{Handle, HandleWrapper}, +}; + +use super::{Approx, ApproxPoint, Tolerance}; + +impl Approx + for ( + &Handle, + SurfacePath, + &Surface, + CurveBoundary>, + ) +{ + type Approximation = CurveApprox; + type Cache = CurveApproxCache; + + fn approx_with_cache( + self, + tolerance: impl Into, + cache: &mut Self::Cache, + ) -> Self::Approximation { + let (curve, surface_path, surface, boundary) = self; + + match cache.get(curve, boundary) { + Some(approx) => approx, + None => { + let approx = + approx_curve(&surface_path, surface, boundary, tolerance); + + cache.insert(curve.clone(), boundary, approx) + } + } + } +} + +fn approx_curve( + path: &SurfacePath, + surface: &Surface, + boundary: CurveBoundary>, + tolerance: impl Into, +) -> CurveApprox { + // There are different cases of varying complexity. Circles are the hard + // part here, as they need to be approximated, while lines don't need to be. + // + // This will probably all be unified eventually, as `SurfacePath` and + // `GlobalPath` grow APIs that are better suited to implementing this code + // in a more abstract way. + let points = match (path, surface.geometry().u) { + (SurfacePath::Circle(_), GlobalPath::Circle(_)) => { + todo!( + "Approximating a circle on a curved surface not supported yet." + ) + } + (SurfacePath::Circle(_), GlobalPath::Line(_)) => { + (path, boundary) + .approx_with_cache(tolerance, &mut ()) + .into_iter() + .map(|(point_curve, point_surface)| { + // We're throwing away `point_surface` here, which is a bit + // weird, as we're recomputing it later (outside of this + // function). + // + // It should be fine though: + // + // 1. We're throwing this version away, so there's no danger + // of inconsistency between this and the later version. + // 2. This version should have been computed using the same + // path and parameters and the later version will be, so + // they should be the same anyway. + // 3. Not all other cases handled in this function have a + // surface point available, so it needs to be computed + // later anyway, in the general case. + + let point_global = surface + .geometry() + .point_from_surface_coords(point_surface); + (point_curve, point_global) + }) + .collect() + } + (SurfacePath::Line(line), _) => { + let range_u = + CurveBoundary::from(boundary.inner.map(|point_curve| { + [path.point_from_path_coords(point_curve).u] + })); + + let approx_u = (surface.geometry().u, range_u) + .approx_with_cache(tolerance, &mut ()); + + let mut points = Vec::new(); + for (u, _) in approx_u { + let t = (u.t - line.origin().u) / line.direction().u; + let point_surface = path.point_from_path_coords([t]); + let point_global = + surface.geometry().point_from_surface_coords(point_surface); + points.push((u, point_global)); + } + + points + } + }; + + let points = points + .into_iter() + .map(|(point_curve, point_global)| { + ApproxPoint::new(point_curve, point_global) + }) + .collect(); + CurveApprox { points } +} + +/// Approximation of [`Curve`], within a specific boundary +#[derive(Clone)] +pub struct CurveApprox { + /// The points that approximate the curve within the boundary + pub points: Vec>, +} + +impl CurveApprox { + fn reverse(mut self) -> Self { + self.points.reverse(); + self + } +} + +/// Cache for curve approximations +#[derive(Default)] +pub struct CurveApproxCache { + inner: + BTreeMap<(HandleWrapper, CurveBoundary>), CurveApprox>, +} + +impl CurveApproxCache { + fn get( + &self, + handle: &Handle, + boundary: CurveBoundary>, + ) -> Option { + let handle = HandleWrapper::from(handle.clone()); + + if let Some(approx) = self.inner.get(&(handle.clone(), boundary)) { + return Some(approx.clone()); + } + if let Some(approx) = self.inner.get(&(handle, boundary.reverse())) { + return Some(approx.clone().reverse()); + } + + None + } + + fn insert( + &mut self, + handle: Handle, + boundary: CurveBoundary>, + approx: CurveApprox, + ) -> CurveApprox { + let handle = HandleWrapper::from(handle); + self.inner + .insert((handle, boundary), approx.clone()) + .unwrap_or(approx) + } +} diff --git a/crates/fj-core/src/algorithms/approx/cycle.rs b/crates/fj-core/src/algorithms/approx/cycle.rs index 163a523e1..091ff9cb8 100644 --- a/crates/fj-core/src/algorithms/approx/cycle.rs +++ b/crates/fj-core/src/algorithms/approx/cycle.rs @@ -9,13 +9,13 @@ use fj_math::Segment; use crate::objects::{Cycle, Surface}; use super::{ - edge::{EdgeApproxCache, HalfEdgeApprox}, + edge::{HalfEdgeApprox, HalfEdgeApproxCache}, Approx, ApproxPoint, Tolerance, }; impl Approx for (&Cycle, &Surface) { type Approximation = CycleApprox; - type Cache = EdgeApproxCache; + type Cache = HalfEdgeApproxCache; fn approx_with_cache( self, diff --git a/crates/fj-core/src/algorithms/approx/edge.rs b/crates/fj-core/src/algorithms/approx/edge.rs index 3246c735f..a36433637 100644 --- a/crates/fj-core/src/algorithms/approx/edge.rs +++ b/crates/fj-core/src/algorithms/approx/edge.rs @@ -1,25 +1,20 @@ //! Edge approximation //! //! The approximation of a curve is its first vertex, combined with the -//! approximation of its curve. The second vertex is left off, as edge +//! approximation of its curve. The second vertex is left out, as edge //! approximations are usually used to build cycle approximations, and this way, //! the caller doesn't have to deal with duplicate vertices. -use std::collections::BTreeMap; +use crate::objects::{HalfEdge, Surface}; -use fj_math::Point; - -use crate::{ - geometry::{CurveBoundary, GlobalPath, SurfacePath}, - objects::{Curve, HalfEdge, Surface, Vertex}, - storage::{Handle, HandleWrapper}, +use super::{ + curve::CurveApproxCache, vertex::VertexApproxCache, Approx, ApproxPoint, + Tolerance, }; -use super::{Approx, ApproxPoint, Tolerance}; - impl Approx for (&HalfEdge, &Surface) { type Approximation = HalfEdgeApprox; - type Cache = EdgeApproxCache; + type Cache = HalfEdgeApproxCache; fn approx_with_cache( self, @@ -30,47 +25,26 @@ impl Approx for (&HalfEdge, &Surface) { let tolerance = tolerance.into(); let start_position_surface = edge.start_position(); - let start_position = - match cache.get_start_position_approx(edge.start_vertex()) { - Some(position) => position, - None => { - let position_global = surface - .geometry() - .point_from_surface_coords(start_position_surface); - cache.insert_start_position_approx( - edge.start_vertex(), - position_global, - ) - } - }; + let start_position = match cache.start_position.get(edge.start_vertex()) + { + Some(position) => position, + None => { + let position_global = surface + .geometry() + .point_from_surface_coords(start_position_surface); + cache + .start_position + .insert(edge.start_vertex().clone(), position_global) + } + }; let first = ApproxPoint::new(start_position_surface, start_position); let rest = { - let cached = - cache.get_curve_approx(edge.curve().clone(), edge.boundary()); - - let approx = match cached { - Some(approx) => approx, - None => { - let approx = approx_curve( - &edge.path(), - surface, - edge.boundary(), - tolerance, - ); - - cache.insert_curve_approx( - edge.curve().clone(), - edge.boundary(), - approx.clone(), - ); - - approx - } - }; - - approx.into_iter().map(|point| { + let approx = (edge.curve(), edge.path(), surface, edge.boundary()) + .approx_with_cache(tolerance, &mut cache.curve); + + approx.points.into_iter().map(|point| { let point_surface = edge.path().point_from_path_coords(point.local_form); @@ -92,143 +66,11 @@ pub struct HalfEdgeApprox { pub points: Vec>, } -fn approx_curve( - path: &SurfacePath, - surface: &Surface, - boundary: CurveBoundary>, - tolerance: impl Into, -) -> Vec> { - // There are different cases of varying complexity. Circles are the hard - // part here, as they need to be approximated, while lines don't need to be. - // - // This will probably all be unified eventually, as `SurfacePath` and - // `GlobalPath` grow APIs that are better suited to implementing this code - // in a more abstract way. - let points = match (path, surface.geometry().u) { - (SurfacePath::Circle(_), GlobalPath::Circle(_)) => { - todo!( - "Approximating a circle on a curved surface not supported yet." - ) - } - (SurfacePath::Circle(_), GlobalPath::Line(_)) => { - (path, boundary) - .approx_with_cache(tolerance, &mut ()) - .into_iter() - .map(|(point_curve, point_surface)| { - // We're throwing away `point_surface` here, which is a bit - // weird, as we're recomputing it later (outside of this - // function). - // - // It should be fine though: - // - // 1. We're throwing this version away, so there's no danger - // of inconsistency between this and the later version. - // 2. This version should have been computed using the same - // path and parameters and the later version will be, so - // they should be the same anyway. - // 3. Not all other cases handled in this function have a - // surface point available, so it needs to be computed - // later anyway, in the general case. - - let point_global = surface - .geometry() - .point_from_surface_coords(point_surface); - (point_curve, point_global) - }) - .collect() - } - (SurfacePath::Line(line), _) => { - let range_u = - CurveBoundary::from(boundary.inner.map(|point_curve| { - [path.point_from_path_coords(point_curve).u] - })); - - let approx_u = (surface.geometry().u, range_u) - .approx_with_cache(tolerance, &mut ()); - - let mut points = Vec::new(); - for (u, _) in approx_u { - let t = (u.t - line.origin().u) / line.direction().u; - let point_surface = path.point_from_path_coords([t]); - let point_global = - surface.geometry().point_from_surface_coords(point_surface); - points.push((u, point_global)); - } - - points - } - }; - - points - .into_iter() - .map(|(point_curve, point_global)| { - ApproxPoint::new(point_curve, point_global) - }) - .collect() -} - -/// Cache for edge approximations +/// Cache for half-edge approximations #[derive(Default)] -pub struct EdgeApproxCache { - start_position_approx: BTreeMap, Point<3>>, - curve_approx: BTreeMap< - (HandleWrapper, CurveBoundary>), - Vec>, - >, -} - -impl EdgeApproxCache { - fn get_start_position_approx( - &self, - handle: &Handle, - ) -> Option> { - self.start_position_approx - .get(&handle.clone().into()) - .cloned() - } - - fn insert_start_position_approx( - &mut self, - handle: &Handle, - position: Point<3>, - ) -> Point<3> { - self.start_position_approx - .insert(handle.clone().into(), position) - .unwrap_or(position) - } - - fn get_curve_approx( - &self, - handle: Handle, - boundary: CurveBoundary>, - ) -> Option>> { - let curve = HandleWrapper::from(handle); - - if let Some(approx) = self.curve_approx.get(&(curve.clone(), boundary)) - { - return Some(approx.clone()); - } - if let Some(approx) = - self.curve_approx.get(&(curve, boundary.reverse())) - { - let mut approx = approx.clone(); - approx.reverse(); - - return Some(approx); - } - - None - } - - fn insert_curve_approx( - &mut self, - handle: Handle, - boundary: CurveBoundary>, - approx: Vec>, - ) { - let curve = HandleWrapper::from(handle); - self.curve_approx.insert((curve, boundary), approx); - } +pub struct HalfEdgeApproxCache { + start_position: VertexApproxCache, + curve: CurveApproxCache, } #[cfg(test)] diff --git a/crates/fj-core/src/algorithms/approx/face.rs b/crates/fj-core/src/algorithms/approx/face.rs index 9df592ee6..aedccf6a5 100644 --- a/crates/fj-core/src/algorithms/approx/face.rs +++ b/crates/fj-core/src/algorithms/approx/face.rs @@ -12,12 +12,13 @@ use crate::{ }; use super::{ - cycle::CycleApprox, edge::EdgeApproxCache, Approx, ApproxPoint, Tolerance, + cycle::CycleApprox, edge::HalfEdgeApproxCache, Approx, ApproxPoint, + Tolerance, }; impl Approx for &Handles { type Approximation = BTreeSet; - type Cache = EdgeApproxCache; + type Cache = HalfEdgeApproxCache; fn approx_with_cache( self, @@ -63,7 +64,7 @@ impl Approx for &Handles { impl Approx for &Face { type Approximation = FaceApprox; - type Cache = EdgeApproxCache; + type Cache = HalfEdgeApproxCache; fn approx_with_cache( self, diff --git a/crates/fj-core/src/algorithms/approx/mod.rs b/crates/fj-core/src/algorithms/approx/mod.rs index 54e195469..513763c59 100644 --- a/crates/fj-core/src/algorithms/approx/mod.rs +++ b/crates/fj-core/src/algorithms/approx/mod.rs @@ -1,5 +1,6 @@ //! Approximation of objects +pub mod curve; pub mod cycle; pub mod edge; pub mod face; @@ -8,6 +9,7 @@ pub mod shell; pub mod sketch; pub mod solid; pub mod tolerance; +pub mod vertex; use std::{ cmp::Ordering, diff --git a/crates/fj-core/src/algorithms/approx/shell.rs b/crates/fj-core/src/algorithms/approx/shell.rs index 1fbb21121..2a2fff755 100644 --- a/crates/fj-core/src/algorithms/approx/shell.rs +++ b/crates/fj-core/src/algorithms/approx/shell.rs @@ -4,11 +4,11 @@ use std::collections::BTreeSet; use crate::objects::Shell; -use super::{edge::EdgeApproxCache, face::FaceApprox, Approx, Tolerance}; +use super::{edge::HalfEdgeApproxCache, face::FaceApprox, Approx, Tolerance}; impl Approx for &Shell { type Approximation = BTreeSet; - type Cache = EdgeApproxCache; + type Cache = HalfEdgeApproxCache; fn approx_with_cache( self, diff --git a/crates/fj-core/src/algorithms/approx/sketch.rs b/crates/fj-core/src/algorithms/approx/sketch.rs index 2e935a9f6..1a49c0c95 100644 --- a/crates/fj-core/src/algorithms/approx/sketch.rs +++ b/crates/fj-core/src/algorithms/approx/sketch.rs @@ -4,11 +4,11 @@ use std::collections::BTreeSet; use crate::objects::Sketch; -use super::{edge::EdgeApproxCache, face::FaceApprox, Approx, Tolerance}; +use super::{edge::HalfEdgeApproxCache, face::FaceApprox, Approx, Tolerance}; impl Approx for &Sketch { type Approximation = BTreeSet; - type Cache = EdgeApproxCache; + type Cache = HalfEdgeApproxCache; fn approx_with_cache( self, diff --git a/crates/fj-core/src/algorithms/approx/solid.rs b/crates/fj-core/src/algorithms/approx/solid.rs index 97ad09fc1..c24d0ed3f 100644 --- a/crates/fj-core/src/algorithms/approx/solid.rs +++ b/crates/fj-core/src/algorithms/approx/solid.rs @@ -4,11 +4,11 @@ use std::collections::BTreeSet; use crate::objects::Solid; -use super::{edge::EdgeApproxCache, face::FaceApprox, Approx, Tolerance}; +use super::{edge::HalfEdgeApproxCache, face::FaceApprox, Approx, Tolerance}; impl Approx for &Solid { type Approximation = BTreeSet; - type Cache = EdgeApproxCache; + type Cache = HalfEdgeApproxCache; fn approx_with_cache( self, diff --git a/crates/fj-core/src/algorithms/approx/vertex.rs b/crates/fj-core/src/algorithms/approx/vertex.rs new file mode 100644 index 000000000..fbfde07c8 --- /dev/null +++ b/crates/fj-core/src/algorithms/approx/vertex.rs @@ -0,0 +1,34 @@ +//! Vertex approximation + +use std::collections::BTreeMap; + +use fj_math::Point; + +use crate::{ + objects::Vertex, + storage::{Handle, HandleWrapper}, +}; + +/// Cache for vertex approximations +#[derive(Default)] +pub struct VertexApproxCache { + inner: BTreeMap, Point<3>>, +} + +impl VertexApproxCache { + /// Get an approximated vertex from the cache + pub fn get(&self, handle: &Handle) -> Option> { + self.inner.get(&handle.clone().into()).cloned() + } + + /// Insert an approximated vertex into the cache + pub fn insert( + &mut self, + handle: Handle, + position: Point<3>, + ) -> Point<3> { + self.inner + .insert(handle.clone().into(), position) + .unwrap_or(position) + } +}