use crate::tessellation::TessellationQuality;
use crate::{Point2, Point3};
use std::f64::consts::PI;
pub(super) fn trim_polyline(points: &[Point3<f64>], start: f64, end: f64) -> Vec<Point3<f64>> {
let n = points.len();
if n < 2 || end <= start {
return Vec::new();
}
let s = start.clamp(0.0, 1.0);
let e = end.clamp(0.0, 1.0);
let denom = (n - 1) as f64;
let lerp = |t: f64| -> Point3<f64> {
let scaled = t * denom;
let mut idx = scaled.floor() as usize;
if idx >= n - 1 {
return points[n - 1];
}
let frac = scaled - idx as f64;
let a = points[idx];
idx += 1;
let b = points[idx];
Point3::new(
a.x + (b.x - a.x) * frac,
a.y + (b.y - a.y) * frac,
a.z + (b.z - a.z) * frac,
)
};
let mut out = Vec::new();
out.push(lerp(s));
for (i, p) in points.iter().enumerate() {
let t = i as f64 / denom;
if t > s && t < e {
out.push(*p);
}
}
out.push(lerp(e));
out
}
pub(super) fn approximate_arc_3pt_3d(
p1: Point3<f64>,
p2: Point3<f64>,
p3: Point3<f64>,
num_segments: usize,
) -> Vec<Point3<f64>> {
let a = p2 - p1;
let b = p3 - p1;
let normal = a.cross(&b);
let normal_len_sq = normal.norm_squared();
let arc_span = (p3 - p1).norm();
let collinear_tol = 1e-12_f64.max(arc_span.powi(4) * 1e-12);
if normal_len_sq < collinear_tol {
return vec![p1, p2, p3];
}
let n_hat = normal / normal_len_sq.sqrt();
let d11 = a.dot(&a);
let d22 = b.dot(&b);
let d12 = a.dot(&b);
let denom = 2.0 * (d11 * d22 - d12 * d12);
if denom.abs() < 1e-20 {
return vec![p1, p2, p3];
}
let u = (d22 * (d11 - d12)) / denom;
let v = (d11 * (d22 - d12)) / denom;
let center = p1 + a * u + b * v;
let radius = (p1 - center).norm();
if radius > arc_span * 100.0 {
return vec![p1, p2, p3];
}
let u_axis = (p1 - center) / radius;
let v_axis = n_hat.cross(&u_axis);
let angle_of = |pt: Point3<f64>| -> f64 {
let r = pt - center;
r.dot(&v_axis).atan2(r.dot(&u_axis))
};
let a1 = angle_of(p1); let a2 = angle_of(p2);
let a3 = angle_of(p3);
fn norm_pi(mut a: f64) -> f64 {
let two_pi = 2.0 * std::f64::consts::PI;
a %= two_pi;
if a > std::f64::consts::PI {
a -= two_pi;
} else if a < -std::f64::consts::PI {
a += two_pi;
}
a
}
let diff13 = norm_pi(a3 - a1);
let diff12 = norm_pi(a2 - a1);
let go_direct = if diff13 > 0.0 {
diff12 > 0.0 && diff12 < diff13
} else {
diff12 < 0.0 && diff12 > diff13
};
let sweep = if go_direct {
diff13
} else if diff13 > 0.0 {
diff13 - 2.0 * std::f64::consts::PI
} else {
diff13 + 2.0 * std::f64::consts::PI
};
let mut out = Vec::with_capacity(num_segments + 1);
for i in 0..=num_segments {
let t = i as f64 / num_segments as f64;
let angle = a1 + t * sweep;
let pt = center + (u_axis * radius * angle.cos()) + (v_axis * radius * angle.sin());
out.push(pt);
}
out
}
pub(super) fn same_point_3d(prev: Option<&Point3<f64>>, next: &Point3<f64>) -> bool {
match prev {
Some(p) => {
(p.x - next.x).abs() < 1e-9
&& (p.y - next.y).abs() < 1e-9
&& (p.z - next.z).abs() < 1e-9
}
None => false,
}
}
pub(super) fn rounded_rectangle_outline(
half_x: f64,
half_y: f64,
radius: f64,
ccw: bool,
quality: TessellationQuality,
) -> Vec<Point2<f64>> {
if radius <= 1.0e-9 {
let pts = vec![
Point2::new(-half_x, -half_y),
Point2::new(half_x, -half_y),
Point2::new(half_x, half_y),
Point2::new(-half_x, half_y),
];
return if ccw {
pts
} else {
pts.into_iter().rev().collect()
};
}
let segments_per_corner = quality.profile_arc_segments(6, 2);
let half_pi = PI / 2.0;
let corners = [
(half_x - radius, -half_y + radius, -half_pi, 0.0),
(half_x - radius, half_y - radius, 0.0, half_pi),
(-half_x + radius, half_y - radius, half_pi, PI),
(-half_x + radius, -half_y + radius, PI, PI + half_pi),
];
let mut points: Vec<Point2<f64>> = Vec::with_capacity((segments_per_corner + 1) * 4);
const SEAM_TOL: f64 = 1.0e-6;
for (cx, cy, a0, a1) in corners {
for i in 0..=segments_per_corner {
let t = i as f64 / segments_per_corner as f64;
let a = a0 + (a1 - a0) * t;
let pt = Point2::new(cx + radius * a.cos(), cy + radius * a.sin());
if let Some(prev) = points.last() {
if (prev.x - pt.x).abs() < SEAM_TOL && (prev.y - pt.y).abs() < SEAM_TOL {
continue;
}
}
points.push(pt);
}
}
if points.len() >= 2 {
let first = points[0];
let last = points[points.len() - 1];
if (first.x - last.x).abs() < SEAM_TOL && (first.y - last.y).abs() < SEAM_TOL {
points.pop();
}
}
if !ccw {
points.reverse();
}
points
}
fn push_dedup(out: &mut Vec<Point2<f64>>, pt: Point2<f64>) {
if out
.last()
.is_none_or(|p| (p.x - pt.x).abs() > 1.0e-9 || (p.y - pt.y).abs() > 1.0e-9)
{
out.push(pt);
}
}
pub(super) fn push_arc(
out: &mut Vec<Point2<f64>>,
cx: f64,
cy: f64,
r: f64,
a0: f64,
a1: f64,
segments: usize,
) {
let n = segments.max(1);
for i in 0..=n {
let t = i as f64 / n as f64;
let a = a0 + (a1 - a0) * t;
push_dedup(out, Point2::new(cx + r * a.cos(), cy + r * a.sin()));
}
}
fn round_corner(
prev: Point2<f64>,
corner: Point2<f64>,
next: Point2<f64>,
r: f64,
segments: usize,
) -> Vec<Point2<f64>> {
if r <= 1.0e-9 {
return vec![corner];
}
let ein = corner - prev;
let eout = next - corner;
let (ein_n, eout_n) = (ein.norm(), eout.norm());
if ein_n < r || eout_n < r {
return vec![corner];
}
let ein = ein / ein_n;
let eout = eout / eout_n;
let t_in = corner - ein * r; let t_out = corner + eout * r; let center = corner - ein * r + eout * r;
let a0 = (t_in.y - center.y).atan2(t_in.x - center.x);
let mut a1 = (t_out.y - center.y).atan2(t_out.x - center.x);
while a1 - a0 > std::f64::consts::PI {
a1 -= 2.0 * std::f64::consts::PI;
}
while a0 - a1 > std::f64::consts::PI {
a1 += 2.0 * std::f64::consts::PI;
}
let mut out = Vec::with_capacity(segments + 1);
push_arc(&mut out, center.x, center.y, r, a0, a1, segments);
out
}
pub(super) fn fillet_outline(
sharp: &[Point2<f64>],
radii: &[(usize, f64)],
segments: usize,
) -> Vec<Point2<f64>> {
let n = sharp.len();
let mut out: Vec<Point2<f64>> = Vec::with_capacity(n + radii.len() * segments);
for i in 0..n {
let r = radii
.iter()
.find(|(idx, _)| *idx == i)
.map(|(_, r)| *r)
.unwrap_or(0.0);
if r > 1.0e-9 {
for pt in round_corner(sharp[(i + n - 1) % n], sharp[i], sharp[(i + 1) % n], r, segments)
{
push_dedup(&mut out, pt);
}
} else {
push_dedup(&mut out, sharp[i]);
}
}
if out.len() > 1 {
let (first, last) = (out[0], out[out.len() - 1]);
if (first.x - last.x).abs() <= 1.0e-9 && (first.y - last.y).abs() <= 1.0e-9 {
out.pop();
}
}
out
}