use super::command::Command;
use super::geometry::{Angle, BoundsBuilder, Point, Transform};
use crate::lib::Vec;
#[derive(Copy, Clone, PartialEq)]
pub enum ArcSize {
Small,
Large,
}
#[derive(Copy, Clone, PartialEq)]
pub enum ArcSweep {
Positive,
Negative,
}
pub trait PathBuilder: Sized {
fn current_point(&self) -> Point;
fn move_to(&mut self, to: impl Into<Point>) -> &mut Self;
fn rel_move_to(&mut self, to: impl Into<Point>) -> &mut Self {
self.move_to(to.into() + self.current_point())
}
fn line_to(&mut self, to: impl Into<Point>) -> &mut Self;
fn rel_line_to(&mut self, to: impl Into<Point>) -> &mut Self {
self.line_to(to.into() + self.current_point())
}
fn curve_to(
&mut self,
control1: impl Into<Point>,
control2: impl Into<Point>,
to: impl Into<Point>,
) -> &mut Self;
fn rel_curve_to(
&mut self,
control1: impl Into<Point>,
control2: impl Into<Point>,
to: impl Into<Point>,
) -> &mut Self {
let r = self.current_point();
self.curve_to(control1.into() + r, control2.into() + r, to.into() + r)
}
fn quad_to(&mut self, control1: impl Into<Point>, to: impl Into<Point>) -> &mut Self;
fn rel_quad_to(&mut self, control: impl Into<Point>, to: impl Into<Point>) -> &mut Self {
let r = self.current_point();
self.quad_to(control.into() + r, to.into() + r)
}
fn arc_to(
&mut self,
rx: f32,
ry: f32,
angle: Angle,
size: ArcSize,
sweep: ArcSweep,
to: impl Into<Point>,
) -> &mut Self {
let from = self.current_point();
arc(
self,
from,
rx,
ry,
angle.to_radians(),
size,
sweep,
to.into(),
);
self
}
fn rel_arc_to(
&mut self,
rx: f32,
ry: f32,
angle: Angle,
size: ArcSize,
sweep: ArcSweep,
to: impl Into<Point>,
) -> &mut Self {
self.arc_to(rx, ry, angle, size, sweep, to.into() + self.current_point())
}
fn close(&mut self) -> &mut Self;
fn add_rect(&mut self, xy: impl Into<Point>, w: f32, h: f32) -> &mut Self {
let p = xy.into();
let (l, t, r, b) = (p.x, p.y, p.x + w, p.y + h);
self.move_to(p);
self.line_to((r, t));
self.line_to((r, b));
self.line_to((l, b));
self.close()
}
fn add_round_rect(
&mut self,
xy: impl Into<Point>,
w: f32,
h: f32,
rx: f32,
ry: f32,
) -> &mut Self {
let p = xy.into();
let size = ArcSize::Small;
let sweep = ArcSweep::Positive;
let a = Angle::from_radians(0.);
let hw = w * 0.5;
let rx = rx.max(0.).min(hw);
let hh = h * 0.5;
let ry = ry.max(0.).min(hh);
self.move_to((p.x + rx, p.y));
self.line_to((p.x + w - rx, p.y));
self.arc_to(rx, ry, a, size, sweep, (p.x + w, p.y + ry));
self.line_to((p.x + w, p.y + h - ry));
self.arc_to(rx, ry, a, size, sweep, (p.x + w - rx, p.y + h));
self.line_to((p.x + rx, p.y + h));
self.arc_to(rx, ry, a, size, sweep, (p.x, p.y + h - ry));
self.line_to((p.x, p.y + ry));
self.arc_to(rx, ry, a, size, sweep, (p.x + rx, p.y));
self.close()
}
fn add_ellipse(&mut self, center: impl Into<Point>, rx: f32, ry: f32) -> &mut Self {
let center = center.into();
let cx = center.x;
let cy = center.y;
let a = 0.551915024494;
let arx = a * rx;
let ary = a * ry;
self.move_to((cx + rx, cy));
self.curve_to((cx + rx, cy + ary), (cx + arx, cy + ry), (cx, cy + ry));
self.curve_to((cx - arx, cy + ry), (cx - rx, cy + ary), (cx - rx, cy));
self.curve_to((cx - rx, cy - ary), (cx - arx, cy - ry), (cx, cy - ry));
self.curve_to((cx + arx, cy - ry), (cx + rx, cy - ary), (cx + rx, cy));
self.close()
}
fn add_circle(&mut self, center: impl Into<Point>, r: f32) -> &mut Self {
self.add_ellipse(center, r, r)
}
}
impl PathBuilder for Vec<Command> {
fn current_point(&self) -> Point {
match self.last() {
None => Point::ZERO,
Some(cmd) => match cmd {
Command::MoveTo(p)
| Command::LineTo(p)
| Command::QuadTo(_, p)
| Command::CurveTo(_, _, p) => *p,
Command::Close => {
for cmd in self.iter().rev().skip(1) {
match cmd {
Command::MoveTo(p) => return *p,
_ => {}
}
}
return Point::ZERO;
}
},
}
}
fn move_to(&mut self, to: impl Into<Point>) -> &mut Self {
self.push(Command::MoveTo(to.into()));
self
}
fn line_to(&mut self, to: impl Into<Point>) -> &mut Self {
self.push(Command::LineTo(to.into()));
self
}
fn quad_to(&mut self, control: impl Into<Point>, to: impl Into<Point>) -> &mut Self {
self.push(Command::QuadTo(control.into(), to.into()));
self
}
fn curve_to(
&mut self,
control1: impl Into<Point>,
control2: impl Into<Point>,
to: impl Into<Point>,
) -> &mut Self {
self.push(Command::CurveTo(
control1.into(),
control2.into(),
to.into(),
));
self
}
fn close(&mut self) -> &mut Self {
self.push(Command::Close);
self
}
}
pub struct TransformSink<'a, S> {
pub sink: &'a mut S,
pub transform: Transform,
}
impl<'a, S: PathBuilder> PathBuilder for TransformSink<'a, S> {
fn current_point(&self) -> Point {
self.sink.current_point()
}
fn move_to(&mut self, to: impl Into<Point>) -> &mut Self {
self.sink.move_to(self.transform.transform_point(to.into()));
self
}
fn line_to(&mut self, to: impl Into<Point>) -> &mut Self {
self.sink.line_to(self.transform.transform_point(to.into()));
self
}
fn quad_to(&mut self, control: impl Into<Point>, to: impl Into<Point>) -> &mut Self {
self.sink.quad_to(
self.transform.transform_point(control.into()),
self.transform.transform_point(to.into()),
);
self
}
fn curve_to(
&mut self,
control1: impl Into<Point>,
control2: impl Into<Point>,
to: impl Into<Point>,
) -> &mut Self {
self.sink.curve_to(
self.transform.transform_point(control1.into()),
self.transform.transform_point(control2.into()),
self.transform.transform_point(to.into()),
);
self
}
fn close(&mut self) -> &mut Self {
self.sink.close();
self
}
}
impl PathBuilder for BoundsBuilder {
fn current_point(&self) -> Point {
self.current
}
fn move_to(&mut self, to: impl Into<Point>) -> &mut Self {
let p = to.into();
self.add(p);
self.current = p;
self
}
fn line_to(&mut self, to: impl Into<Point>) -> &mut Self {
let p = to.into();
self.add(p);
self.current = p;
self
}
fn quad_to(&mut self, control: impl Into<Point>, to: impl Into<Point>) -> &mut Self {
self.add(control.into());
let p = to.into();
self.add(p);
self.current = p;
self
}
fn curve_to(
&mut self,
control1: impl Into<Point>,
control2: impl Into<Point>,
to: impl Into<Point>,
) -> &mut Self {
self.add(control1.into());
self.add(control2.into());
let p = to.into();
self.add(p);
self.current = p;
self
}
fn close(&mut self) -> &mut Self {
self
}
}
#[derive(Copy, Clone, Default)]
pub struct Arc {
count: usize,
center: (f32, f32),
radii: (f32, f32),
cosphi: f32,
sinphi: f32,
ang1: f32,
ang2: f32,
a: f32,
}
impl Arc {
pub fn new(
from: impl Into<[f32; 2]>,
rx: f32,
ry: f32,
angle: f32,
size: ArcSize,
sweep: ArcSweep,
to: impl Into<[f32; 2]>,
) -> Self {
let from = from.into();
let to = to.into();
let (px, py) = (from[0], from[1]);
const TAU: f32 = 3.141579 * 2.;
let (sinphi, cosphi) = angle.sin_cos();
let pxp = cosphi * (px - to[0]) / 2. + sinphi * (py - to[1]) / 2.;
let pyp = -sinphi * (px - to[0]) / 2. + cosphi * (py - to[1]) / 2.;
if pxp == 0. && pyp == 0. {
return Self::default();
}
let mut rx = rx.abs();
let mut ry = ry.abs();
let lambda = pxp.powi(2) / rx.powi(2) + pyp.powi(2) / ry.powi(2);
if lambda > 1. {
let s = lambda.sqrt();
rx *= s;
ry *= s;
}
let large_arc = size == ArcSize::Large;
let sweep = sweep == ArcSweep::Positive;
let (cx, cy, ang1, mut ang2) = {
fn vec_angle(ux: f32, uy: f32, vx: f32, vy: f32) -> f32 {
let sign = if (ux * vy - uy * vx) < 0. { -1. } else { 1. };
let mut dot = ux * vx + uy * vy;
if dot > 1. {
dot = 1.
} else if dot < -1. {
dot = -1.
}
sign * dot.acos()
}
let rxsq = rx * rx;
let rysq = ry * ry;
let pxpsq = pxp * pxp;
let pypsq = pyp * pyp;
let mut radicant = (rxsq * rysq) - (rxsq * pypsq) - (rysq * pxpsq);
if radicant < 0. {
radicant = 0.;
}
radicant /= (rxsq * pypsq) + (rysq * pxpsq);
radicant = radicant.sqrt() * if large_arc == sweep { -1. } else { 1. };
let cxp = radicant * rx / ry * pyp;
let cyp = radicant * -ry / rx * pxp;
let cx = cosphi * cxp - sinphi * cyp + (px + to[0]) / 2.;
let cy = sinphi * cxp + cosphi * cyp + (py + to[1]) / 2.;
let vx1 = (pxp - cxp) / rx;
let vy1 = (pyp - cyp) / ry;
let vx2 = (-pxp - cxp) / rx;
let vy2 = (-pyp - cyp) / ry;
let ang1 = vec_angle(1., 0., vx1, vy1);
let mut ang2 = vec_angle(vx1, vy1, vx2, vy2);
if !sweep && ang2 > 0. {
ang2 -= TAU;
}
if sweep && ang2 < 0. {
ang2 += TAU;
}
(cx, cy, ang1, ang2)
};
let mut ratio = ang2.abs() / (TAU / 4.);
if (1. - ratio).abs() < 0.0000001 {
ratio = 1.
}
let segments = ratio.ceil().max(1.);
ang2 /= segments;
let a = if ang2 == 1.5707963267948966 {
0.551915024494
} else if ang2 == -1.5707963267948966 {
-0.551915024494
} else {
4. / 3. * (ang2 / 4.).tan()
};
Self {
count: segments as usize,
center: (cx, cy),
radii: (rx, ry),
sinphi,
cosphi,
ang1,
ang2,
a,
}
}
}
impl Iterator for Arc {
type Item = Command;
fn next(&mut self) -> Option<Self::Item> {
if self.count == 0 {
return None;
}
self.count -= 1;
let (y1, x1) = self.ang1.sin_cos();
let (y2, x2) = (self.ang1 + self.ang2).sin_cos();
let a = self.a;
let (cx, cy) = self.center;
let (rx, ry) = self.radii;
let sinphi = self.sinphi;
let cosphi = self.cosphi;
let c1 = Point::new((x1 - y1 * a) * rx, (y1 + x1 * a) * ry);
let c1 = Point::new(
cx + (cosphi * c1.x - sinphi * c1.y),
cy + (sinphi * c1.x + cosphi * c1.y),
);
let c2 = Point::new((x2 + y2 * a) * rx, (y2 - x2 * a) * ry);
let c2 = Point::new(
cx + (cosphi * c2.x - sinphi * c2.y),
cy + (sinphi * c2.x + cosphi * c2.y),
);
let p = Point::new(x2 * rx, y2 * ry);
let p = Point::new(
cx + (cosphi * p.x - sinphi * p.y),
cy + (sinphi * p.x + cosphi * p.y),
);
self.ang1 += self.ang2;
Some(Command::CurveTo(c1, c2, p))
}
}
pub fn arc(
sink: &mut impl PathBuilder,
from: Point,
rx: f32,
ry: f32,
angle: f32,
size: ArcSize,
sweep: ArcSweep,
to: Point,
) {
let p = from;
let (px, py) = (p.x, p.y);
const TAU: f32 = core::f32::consts::PI * 2.;
let (sinphi, cosphi) = angle.sin_cos();
let pxp = cosphi * (px - to.x) / 2. + sinphi * (py - to.y) / 2.;
let pyp = -sinphi * (px - to.x) / 2. + cosphi * (py - to.y) / 2.;
if pxp == 0. && pyp == 0. {
return;
}
let mut rx = rx.abs();
let mut ry = ry.abs();
let lambda = pxp.powi(2) / rx.powi(2) + pyp.powi(2) / ry.powi(2);
if lambda > 1. {
let s = lambda.sqrt();
rx *= s;
ry *= s;
}
let large_arc = size == ArcSize::Large;
let sweep = sweep == ArcSweep::Positive;
let (cx, cy, mut ang1, mut ang2) = {
fn vec_angle(ux: f32, uy: f32, vx: f32, vy: f32) -> f32 {
let sign = if (ux * vy - uy * vx) < 0. { -1. } else { 1. };
let mut dot = ux * vx + uy * vy;
if dot > 1. {
dot = 1.
} else if dot < -1. {
dot = -1.
}
sign * dot.acos()
}
let rxsq = rx * rx;
let rysq = ry * ry;
let pxpsq = pxp * pxp;
let pypsq = pyp * pyp;
let mut radicant = (rxsq * rysq) - (rxsq * pypsq) - (rysq * pxpsq);
if radicant < 0. {
radicant = 0.;
}
radicant /= (rxsq * pypsq) + (rysq * pxpsq);
radicant = radicant.sqrt() * if large_arc == sweep { -1. } else { 1. };
let cxp = radicant * rx / ry * pyp;
let cyp = radicant * -ry / rx * pxp;
let cx = cosphi * cxp - sinphi * cyp + (px + to.x) / 2.;
let cy = sinphi * cxp + cosphi * cyp + (py + to.y) / 2.;
let vx1 = (pxp - cxp) / rx;
let vy1 = (pyp - cyp) / ry;
let vx2 = (-pxp - cxp) / rx;
let vy2 = (-pyp - cyp) / ry;
let ang1 = vec_angle(1., 0., vx1, vy1);
let mut ang2 = vec_angle(vx1, vy1, vx2, vy2);
if !sweep && ang2 > 0. {
ang2 -= TAU;
}
if sweep && ang2 < 0. {
ang2 += TAU;
}
(cx, cy, ang1, ang2)
};
let mut ratio = ang2.abs() / (TAU / 4.);
if (1. - ratio).abs() < 0.0000001 {
ratio = 1.
}
let segments = ratio.ceil().max(1.);
ang2 /= segments;
let a = if ang2 == 1.5707963267948966 {
0.551915024494
} else if ang2 == -1.5707963267948966 {
-0.551915024494
} else {
4. / 3. * (ang2 / 4.).tan()
};
for _ in 0..segments as usize {
let (y1, x1) = ang1.sin_cos();
let (y2, x2) = (ang1 + ang2).sin_cos();
let c1 = Point::new((x1 - y1 * a) * rx, (y1 + x1 * a) * ry);
let c1 = Point::new(
cx + (cosphi * c1.x - sinphi * c1.y),
cy + (sinphi * c1.x + cosphi * c1.y),
);
let c2 = Point::new((x2 + y2 * a) * rx, (y2 - x2 * a) * ry);
let c2 = Point::new(
cx + (cosphi * c2.x - sinphi * c2.y),
cy + (sinphi * c2.x + cosphi * c2.y),
);
let p = Point::new(x2 * rx, y2 * ry);
let p = Point::new(
cx + (cosphi * p.x - sinphi * p.y),
cy + (sinphi * p.x + cosphi * p.y),
);
sink.curve_to(c1, c2, p);
ang1 += ang2;
}
}