diff --git a/crates/fj-kernel/src/algorithms/approx/curve.rs b/crates/fj-kernel/src/algorithms/approx/curve.rs index 775d02d42..c62dfb4c4 100644 --- a/crates/fj-kernel/src/algorithms/approx/curve.rs +++ b/crates/fj-kernel/src/algorithms/approx/curve.rs @@ -11,7 +11,10 @@ use std::collections::BTreeMap; -use crate::objects::{Curve, GlobalCurve}; +use crate::{ + objects::{Curve, GlobalCurve}, + path::{GlobalPath, SurfacePath}, +}; use super::{path::RangeOnPath, Approx, ApproxPoint, Tolerance}; @@ -30,8 +33,7 @@ impl Approx for (&Curve, RangeOnPath) { let global_curve_approx = match cache.get(cache_key) { Some(approx) => approx, None => { - let approx = (curve.global_form(), range) - .approx_with_cache(tolerance, &mut ()); + let approx = approx_global_curve(curve, range, tolerance); cache.insert(cache_key, approx) } }; @@ -40,6 +42,7 @@ impl Approx for (&Curve, RangeOnPath) { global_curve_approx.points.into_iter().map(|point| { let point_surface = curve.path().point_from_path_coords(point.local_form); + ApproxPoint::new(point_surface, point.global_form) .with_source((*curve, point.local_form)) }), @@ -47,27 +50,79 @@ impl Approx for (&Curve, RangeOnPath) { } } -impl Approx for (&GlobalCurve, RangeOnPath) { - type Approximation = GlobalCurveApprox; - type Cache = (); +fn approx_global_curve( + curve: &Curve, + range: RangeOnPath, + tolerance: impl Into, +) -> GlobalCurveApprox { + // 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 (curve.path(), curve.surface().u()) { + (SurfacePath::Circle(_), GlobalPath::Circle(_)) => { + todo!( + "Approximating a circle on a curved surface not supported yet." + ) + } + (SurfacePath::Circle(_), GlobalPath::Line(_)) => { + (curve.path(), range) + .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. - fn approx_with_cache( - self, - tolerance: impl Into, - cache: &mut Self::Cache, - ) -> Self::Approximation { - let (curve, range) = self; + let point_global = curve + .surface() + .point_from_surface_coords(point_surface); + (point_curve, point_global) + }) + .collect() + } + (SurfacePath::Line(line), _) => { + let range_u = + RangeOnPath::from(range.boundary.map(|point_curve| { + [curve.path().point_from_path_coords(point_curve).u] + })); - let points = (curve.path(), range) - .approx_with_cache(tolerance, cache) - .into_iter() - .map(|(point_curve, point_global)| { - ApproxPoint::new(point_curve, point_global) - }) - .collect(); + let approx_u = (curve.surface().u(), range_u) + .approx_with_cache(tolerance, &mut ()); - GlobalCurveApprox { points } - } + let mut points = Vec::new(); + for (u, _) in approx_u { + let t = (u.t - line.origin().u) / line.direction().u; + let point_surface = curve.path().point_from_path_coords([t]); + let point_global = + curve.surface().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(); + GlobalCurveApprox { points } } /// An approximation of a [`Curve`] @@ -130,3 +185,90 @@ pub struct GlobalCurveApprox { /// The points that approximate the curve pub points: Vec>, } + +#[cfg(test)] +mod tests { + use std::f64::consts::TAU; + + use pretty_assertions::assert_eq; + + use crate::{ + algorithms::approx::{path::RangeOnPath, Approx, ApproxPoint}, + objects::{Curve, Surface}, + path::GlobalPath, + }; + + use super::CurveApprox; + + #[test] + fn approx_line_on_flat_surface() { + let surface = Surface::new(GlobalPath::x_axis(), [0., 0., 1.]); + let curve = + Curve::build(surface).line_from_points([[1., 1.], [2., 1.]]); + let range = RangeOnPath::from([[0.], [1.]]); + + let approx = (&curve, range).approx(1.); + + assert_eq!(approx, CurveApprox::empty()) + } + + #[test] + fn approx_line_on_curved_surface_but_not_along_curve() { + let surface = + Surface::new(GlobalPath::circle_from_radius(1.), [0., 0., 1.]); + let curve = + Curve::build(surface).line_from_points([[1., 1.], [1., 2.]]); + let range = RangeOnPath::from([[0.], [1.]]); + + let approx = (&curve, range).approx(1.); + + assert_eq!(approx, CurveApprox::empty()); + } + + #[test] + fn approx_line_on_curved_surface_along_curve() { + let path = GlobalPath::circle_from_radius(1.); + let surface = Surface::new(path, [0., 0., 1.]); + let curve = + Curve::build(surface).line_from_points([[0., 1.], [1., 1.]]); + + let range = RangeOnPath::from([[0.], [TAU]]); + let tolerance = 1.; + + let approx = (&curve, range).approx(tolerance); + + let expected_approx = (path, range) + .approx(tolerance) + .into_iter() + .map(|(point_local, _)| { + let point_surface = + curve.path().point_from_path_coords(point_local); + let point_global = + surface.point_from_surface_coords(point_surface); + ApproxPoint::new(point_surface, point_global) + }) + .collect::>(); + assert_eq!(approx.points, expected_approx); + } + + #[test] + fn approx_circle_on_flat_surface() { + let surface = Surface::new(GlobalPath::x_axis(), [0., 0., 1.]); + let curve = Curve::build(surface).circle_from_radius(1.); + + let range = RangeOnPath::from([[0.], [TAU]]); + let tolerance = 1.; + let approx = (&curve, range).approx(tolerance); + + let expected_approx = (curve.path(), range) + .approx(tolerance) + .into_iter() + .map(|(_, point_surface)| { + let point_global = + curve.surface().point_from_surface_coords(point_surface); + ApproxPoint::new(point_surface, point_global) + }) + .collect::>(); + assert_eq!(approx.points, expected_approx); + } +} diff --git a/crates/fj-kernel/src/algorithms/approx/path.rs b/crates/fj-kernel/src/algorithms/approx/path.rs index ef704642a..1ce214b79 100644 --- a/crates/fj-kernel/src/algorithms/approx/path.rs +++ b/crates/fj-kernel/src/algorithms/approx/path.rs @@ -32,10 +32,30 @@ use std::iter; use fj_math::{Circle, Point, Scalar, Sign}; -use crate::path::GlobalPath; +use crate::path::{GlobalPath, SurfacePath}; use super::{Approx, Tolerance}; +impl Approx for (SurfacePath, RangeOnPath) { + type Approximation = Vec<(Point<1>, Point<2>)>; + type Cache = (); + + fn approx_with_cache( + self, + tolerance: impl Into, + (): &mut Self::Cache, + ) -> Self::Approximation { + let (path, range) = self; + + match path { + SurfacePath::Circle(circle) => { + approx_circle(&circle, range, tolerance.into()) + } + SurfacePath::Line(_) => vec![], + } + } +} + impl Approx for (GlobalPath, RangeOnPath) { type Approximation = Vec<(Point<1>, Point<3>)>; type Cache = (); diff --git a/crates/fj-kernel/src/algorithms/triangulate/polygon.rs b/crates/fj-kernel/src/algorithms/triangulate/polygon.rs index 21e057c69..f5d77519a 100644 --- a/crates/fj-kernel/src/algorithms/triangulate/polygon.rs +++ b/crates/fj-kernel/src/algorithms/triangulate/polygon.rs @@ -1,4 +1,4 @@ -use fj_math::{Point, PolyChain, Segment}; +use fj_math::{Point, PolyChain, Segment, Triangle}; use crate::algorithms::intersect::{ ray_segment::RaySegmentIntersection, HorizontalRayToTheRight, Intersect, @@ -42,11 +42,8 @@ impl Polygon { self } - pub fn contains_triangle( - &self, - triangle: [impl Into>; 3], - ) -> bool { - let [a, b, c] = triangle.map(Into::into); + pub fn contains_triangle(&self, triangle: impl Into>) -> bool { + let [a, b, c] = triangle.into().points(); let mut might_be_hole = true;