use super::*;
use super::geometry::{ellipse_point, sample_bezier_polyline, SegKind, Segment};
pub(super) struct Chain {
pub(super) entries: Vec<(usize, bool)>,
pub(super) closed: bool,
}
pub(super) fn chain_segments(segments: &[Segment]) -> Vec<Chain> {
let count = segments.len();
let mut used = vec![false; count];
let mut chains = Vec::new();
while let Some(seed) = used.iter().position(|&u| !u) {
used[seed] = true;
let mut entries: Vec<(usize, bool)> = vec![(seed, false)];
let mut head = segments[seed].a;
let mut tail = segments[seed].b;
let mut closed = false;
loop {
if close(tail, head) {
closed = true;
break;
}
if let Some((index, reversed, far)) = attach(segments, &used, tail) {
used[index] = true;
entries.push((index, reversed));
tail = far;
continue;
}
if let Some((index, reversed, far)) = attach(segments, &used, head) {
used[index] = true;
entries.insert(0, (index, !reversed));
head = far;
continue;
}
break;
}
chains.push(Chain { entries, closed });
}
chains
}
fn attach(segments: &[Segment], used: &[bool], cursor: [f64; 2]) -> Option<(usize, bool, [f64; 2])> {
for (index, segment) in segments.iter().enumerate() {
if used[index] {
continue;
}
if close(segment.a, cursor) {
return Some((index, false, segment.b));
}
if close(segment.b, cursor) {
return Some((index, true, segment.a));
}
}
None
}
pub(super) fn plan_length(plan: &[(usize, bool)], segments: &[Segment]) -> f64 {
plan.iter()
.map(|&(index, _)| {
let segment = &segments[index];
match &segment.kind {
SegKind::Line => {
(segment.b[0] - segment.a[0]).hypot(segment.b[1] - segment.a[1])
}
SegKind::Arc { radius, sweep, .. } => radius * sweep,
SegKind::Bezier { controls } => sample_bezier_polyline(controls, 16)
.windows(2)
.map(|pair| (pair[1][0] - pair[0][0]).hypot(pair[1][1] - pair[0][1]))
.sum(),
SegKind::EllipseHalf {
center,
major,
minor,
upper,
} => {
let base = if *upper { PI } else { 0.0 };
(0..16)
.map(|step| {
let t0 = base + PI * (step as f64 / 16.0);
let t1 = base + PI * ((step + 1) as f64 / 16.0);
let p0 = ellipse_point(*center, *major, *minor, t0);
let p1 = ellipse_point(*center, *major, *minor, t1);
(p1[0] - p0[0]).hypot(p1[1] - p0[1])
})
.sum()
}
}
})
.sum()
}
pub(super) fn emit_chain(
plan: &[(usize, bool)],
segments: &[Segment],
frame: &Frame,
closed: bool,
) -> Result<Vec<NurbsCurve>, String> {
let mut curves = Vec::with_capacity(plan.len());
for &(index, reversed) in plan {
curves.push(segments[index].to_curve(frame, reversed)?);
}
weld_chain(&mut curves, closed);
Ok(curves)
}
pub(super) fn weld_chain(curves: &mut [NurbsCurve], closed: bool) {
for index in 1..curves.len() {
let target = curve_endpoint(&curves[index - 1], false);
snap_curve_endpoint(&mut curves[index], target, true);
}
if closed && !curves.is_empty() {
let target = curve_endpoint(&curves[0], true);
let last = curves.len() - 1;
snap_curve_endpoint(&mut curves[last], target, false);
}
}
fn curve_endpoint(curve: &NurbsCurve, at_start: bool) -> Vec3 {
let point = if at_start {
curve.control_points.first()
} else {
curve.control_points.last()
}
.copied()
.expect("profile curves have control points");
Vec3::new(point.x / point.w, point.y / point.w, point.z / point.w)
}
fn snap_curve_endpoint(curve: &mut NurbsCurve, target: Vec3, at_start: bool) {
let index = if at_start {
0
} else {
curve.control_points.len() - 1
};
let weight = curve.control_points[index].w;
curve.control_points[index] = Vec4::from_point(target, weight);
}
fn close(a: [f64; 2], b: [f64; 2]) -> bool {
(a[0] - b[0]).abs() <= JOIN_TOL && (a[1] - b[1]).abs() <= JOIN_TOL
}