use core::f64;
const TAU: f64 = f64::consts::PI * 2.0;
fn unit_vector_angle(ux: f64, uy: f64, vx: f64, vy: f64) -> f64 {
let sign = if (ux * vy) - (uy * vx) < 0.0 {
-1.0
} else {
1.0
};
let mut dot = ux * vx + uy * vy;
if dot > 1.0 {
dot = 1.0;
}
if dot < -1.0 {
dot = -1.0;
}
sign * dot.acos()
}
fn get_arc_center(
x1: f64,
y1: f64,
x2: f64,
y2: f64,
fa: bool,
fs: bool,
rx: f64,
ry: f64,
sin_phi: f64,
cos_phi: f64,
) -> [f64; 4] {
let x1p = cos_phi * (x1 - x2) / 2.0 + sin_phi * (y1 - y2) / 2.0;
let y1p = -sin_phi * (x1 - x2) / 2.0 + cos_phi * (y1 - y2) / 2.0;
let rx_sq = rx * rx;
let ry_sq = ry * ry;
let x1p_sq = x1p * x1p;
let y1p_sq = y1p * y1p;
let mut radicant = (rx_sq * ry_sq) - (rx_sq * y1p_sq) - (ry_sq * x1p_sq);
if radicant < 0.0 {
radicant = 0.0;
}
radicant /= (rx_sq * y1p_sq) + (ry_sq * x1p_sq);
radicant = radicant.sqrt() * if fa == fs { -1.0 } else { 1.0 };
let cxp = radicant * rx / ry * y1p;
let cyp = radicant * -ry / rx * x1p;
let cx = cos_phi * cxp - sin_phi * cyp + (x1 + x2) / 2.0;
let cy = sin_phi * cxp + cos_phi * cyp + (y1 + y2) / 2.0;
let v1x = (x1p - cxp) / rx;
let v1y = (y1p - cyp) / ry;
let v2x = (-x1p - cxp) / rx;
let v2y = (-y1p - cyp) / ry;
let theta1 = unit_vector_angle(1.0, 0.0, v1x, v1y);
let mut delta_theta = unit_vector_angle(v1x, v1y, v2x, v2y);
if !fs && delta_theta > 0.0 {
delta_theta -= TAU;
}
if fs && delta_theta < 0.0 {
delta_theta += TAU;
}
[cx, cy, theta1, delta_theta]
}
fn approximate_unit_arc(theta1: f64, delta_theta: f64) -> [f64; 8] {
let alpha = 4.0 / 3.0 * (delta_theta / 4.0).tan();
let x1 = theta1.cos();
let y1 = theta1.sin();
let x2 = (theta1 + delta_theta).cos();
let y2 = (theta1 + delta_theta).sin();
return [
x1,
y1,
x1 - y1 * alpha,
y1 + x1 * alpha,
x2 + y2 * alpha,
y2 - x2 * alpha,
x2,
y2,
];
}
pub fn a2c(
x1: f64,
y1: f64,
x2: f64,
y2: f64,
fa: bool,
fs: bool,
rx: f64,
ry: f64,
phi: f64,
) -> Vec<[f64; 8]> {
let sin_phi = (phi * TAU / 360.0).sin();
let cos_phi = (phi * TAU / 360.0).cos();
let x1p = cos_phi * (x1 - x2) / 2.0 + sin_phi * (y1 - y2) / 2.0;
let y1p = -sin_phi * (x1 - x2) / 2.0 + cos_phi * (y1 - y2) / 2.0;
if x1p == 0.0 && y1p == 0.0 {
return vec![];
}
if rx == 0.0 || ry == 0.0 {
return vec![];
}
let mut rx = rx.abs();
let mut ry = ry.abs();
let lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
if lambda > 1.0 {
rx *= lambda.sqrt();
ry *= lambda.sqrt();
}
let cc = get_arc_center(x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi);
let mut result = vec![];
let mut theta1 = cc[2];
let mut delta_theta = cc[3];
let segments = *[(delta_theta.abs() / (TAU / 4.0)).ceil(), 1.0]
.iter()
.max_by(|a, b| a.total_cmp(b))
.expect("can not find max") as u32;
delta_theta /= segments as f64;
for _ in 0..segments {
result.push(approximate_unit_arc(theta1, delta_theta));
theta1 += delta_theta;
}
return result
.iter_mut()
.map(|curve| {
for i in (0..curve.len()).step_by(2) {
let mut x = curve[i + 0];
let mut y = curve[i + 1];
x *= rx;
y *= ry;
let xp = cos_phi * x - sin_phi * y;
let yp = sin_phi * x + cos_phi * y;
curve[i + 0] = xp + cc[0];
curve[i + 1] = yp + cc[1];
}
return *curve;
})
.collect();
}