use std::f32::consts::{PI, TAU};
use crate::{
PathContour, Point, StrokeCap, stroke::LineGeometry, vector_path::FLATTEN_TOLERANCE,
whole::to_i32,
};
const MAX_DISC_POINTS: usize = 64;
#[derive(Debug, Default)]
pub(crate) struct StrokeOutline {
contours: Vec<PathContour>,
used: usize,
last_disc: Option<Point>,
}
impl StrokeOutline {
pub(crate) const fn new() -> Self {
Self {
contours: Vec::new(),
used: 0,
last_disc: None,
}
}
pub(crate) fn clear(&mut self) {
self.used = 0;
self.last_disc = None;
}
pub(crate) fn contours(&self) -> &[PathContour] {
&self.contours[..self.used]
}
pub(crate) fn add_line(&mut self, line: LineGeometry) {
if line.is_degenerate() {
return;
}
let half = line.half_width;
if line.start != line.end || line.cap == StrokeCap::Square {
let along = line.frame().direction;
let reach = if line.cap == StrokeCap::Square {
half
} else {
0.0
};
let (across_x, across_y) = (-along.y * half, along.x * half);
let start = Point::new(
line.start.x - along.x * reach,
line.start.y - along.y * reach,
);
let end = Point::new(line.end.x + along.x * reach, line.end.y + along.y * reach);
self.next_contour().extend([
Point::new(start.x + across_x, start.y + across_y),
Point::new(end.x + across_x, end.y + across_y),
Point::new(end.x - across_x, end.y - across_y),
Point::new(start.x - across_x, start.y - across_y),
]);
}
if line.cap == StrokeCap::Round {
self.add_disc(line.start, half);
self.add_disc(line.end, half);
}
}
fn add_disc(&mut self, center: Point, radius: f32) {
if self.last_disc == Some(center) {
return;
}
self.last_disc = Some(center);
let count = disc_points(radius);
let (sin, cos) = (TAU / count as f32).sin_cos();
let (mut x, mut y) = (radius, 0.0_f32);
let points = self.next_contour();
for _ in 0..count {
points.push(Point::new(center.x + x, center.y + y));
(x, y) = (x * cos + y * sin, y * cos - x * sin);
}
}
fn next_contour(&mut self) -> &mut Vec<Point> {
if self.used == self.contours.len() {
self.contours.push(PathContour {
points: Vec::new(),
closed: true,
});
}
self.used += 1;
let points = &mut self.contours[self.used - 1].points;
points.clear();
points
}
}
fn disc_points(radius: f32) -> usize {
let cos = 1.0 - FLATTEN_TOLERANCE / radius;
let needed = if cos > -1.0 {
to_i32((PI / cos.acos()).ceil())
} else {
0
};
(usize::try_from(needed).map_or(4, |count| count.clamp(4, MAX_DISC_POINTS)) + 1) & !1
}