use crate::{BBox, Cubic, Curve, CurveFlattenIter, Line, Point, Scalar, Transform, PI};
use std::fmt;
#[derive(Clone, Copy, PartialEq)]
pub struct EllipArc {
center: Point,
rx: Scalar,
ry: Scalar,
phi: Scalar,
eta: Scalar,
eta_delta: Scalar,
}
impl fmt::Debug for EllipArc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Arc center:{:?} radius:{:?} phi:{:.3?} eta:{:.3?} eta_delta:{:.3?}",
self.center,
Point([self.rx, self.ry]),
self.phi,
self.eta,
self.eta_delta
)
}
}
impl EllipArc {
pub fn new_param(
src: Point,
dst: Point,
rx: Scalar,
ry: Scalar,
x_axis_rot: Scalar,
large_flag: bool,
sweep_flag: bool,
) -> Option<Self> {
let rx = rx.abs();
let ry = ry.abs();
let phi = x_axis_rot * PI / 180.0;
let Point([x1, y1]) = Transform::new_rotate(-phi).apply(0.5 * (src - dst));
let s = (x1 / rx).powi(2) + (y1 / ry).powi(2);
let (rx, ry) = if s > 1.0 {
let s = s.sqrt();
(rx * s, ry * s)
} else {
(rx, ry)
};
let sq = ((rx * ry).powi(2) / ((rx * y1).powi(2) + (ry * x1).powi(2)) - 1.0)
.max(0.0)
.sqrt();
let sq = if large_flag == sweep_flag { -sq } else { sq };
let center = sq * Point([rx * y1 / ry, -ry * x1 / rx]);
let Point([cx, cy]) = center;
let center = Transform::new_rotate(phi).apply(center) + 0.5 * (dst + src);
let v0 = Point([1.0, 0.0]);
let v1 = Point([(x1 - cx) / rx, (y1 - cy) / ry]);
let v2 = Point([(-x1 - cx) / rx, (-y1 - cy) / ry]);
let eta = v0.angle_between(v1)?;
let eta_delta = v1.angle_between(v2)?.rem_euclid(2.0 * PI);
let eta_delta = if !sweep_flag && eta_delta > 0.0 {
eta_delta - 2.0 * PI
} else if sweep_flag && eta_delta < 0.0 {
eta_delta + 2.0 * PI
} else {
eta_delta
};
Some(Self {
center,
rx,
ry,
phi,
eta,
eta_delta,
})
}
pub fn at(&self, t: Scalar) -> Point {
let (angle_sin, angle_cos) = (self.eta + t * self.eta_delta).sin_cos();
let point = Point([self.rx * angle_cos, self.ry * angle_sin]);
Transform::new_rotate(self.phi).apply(point) + self.center
}
pub fn start(&self) -> Point {
self.at(0.0)
}
pub fn end(&self) -> Point {
self.at(1.0)
}
pub fn bbox(&self, init: Option<BBox>) -> BBox {
EllipArcCubicIter::new(*self)
.fold(init, |bbox, cubic| Some(cubic.bbox(bbox)))
.expect("EllipArcCubicIter is empty")
}
pub fn reverse(&self) -> Self {
Self {
center: self.center,
rx: self.rx,
ry: self.ry,
phi: self.phi,
eta: self.eta + self.eta_delta,
eta_delta: -self.eta_delta,
}
}
pub fn to_cubics(&self) -> EllipArcCubicIter {
EllipArcCubicIter::new(*self)
}
pub fn flatten(&self, tr: Transform, flatness: Scalar) -> EllipArcFlattenIter {
EllipArcFlattenIter::new(*self, tr, flatness)
}
}
pub struct EllipArcCubicIter {
arc: EllipArc,
phi_tr: Transform,
segment_delta: Scalar,
segment_index: Scalar,
segment_count: Scalar,
}
impl EllipArcCubicIter {
fn new(arc: EllipArc) -> Self {
let phi_tr = Transform::new_rotate(arc.phi);
let segment_max_angle = PI / 2.0; let segment_count = (arc.eta_delta.abs() / segment_max_angle).ceil();
let segment_delta = arc.eta_delta / segment_count;
Self {
arc,
phi_tr,
segment_delta,
segment_index: 0.0,
segment_count: segment_count - 1.0,
}
}
fn at(&self, alpha: Scalar) -> (Point, Point) {
let (sin, cos) = alpha.sin_cos();
let at = self
.phi_tr
.apply(Point([self.arc.rx * cos, self.arc.ry * sin]))
+ self.arc.center;
let at_deriv = self
.phi_tr
.apply(Point([-self.arc.rx * sin, self.arc.ry * cos]));
(at, at_deriv)
}
}
impl Iterator for EllipArcCubicIter {
type Item = Cubic;
fn next(&mut self) -> Option<Self::Item> {
if self.segment_index > self.segment_count {
return None;
}
let eta_1 = self.arc.eta + self.segment_delta * self.segment_index;
let eta_2 = eta_1 + self.segment_delta;
self.segment_index += 1.0;
let sq = (4.0 + 3.0 * ((eta_2 - eta_1) / 2.0).tan().powi(2)).sqrt();
let alpha = (eta_2 - eta_1).sin() * (sq - 1.0) / 3.0;
let (p0, d0) = self.at(eta_1);
let (p3, d3) = self.at(eta_2);
let p1 = p0 + alpha * d0;
let p2 = p3 - alpha * d3;
Some(Cubic([p0, p1, p2, p3]))
}
}
pub struct EllipArcFlattenIter {
tr: Transform,
flatness: Scalar,
cubics: EllipArcCubicIter,
cubic: Option<CurveFlattenIter>,
}
impl EllipArcFlattenIter {
fn new(arc: EllipArc, tr: Transform, flatness: Scalar) -> Self {
Self {
tr,
flatness,
cubics: arc.to_cubics(),
cubic: None,
}
}
}
impl Iterator for EllipArcFlattenIter {
type Item = Line;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.cubic.as_mut().and_then(Iterator::next) {
line @ Some(_) => return line,
None => self.cubic = Some(self.cubics.next()?.flatten(self.tr, self.flatness)),
}
}
}
}