use super::{
types::{Coordinate,Degree},
point::{Point,GenericPoint}
};
pub fn arc_to_curves(start_point: Point, center_point: Point, angle: Degree) -> Vec<[Point;4]> {
if angle != 0.0 {
let (full_curves_n, last_curve_angle) = (
(angle / 90.0).abs().floor() as u16,
angle % 90.0
);
let mut curves = Vec::with_capacity(if last_curve_angle != 0.0 {full_curves_n + 1} else {full_curves_n} as usize);
const CONTROL_POINT_DISTANCE: Degree = 0.390_262_856_458_446_8; const SIN_COS_45: Coordinate = std::f64::consts::FRAC_1_SQRT_2 as Coordinate;
let mut vector = start_point - center_point;
let angle_direction = angle.signum() as Coordinate;
for _ in 0..full_curves_n {
let rotated_vector = Point {
x: -vector.y * angle_direction,
y: vector.x * angle_direction
};
let start_end_line = (rotated_vector - vector) * CONTROL_POINT_DISTANCE as Coordinate * SIN_COS_45;
let end_start_line = -start_end_line;
curves.push([
center_point + vector,
center_point + vector + Point {
x: start_end_line.x + start_end_line.y * angle_direction,
y: start_end_line.y - start_end_line.x * angle_direction
},
center_point + rotated_vector + Point {
x: end_start_line.x - end_start_line.y * angle_direction,
y: end_start_line.x * angle_direction + end_start_line.y
},
center_point + rotated_vector
]);
vector = rotated_vector;
}
if last_curve_angle != 0.0 {
let precise_vector = GenericPoint::<Degree>::from(vector);
let last_curve_rad = last_curve_angle.to_radians();
let (angle_sin, angle_cos) = (last_curve_rad.sin(), last_curve_rad.cos());
let precise_rotated_vector = GenericPoint {
x: precise_vector.x * angle_cos - precise_vector.y * angle_sin,
y: precise_vector.y * angle_cos + precise_vector.x * angle_sin
};
let start_end_line = (precise_rotated_vector - precise_vector) * CONTROL_POINT_DISTANCE;
let end_start_line = -start_end_line;
let last_curve_half_rad = last_curve_rad * 0.5;
let (half_angle_sin, half_angle_cos) = (last_curve_half_rad.sin(), last_curve_half_rad.cos());
curves.push([
center_point + vector,
center_point + vector + Point {
x: (start_end_line.x * half_angle_cos + start_end_line.y * half_angle_sin) as Coordinate,
y: (start_end_line.y * half_angle_cos - start_end_line.x * half_angle_sin) as Coordinate
},
center_point + Point::from(precise_rotated_vector) + Point {
x: (end_start_line.x * half_angle_cos - end_start_line.y * half_angle_sin) as Coordinate,
y: (end_start_line.y * half_angle_cos + end_start_line.x * half_angle_sin) as Coordinate
},
center_point + Point::from(precise_rotated_vector)
]);
}
curves
} else {
vec![]
}
}
const CURVE_DEVIATION_LENGTH: Coordinate = 0.125;
#[inline]
fn is_curve_flat(p: &[Point;4]) -> bool {
(p[1] - p[0]).len() + (p[2] - p[1]).len() + (p[3] - p[2]).len()
<
(p[3] - p[0]).len() + CURVE_DEVIATION_LENGTH
}
#[inline]
fn split_curve_mid(p: [Point;4]) -> ([Point;4], [Point;4]) {
const T: Coordinate = 0.5;
let (p01, p12, p23) = (
p[0] + (p[1] - p[0]) * T,
p[1] + (p[2] - p[1]) * T,
p[2] + (p[3] - p[2]) * T
);
let (p012, p123) = (
p01 + (p12 - p01) * T,
p12 + (p23 - p12) * T
);
let p1234 = p012 + (p123 - p012) * T;
(
[p[0], p01, p012, p1234],
[p1234, p123, p23, p[3]]
)
}
pub fn flatten_curve(start_point: Point, control_point1: Point, control_point2: Point, end_point: Point) -> Vec<Point> {
let mut points = vec![start_point];
let mut curves = vec![[start_point, control_point1, control_point2, end_point]];
while let Some(curve) = curves.pop() {
if is_curve_flat(&curve) {
points.push(curve[3]);
} else {
let (curve1, curve2) = split_curve_mid(curve);
curves.push(curve2);
curves.push(curve1);
}
}
points
}
#[cfg(test)]
mod tests {
use super::{Point,arc_to_curves,flatten_curve};
#[test]
fn arc_curves() {
let curve = arc_to_curves(Point {x: 10.0, y: 10.0}, Point {x: 10.0, y: 50.0}, 45.0);
assert!(
{
let end_point = curve.first().expect("One curve must exist for this input!")[3];
(38.0..38.5).contains(&end_point.x) && (21.5..22.0).contains(&end_point.y)
},
"Short curve points unexpected: {:?}", curve
);
assert_eq!(
arc_to_curves(Point {x: 0.0, y: 0.0}, Point {x: 0.0, y: 100.0}, -450.0),
vec![
[Point {x: 0.0, y: 0.0}, Point {x: -55.191498, y: 0.0}, Point {x: -100.0, y: 44.808502}, Point {x: -100.0, y: 100.0}],
[Point {x: -100.0, y: 100.0}, Point {x: -100.0, y: 155.1915}, Point {x: -55.191498, y: 200.0}, Point {x: 0.0, y: 200.0}],
[Point {x: 0.0, y: 200.0}, Point {x: 55.191498, y: 200.0}, Point {x: 100.0, y: 155.1915}, Point {x: 100.0, y: 100.0}],
[Point {x: 100.0, y: 100.0}, Point {x: 100.0, y: 44.808502}, Point {x: 55.191498, y: 0.0}, Point {x: 0.0, y: 0.0}],
[Point {x: 0.0, y: 0.0}, Point {x: -55.191498, y: 0.0}, Point {x: -100.0, y: 44.808502}, Point {x: -100.0, y: 100.0}]
]
);
}
#[test]
fn flat_curve() {
assert_eq!(
flatten_curve(Point {x: -2.0, y: 7.0}, Point {x: -1.0, y: 7.0}, Point {x: 0.0, y: 7.0}, Point {x: 1.0, y: 7.0}),
vec![Point {x: -2.0, y: 7.0}, Point {x: 1.0, y: 7.0}]
);
let points = flatten_curve(Point {x: -5.0, y: 0.0}, Point {x: 0.0, y: -4.0}, Point {x: 5.0, y: 6.0}, Point {x: 10.0, y: 1.0});
assert!(points.len() > 5, "Not enough points: {:?}", points);
}
}