use crate::basics::{
uround, VertexSource, PATH_CMD_END_POLY, PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP,
PATH_FLAGS_CCW, PATH_FLAGS_CLOSE, PI,
};
pub struct Ellipse {
x: f64,
y: f64,
rx: f64,
ry: f64,
scale: f64,
num: u32,
step: u32,
cw: bool,
}
impl Ellipse {
pub fn new(x: f64, y: f64, rx: f64, ry: f64, num_steps: u32, cw: bool) -> Self {
let mut e = Self {
x,
y,
rx,
ry,
scale: 1.0,
num: num_steps,
step: 0,
cw,
};
if e.num == 0 {
e.calc_num_steps();
}
e
}
pub fn default_new() -> Self {
Self {
x: 0.0,
y: 0.0,
rx: 1.0,
ry: 1.0,
scale: 1.0,
num: 4,
step: 0,
cw: false,
}
}
pub fn init(&mut self, x: f64, y: f64, rx: f64, ry: f64, num_steps: u32, cw: bool) {
self.x = x;
self.y = y;
self.rx = rx;
self.ry = ry;
self.num = num_steps;
self.step = 0;
self.cw = cw;
if self.num == 0 {
self.calc_num_steps();
}
}
pub fn set_approximation_scale(&mut self, scale: f64) {
self.scale = scale;
self.calc_num_steps();
}
fn calc_num_steps(&mut self) {
let ra = (self.rx.abs() + self.ry.abs()) / 2.0;
let da = (ra / (ra + 0.125 / self.scale)).acos() * 2.0;
self.num = uround(2.0 * PI / da);
}
}
impl VertexSource for Ellipse {
fn rewind(&mut self, _path_id: u32) {
self.step = 0;
}
fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
if self.step == self.num {
self.step += 1;
return PATH_CMD_END_POLY | PATH_FLAGS_CLOSE | PATH_FLAGS_CCW;
}
if self.step > self.num {
return PATH_CMD_STOP;
}
let mut angle = self.step as f64 / self.num as f64 * 2.0 * PI;
if self.cw {
angle = 2.0 * PI - angle;
}
*x = self.x + angle.cos() * self.rx;
*y = self.y + angle.sin() * self.ry;
self.step += 1;
if self.step == 1 {
PATH_CMD_MOVE_TO
} else {
PATH_CMD_LINE_TO
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::basics::{is_end_poly, is_stop};
#[test]
fn test_ellipse_basic() {
let mut e = Ellipse::new(0.0, 0.0, 10.0, 10.0, 8, false);
e.rewind(0);
let mut x = 0.0;
let mut y = 0.0;
let cmd = e.vertex(&mut x, &mut y);
assert_eq!(cmd, PATH_CMD_MOVE_TO);
assert!((x - 10.0).abs() < 1e-6);
assert!(y.abs() < 1e-6);
for _ in 1..8 {
let cmd = e.vertex(&mut x, &mut y);
assert_eq!(cmd, PATH_CMD_LINE_TO);
}
let cmd = e.vertex(&mut x, &mut y);
assert!(is_end_poly(cmd));
let cmd = e.vertex(&mut x, &mut y);
assert!(is_stop(cmd));
}
#[test]
fn test_ellipse_vertices_on_circle() {
let mut e = Ellipse::new(0.0, 0.0, 10.0, 10.0, 4, false);
e.rewind(0);
let mut x = 0.0;
let mut y = 0.0;
e.vertex(&mut x, &mut y);
assert!((x - 10.0).abs() < 1e-6);
assert!(y.abs() < 1e-6);
e.vertex(&mut x, &mut y);
assert!(x.abs() < 1e-6);
assert!((y - 10.0).abs() < 1e-6);
e.vertex(&mut x, &mut y);
assert!((x + 10.0).abs() < 1e-6);
assert!(y.abs() < 1e-6);
e.vertex(&mut x, &mut y);
assert!(x.abs() < 1e-6);
assert!((y + 10.0).abs() < 1e-6);
}
#[test]
fn test_ellipse_cw() {
let mut e = Ellipse::new(0.0, 0.0, 10.0, 10.0, 4, true);
e.rewind(0);
let mut x = 0.0;
let mut y = 0.0;
e.vertex(&mut x, &mut y);
assert!((x - 10.0).abs() < 1e-6);
e.vertex(&mut x, &mut y);
assert!(x.abs() < 1e-6);
assert!((y + 10.0).abs() < 1e-6);
}
#[test]
fn test_ellipse_center_offset() {
let mut e = Ellipse::new(5.0, 3.0, 10.0, 10.0, 4, false);
e.rewind(0);
let mut x = 0.0;
let mut y = 0.0;
e.vertex(&mut x, &mut y);
assert!((x - 15.0).abs() < 1e-6);
assert!((y - 3.0).abs() < 1e-6);
}
#[test]
fn test_ellipse_auto_steps() {
let e = Ellipse::new(0.0, 0.0, 100.0, 100.0, 0, false);
assert!(e.num > 20);
}
#[test]
fn test_ellipse_rewind_restarts() {
let mut e = Ellipse::new(0.0, 0.0, 10.0, 10.0, 4, false);
let mut x = 0.0;
let mut y = 0.0;
e.rewind(0);
e.vertex(&mut x, &mut y);
e.vertex(&mut x, &mut y);
e.rewind(0);
let cmd = e.vertex(&mut x, &mut y);
assert_eq!(cmd, PATH_CMD_MOVE_TO);
assert!((x - 10.0).abs() < 1e-6);
}
#[test]
fn test_ellipse_different_radii() {
let mut e = Ellipse::new(0.0, 0.0, 20.0, 10.0, 4, false);
e.rewind(0);
let mut x = 0.0;
let mut y = 0.0;
e.vertex(&mut x, &mut y);
assert!((x - 20.0).abs() < 1e-6);
e.vertex(&mut x, &mut y);
assert!(x.abs() < 1e-6);
assert!((y - 10.0).abs() < 1e-6);
}
}