use std::collections::BTreeMap;
use fj_math::Point;
use crate::{
geometry::curve::{Curve, GlobalPath},
objects::{GlobalEdge, HalfEdge, Surface, Vertex},
storage::{Handle, ObjectId},
};
use super::{path::RangeOnPath, Approx, ApproxPoint, Tolerance};
impl Approx for (&HalfEdge, &Surface) {
type Approximation = HalfEdgeApprox;
type Cache = EdgeCache;
fn approx_with_cache(
self,
tolerance: impl Into<Tolerance>,
cache: &mut Self::Cache,
) -> Self::Approximation {
let (half_edge, surface) = self;
let boundary = half_edge.boundary();
let range = RangeOnPath { boundary };
let position_surface = half_edge.start_position();
let position_global = match cache.get_position(half_edge.start_vertex())
{
Some(position) => position,
None => {
let position_global = surface
.geometry()
.point_from_surface_coords(position_surface);
cache.insert_position(half_edge.start_vertex(), position_global)
}
};
let first = ApproxPoint::new(position_surface, position_global);
let points = {
let approx =
match cache.get_edge(half_edge.global_form().clone(), range) {
Some(approx) => approx,
None => {
let approx = approx_edge(
&half_edge.curve(),
surface,
range,
tolerance,
);
cache.insert_edge(
half_edge.global_form().clone(),
range,
approx,
)
}
};
approx
.points
.into_iter()
.map(|point| {
let point_surface = half_edge
.curve()
.point_from_path_coords(point.local_form);
ApproxPoint::new(point_surface, point.global_form)
})
.collect()
};
HalfEdgeApprox { first, points }
}
}
#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct HalfEdgeApprox {
pub first: ApproxPoint<2>,
pub points: Vec<ApproxPoint<2>>,
}
impl HalfEdgeApprox {
pub fn points(&self) -> Vec<ApproxPoint<2>> {
let mut points = Vec::new();
points.push(self.first.clone());
points.extend(self.points.iter().cloned());
points
}
}
fn approx_edge(
curve: &Curve,
surface: &Surface,
range: RangeOnPath,
tolerance: impl Into<Tolerance>,
) -> GlobalEdgeApprox {
let points = match (curve, surface.geometry().u) {
(Curve::Circle(_), GlobalPath::Circle(_)) => {
todo!(
"Approximating a circle on a curved surface not supported yet."
)
}
(Curve::Circle(_), GlobalPath::Line(_)) => {
(curve, range)
.approx_with_cache(tolerance, &mut ())
.into_iter()
.map(|(point_curve, point_surface)| {
let point_global = surface
.geometry()
.point_from_surface_coords(point_surface);
(point_curve, point_global)
})
.collect()
}
(Curve::Line(line), _) => {
let range_u =
RangeOnPath::from(range.boundary.map(|point_curve| {
[curve.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 = curve.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();
GlobalEdgeApprox { points }
}
#[derive(Default)]
pub struct EdgeCache {
edge_approx: BTreeMap<(ObjectId, RangeOnPath), GlobalEdgeApprox>,
vertex_approx: BTreeMap<ObjectId, Point<3>>,
}
impl EdgeCache {
pub fn new() -> Self {
Self::default()
}
pub fn get_edge(
&self,
handle: Handle<GlobalEdge>,
range: RangeOnPath,
) -> Option<GlobalEdgeApprox> {
if let Some(approx) = self.edge_approx.get(&(handle.id(), range)) {
return Some(approx.clone());
}
if let Some(approx) =
self.edge_approx.get(&(handle.id(), range.reverse()))
{
return Some(approx.clone().reverse());
}
None
}
pub fn insert_edge(
&mut self,
handle: Handle<GlobalEdge>,
range: RangeOnPath,
approx: GlobalEdgeApprox,
) -> GlobalEdgeApprox {
self.edge_approx
.insert((handle.id(), range), approx.clone())
.unwrap_or(approx)
}
fn get_position(&self, handle: &Handle<Vertex>) -> Option<Point<3>> {
self.vertex_approx.get(&handle.id()).cloned()
}
fn insert_position(
&mut self,
handle: &Handle<Vertex>,
position: Point<3>,
) -> Point<3> {
self.vertex_approx
.insert(handle.id(), position)
.unwrap_or(position)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct GlobalEdgeApprox {
pub points: Vec<ApproxPoint<1>>,
}
impl GlobalEdgeApprox {
pub fn reverse(mut self) -> Self {
self.points.reverse();
self
}
}
#[cfg(test)]
mod tests {
use std::{f64::consts::TAU, ops::Deref};
use pretty_assertions::assert_eq;
use crate::{
algorithms::approx::{path::RangeOnPath, Approx, ApproxPoint},
geometry::{curve::GlobalPath, surface::SurfaceGeometry},
objects::{HalfEdge, Surface},
operations::{BuildHalfEdge, Insert},
services::Services,
};
#[test]
fn approx_line_on_flat_surface() {
let mut services = Services::new();
let surface = services.objects.surfaces.xz_plane();
let half_edge = HalfEdge::line_segment(
[[1., 1.], [2., 1.]],
None,
&mut services.objects,
);
let tolerance = 1.;
let approx = (&half_edge, surface.deref()).approx(tolerance);
assert_eq!(approx.points, Vec::new());
}
#[test]
fn approx_line_on_curved_surface_but_not_along_curve() {
let mut services = Services::new();
let surface = Surface::new(SurfaceGeometry {
u: GlobalPath::circle_from_radius(1.),
v: [0., 0., 1.].into(),
})
.insert(&mut services.objects);
let half_edge = HalfEdge::line_segment(
[[1., 1.], [2., 1.]],
None,
&mut services.objects,
);
let tolerance = 1.;
let approx = (&half_edge, surface.deref()).approx(tolerance);
assert_eq!(approx.points, Vec::new());
}
#[test]
fn approx_line_on_curved_surface_along_curve() {
let mut services = Services::new();
let path = GlobalPath::circle_from_radius(1.);
let range = RangeOnPath::from([[0.], [TAU]]);
let surface = Surface::new(SurfaceGeometry {
u: path,
v: [0., 0., 1.].into(),
})
.insert(&mut services.objects);
let half_edge = HalfEdge::line_segment(
[[0., 1.], [TAU, 1.]],
Some(range.boundary),
&mut services.objects,
);
let tolerance = 1.;
let approx = (&half_edge, surface.deref()).approx(tolerance);
let expected_approx = (path, range)
.approx(tolerance)
.into_iter()
.map(|(point_local, _)| {
let point_surface =
half_edge.curve().point_from_path_coords(point_local);
let point_global =
surface.geometry().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 mut services = Services::new();
let surface = services.objects.surfaces.xz_plane();
let half_edge = HalfEdge::circle(1., &mut services.objects);
let tolerance = 1.;
let approx = (&half_edge, surface.deref()).approx(tolerance);
let expected_approx =
(&half_edge.curve(), RangeOnPath::from([[0.], [TAU]]))
.approx(tolerance)
.into_iter()
.map(|(_, point_surface)| {
let point_global = surface
.geometry()
.point_from_surface_coords(point_surface);
ApproxPoint::new(point_surface, point_global)
})
.collect::<Vec<_>>();
assert_eq!(approx.points, expected_approx);
}
}