use crate::profile::Profile2D;
use crate::Point2;
const SMOOTH_CURVE_SPACING_RATIO: f64 = 1.0 / 16.0;
const SMOOTH_CURVE_LONGEST_EDGE_RATIO: f64 = 0.10;
const RDP_EPSILON_RATIO: f64 = 1.0 / 100.0;
const RDP_EPSILON_MIN: f64 = 5.0e-3;
const RDP_EPSILON_MAX_M: f64 = 1.0e-2;
const THIN_FEATURE_RATIO: f64 = 0.05;
const DOUBLE_BACK_REFLEX_ARC_FRACTION: f64 = 0.15;
const REFLEX_TURN_SIN_TOL: f64 = 1.0e-6;
const SMOOTH_CURVE_MIN_VERTICES: usize = 24;
const SIMPLIFIED_MIN_VERTICES: usize = 12;
pub(super) fn mirror_profile_about_y_axis(profile: &mut Profile2D) {
for p in &mut profile.outer {
p.x = -p.x;
}
profile.outer.reverse();
for hole in &mut profile.holes {
for p in hole.iter_mut() {
p.x = -p.x;
}
hole.reverse();
}
}
#[inline]
fn perpendicular_distance(p: Point2<f64>, a: Point2<f64>, b: Point2<f64>) -> f64 {
let dx = b.x - a.x;
let dy = b.y - a.y;
let len_sq = dx * dx + dy * dy;
if len_sq < f64::EPSILON {
let ex = p.x - a.x;
let ey = p.y - a.y;
return (ex * ex + ey * ey).sqrt();
}
let cross = (p.x - a.x) * dy - (p.y - a.y) * dx;
cross.abs() / len_sq.sqrt()
}
fn rdp_simplify_open(points: &[Point2<f64>], epsilon: f64) -> Vec<Point2<f64>> {
let n = points.len();
if n < 3 {
return points.to_vec();
}
let mut keep = vec![false; n];
keep[0] = true;
keep[n - 1] = true;
let mut stack: Vec<(usize, usize)> = Vec::new();
stack.push((0, n - 1));
while let Some((start, end)) = stack.pop() {
if end <= start + 1 {
continue;
}
let a = points[start];
let b = points[end];
let mut max_dist = 0.0;
let mut max_idx = start;
for (i, p) in points.iter().enumerate().take(end).skip(start + 1) {
let d = perpendicular_distance(*p, a, b);
if d > max_dist {
max_dist = d;
max_idx = i;
}
}
if max_dist > epsilon {
keep[max_idx] = true;
stack.push((start, max_idx));
stack.push((max_idx, end));
}
}
points
.iter()
.enumerate()
.filter_map(|(i, p)| if keep[i] { Some(*p) } else { None })
.collect()
}
const SELF_INTERSECT_SCAN_MAX_VERTICES: usize = 1024;
fn closed_loop_self_intersects(loop_: &[Point2<f64>]) -> bool {
let n = loop_.len();
if !(4..=SELF_INTERSECT_SCAN_MAX_VERTICES).contains(&n) {
return false;
}
#[inline]
fn orient(a: Point2<f64>, b: Point2<f64>, c: Point2<f64>) -> f64 {
(b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
}
#[inline]
fn segments_properly_cross(
a: Point2<f64>,
b: Point2<f64>,
c: Point2<f64>,
d: Point2<f64>,
) -> bool {
let d1 = orient(c, d, a);
let d2 = orient(c, d, b);
let d3 = orient(a, b, c);
let d4 = orient(a, b, d);
((d1 > 0.0) != (d2 > 0.0)) && ((d3 > 0.0) != (d4 > 0.0))
}
for i in 0..n {
let a = loop_[i];
let b = loop_[(i + 1) % n];
for j in (i + 1)..n {
if (j + 1) % n == i || (i + 1) % n == j {
continue;
}
let c = loop_[j];
let d = loop_[(j + 1) % n];
if segments_properly_cross(a, b, c, d) {
return true;
}
}
}
false
}
fn loop_doubles_back(loop_: &[Point2<f64>]) -> bool {
let n = loop_.len();
if n < 4 {
return false;
}
let mut signed_area2 = 0.0;
for i in 0..n {
let a = loop_[i];
let b = loop_[(i + 1) % n];
signed_area2 += a.x * b.y - b.x * a.y;
}
if signed_area2 == 0.0 {
return false;
}
let winding = signed_area2.signum();
let mut perimeter = 0.0;
let mut reflex_len = 0.0;
for i in 0..n {
let prev = loop_[(i + n - 1) % n];
let cur = loop_[i];
let next = loop_[(i + 1) % n];
let (ex_in, ey_in) = (cur.x - prev.x, cur.y - prev.y);
let (ex_out, ey_out) = (next.x - cur.x, next.y - cur.y);
let out_len = (ex_out * ex_out + ey_out * ey_out).sqrt();
perimeter += out_len;
let in_len = (ex_in * ex_in + ey_in * ey_in).sqrt();
let denom = in_len * out_len;
if denom <= 0.0 {
continue; }
let sin_turn = (ex_in * ey_out - ey_in * ex_out) / denom;
if sin_turn * winding < -REFLEX_TURN_SIN_TOL {
reflex_len += out_len;
}
}
perimeter > 0.0 && reflex_len / perimeter > DOUBLE_BACK_REFLEX_ARC_FRACTION
}
pub(super) fn simplify_smooth_curve_polyline(
points: &[Point2<f64>],
length_unit_scale: f64,
) -> Vec<Point2<f64>> {
let raw_len = points.len();
if raw_len < SMOOTH_CURVE_MIN_VERTICES {
return points.to_vec();
}
let closed = raw_len >= 2
&& (points[0].x - points[raw_len - 1].x).abs() < 1e-9
&& (points[0].y - points[raw_len - 1].y).abs() < 1e-9;
let core: &[Point2<f64>] = if closed {
&points[..raw_len - 1]
} else {
points
};
let n = core.len();
if n < SMOOTH_CURVE_MIN_VERTICES {
return points.to_vec();
}
let mut min_x = f64::INFINITY;
let mut min_y = f64::INFINITY;
let mut max_x = f64::NEG_INFINITY;
let mut max_y = f64::NEG_INFINITY;
for p in core {
if p.x < min_x {
min_x = p.x;
}
if p.y < min_y {
min_y = p.y;
}
if p.x > max_x {
max_x = p.x;
}
if p.y > max_y {
max_y = p.y;
}
}
let dx = max_x - min_x;
let dy = max_y - min_y;
let diag = (dx * dx + dy * dy).sqrt();
if !diag.is_finite() || diag < f64::EPSILON {
return points.to_vec();
}
let mut perimeter = 0.0;
let mut longest_edge: f64 = 0.0;
let mut area2 = 0.0; for i in 0..n {
let a = core[i];
let b = core[(i + 1) % n];
let ex = b.x - a.x;
let ey = b.y - a.y;
let len = (ex * ex + ey * ey).sqrt();
perimeter += len;
if len > longest_edge {
longest_edge = len;
}
area2 += a.x * b.y - b.x * a.y;
}
let mean_edge = perimeter / n as f64;
if mean_edge / diag > SMOOTH_CURVE_SPACING_RATIO {
return points.to_vec();
}
if longest_edge / diag > SMOOTH_CURVE_LONGEST_EDGE_RATIO {
return points.to_vec();
}
let half_thickness = if perimeter > f64::EPSILON {
area2.abs() / (2.0 * perimeter)
} else {
0.0
};
let is_thin = half_thickness / diag < THIN_FEATURE_RATIO;
if is_thin && loop_doubles_back(core) {
return points.to_vec();
}
let eps_cap_units = if length_unit_scale.is_finite() && length_unit_scale > 0.0 {
RDP_EPSILON_MAX_M / length_unit_scale
} else {
f64::INFINITY
};
let epsilon = (diag * RDP_EPSILON_RATIO).max(RDP_EPSILON_MIN).min(eps_cap_units);
let mut working: Vec<Point2<f64>> = core.to_vec();
working.push(core[0]); let simplified = rdp_simplify_open(&working, epsilon);
let mut simplified_core = simplified;
if simplified_core.len() >= 2 {
let last = simplified_core.len() - 1;
if (simplified_core[0].x - simplified_core[last].x).abs() < 1e-9
&& (simplified_core[0].y - simplified_core[last].y).abs() < 1e-9
{
simplified_core.pop();
}
}
if simplified_core.len() < SIMPLIFIED_MIN_VERTICES || simplified_core.len() >= n {
return points.to_vec();
}
if simplified_core.len() > SELF_INTERSECT_SCAN_MAX_VERTICES
|| closed_loop_self_intersects(&simplified_core)
{
return points.to_vec();
}
if closed {
let first = simplified_core[0];
simplified_core.push(first);
}
simplified_core
}
#[cfg(test)]
#[path = "simplify_tests.rs"]
mod tests;