use crate::output::path::{CubicSegment, SmoothPath};
use crate::types::{Point, Vector};
use crate::utils::EPSILON;
use super::axis::sketch_rect_axis;
use super::corner::{RectCorner, rect_corner_cubics};
pub(super) fn build_rect_path(width: f64, height: f64, radius: f64, smoothing: f64) -> SmoothPath {
if width <= EPSILON || height <= EPSILON {
return collapsed_path();
}
if radius <= EPSILON {
return sharp_rect_path(width, height);
}
build_smooth_rect_path(width, height, radius, smoothing)
}
fn build_smooth_rect_path(width: f64, height: f64, radius: f64, smoothing: f64) -> SmoothPath {
let horizontal = sketch_rect_axis(radius, smoothing, width);
let vertical = sketch_rect_axis(radius, smoothing, height);
let corners = [
RectCorner::new(
Point::new(0.0, 0.0),
Vector::new(0.0, 1.0),
vertical,
Vector::new(1.0, 0.0),
horizontal,
),
RectCorner::new(
Point::new(width, 0.0),
Vector::new(-1.0, 0.0),
horizontal,
Vector::new(0.0, 1.0),
vertical,
),
RectCorner::new(
Point::new(width, height),
Vector::new(0.0, -1.0),
vertical,
Vector::new(-1.0, 0.0),
horizontal,
),
RectCorner::new(
Point::new(0.0, height),
Vector::new(1.0, 0.0),
horizontal,
Vector::new(0.0, -1.0),
vertical,
),
];
let cubics = corners
.iter()
.map(|corner| rect_corner_cubics(*corner, radius))
.collect::<Vec<_>>();
build_path_from_corners(corners, cubics)
}
fn collapsed_path() -> SmoothPath {
let mut path = SmoothPath::new();
path.move_to(Point::new(0.0, 0.0));
path.close();
path
}
fn sharp_rect_path(width: f64, height: f64) -> SmoothPath {
let mut path = SmoothPath::new();
path.move_to(Point::new(0.0, 0.0));
path.line_to(Point::new(width, 0.0));
path.line_to(Point::new(width, height));
path.line_to(Point::new(0.0, height));
path.close();
path
}
fn build_path_from_corners(corners: [RectCorner; 4], cubics: Vec<[CubicSegment; 3]>) -> SmoothPath {
let mut path = SmoothPath::new();
let mut current = corners[0].end();
path.move_to(current);
for index in 1..corners.len() {
push_line_if_needed(&mut path, &mut current, corners[index].start());
push_cubics_if_needed(&mut path, &mut current, &cubics[index]);
}
push_line_if_needed(&mut path, &mut current, corners[0].start());
push_cubics_if_needed(&mut path, &mut current, &cubics[0]);
path.close();
path
}
fn push_line_if_needed(path: &mut SmoothPath, current: &mut Point, to: Point) {
if !points_close(*current, to, EPSILON) {
path.line_to(to);
}
*current = to;
}
fn push_cubics_if_needed(path: &mut SmoothPath, current: &mut Point, cubics: &[CubicSegment]) {
for (index, cubic) in cubics.iter().enumerate() {
if index == 1 || !cubic_is_zero(*cubic) {
path.cubic_to(cubic.ctrl1, cubic.ctrl2, cubic.to);
}
*current = cubic.to;
}
}
fn cubic_is_zero(cubic: CubicSegment) -> bool {
points_close(cubic.from, cubic.ctrl1, EPSILON)
&& points_close(cubic.from, cubic.ctrl2, EPSILON)
&& points_close(cubic.from, cubic.to, EPSILON)
}
fn points_close(a: Point, b: Point, tolerance: f64) -> bool {
(a.x - b.x).abs() <= tolerance && (a.y - b.y).abs() <= tolerance
}