use crate::{Angle, Arc, Line, Point};
use std::{
iter,
iter::{Chain, Once},
};
pub trait Approximate {
type Iter: Iterator<Item = Point>;
fn approximate(&self, tolerance: f64) -> Self::Iter;
}
impl<'a, A: Approximate + ?Sized> Approximate for &'a A {
type Iter = A::Iter;
fn approximate(&self, tolerance: f64) -> Self::Iter {
(*self).approximate(tolerance)
}
}
impl Approximate for Point {
type Iter = Once<Point>;
fn approximate(&self, _tolerance: f64) -> Self::Iter {
std::iter::once(*self)
}
}
impl Approximate for Line {
type Iter = Chain<Once<Point>, Once<Point>>;
fn approximate(&self, _tolerance: f64) -> Self::Iter {
iter::once(self.start).chain(iter::once(self.end))
}
}
impl Approximate for Arc {
type Iter = ApproximatedArc;
fn approximate(&self, tolerance: f64) -> Self::Iter {
let (steps, delta) = if tolerance <= 0.0 || self.radius() <= tolerance {
(1, self.sweep_angle())
} else {
let cos_theta_on_two = 1.0 - tolerance / self.radius();
let theta = cos_theta_on_two.acos() * 2.0;
let line_segment_count = self.sweep_angle().get() / theta;
let line_segment_count = f64::max(line_segment_count, 2.0);
let actual_step = self.sweep_angle() / line_segment_count;
(line_segment_count.ceil().abs() as usize, actual_step)
};
ApproximatedArc {
i: 0,
steps,
step_size: delta,
arc: *self,
}
}
}
#[derive(Debug, Clone)]
#[allow(missing_copy_implementations)] pub struct ApproximatedArc {
i: usize,
steps: usize,
step_size: Angle,
arc: Arc,
}
impl Iterator for ApproximatedArc {
type Item = Point;
fn next(&mut self) -> Option<Self::Item> {
if self.i > self.steps {
return None;
}
let angle = Angle::radians(self.i as f64 * self.step_size.radians);
let point = self.arc.point_at(angle);
self.i += 1;
Some(point)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn approximate_arc_with_points() {
let arc = Arc::from_centre_radius(
Point::zero(),
100.0,
Angle::zero(),
Angle::frac_pi_2(),
);
let quality = 10.0;
let pieces: Vec<_> = arc.approximate(quality).collect();
for &piece in &pieces {
let error = arc.radius() - (piece - arc.centre()).length();
assert!(error < quality);
}
assert_eq!(arc.start(), *pieces.first().unwrap());
assert_eq!(arc.end(), *pieces.last().unwrap());
}
}