use std::collections::BTreeMap;
use crate::{
objects::{Curve, GlobalCurve},
path::{GlobalPath, SurfacePath},
stores::{Handle, ObjectId},
};
use super::{path::RangeOnPath, Approx, ApproxPoint, Tolerance};
impl Approx for (&Handle<Curve>, RangeOnPath) {
type Approximation = CurveApprox;
type Cache = CurveCache;
fn approx_with_cache(
self,
tolerance: impl Into<Tolerance>,
cache: &mut Self::Cache,
) -> Self::Approximation {
let (curve, range) = self;
let global_curve = curve.global_form().clone();
let global_curve_approx = match cache.get(global_curve.clone(), range) {
Some(approx) => approx,
None => {
let approx = approx_global_curve(curve, range, tolerance);
cache.insert(global_curve, range, approx)
}
};
CurveApprox::empty().with_points(
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.clone(), point.local_form))
}),
)
}
}
fn approx_global_curve(
curve: &Curve,
range: RangeOnPath,
tolerance: impl Into<Tolerance>,
) -> GlobalCurveApprox {
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)| {
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 approx_u = (curve.surface().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 = 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 }
}
#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct CurveApprox {
pub points: Vec<ApproxPoint<2>>,
}
impl CurveApprox {
pub fn empty() -> Self {
Self { points: Vec::new() }
}
pub fn with_points(
mut self,
points: impl IntoIterator<Item = ApproxPoint<2>>,
) -> Self {
self.points.extend(points);
self
}
}
#[derive(Default)]
pub struct CurveCache {
inner: BTreeMap<(ObjectId, RangeOnPath), GlobalCurveApprox>,
}
impl CurveCache {
pub fn new() -> Self {
Self::default()
}
pub fn insert(
&mut self,
handle: Handle<GlobalCurve>,
range: RangeOnPath,
approx: GlobalCurveApprox,
) -> GlobalCurveApprox {
self.inner.insert((handle.id(), range), approx.clone());
approx
}
pub fn get(
&self,
handle: Handle<GlobalCurve>,
range: RangeOnPath,
) -> Option<GlobalCurveApprox> {
self.inner.get(&(handle.id(), range)).cloned()
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct GlobalCurveApprox {
pub points: Vec<ApproxPoint<1>>,
}
#[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},
partial::HasPartial,
path::GlobalPath,
stores::{Handle, Stores},
};
use super::CurveApprox;
#[test]
fn approx_line_on_flat_surface() {
let stores = Stores::new();
let surface = stores
.surfaces
.insert(Surface::new(GlobalPath::x_axis(), [0., 0., 1.]));
let curve = Handle::<Curve>::partial()
.with_surface(Some(surface))
.as_line_from_points([[1., 1.], [2., 1.]])
.build(&stores);
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 stores = Stores::new();
let surface = stores.surfaces.insert(Surface::new(
GlobalPath::circle_from_radius(1.),
[0., 0., 1.],
));
let curve = Handle::<Curve>::partial()
.with_surface(Some(surface))
.as_line_from_points([[1., 1.], [1., 2.]])
.build(&stores);
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 stores = Stores::new();
let path = GlobalPath::circle_from_radius(1.);
let surface = stores.surfaces.insert(Surface::new(path, [0., 0., 1.]));
let curve = Handle::<Curve>::partial()
.with_surface(Some(surface.clone()))
.as_line_from_points([[0., 1.], [1., 1.]])
.build(&stores);
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::<Vec<_>>();
assert_eq!(approx.points, expected_approx);
}
#[test]
fn approx_circle_on_flat_surface() {
let stores = Stores::new();
let surface = stores
.surfaces
.insert(Surface::new(GlobalPath::x_axis(), [0., 0., 1.]));
let curve = Handle::<Curve>::partial()
.with_surface(Some(surface))
.as_circle_from_radius(1.)
.build(&stores);
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::<Vec<_>>();
assert_eq!(approx.points, expected_approx);
}
}