use crate::MathError;
use crate::nurbs::curve::NurbsCurve;
use crate::nurbs::fitting::{chord_length_params, interpolate};
use crate::vec::Point3;
use super::surface_marching::near_existing_segment;
use super::{IntersectionCurve, IntersectionPoint};
pub(super) fn chain_traced_segments(
segments: Vec<Vec<IntersectionPoint>>,
near: f64,
join: f64,
) -> Vec<Vec<IntersectionPoint>> {
let mut chains: Vec<Vec<IntersectionPoint>> = Vec::new();
for segment in segments {
let mut runs = Vec::new();
let mut run: Vec<IntersectionPoint> = Vec::new();
for p in segment {
if near_existing_segment(&chains, &p, near) {
if run.len() >= 2 {
runs.push(std::mem::take(&mut run));
} else {
run.clear();
}
} else {
run.push(p);
}
}
if run.len() >= 2 {
runs.push(run);
}
chains.extend(runs);
}
let meets = |a: Point3, b: Point3| (a - b).length() < join;
'join: loop {
for i in 0..chains.len() {
for j in 0..chains.len() {
if i == j {
continue;
}
let (first_i, last_i) = (chains[i][0].point, chains[i][chains[i].len() - 1].point);
let (first_j, last_j) = (chains[j][0].point, chains[j][chains[j].len() - 1].point);
if (first_i - last_i).length() < 1e-12 || (first_j - last_j).length() < 1e-12 {
continue;
}
let joined = if meets(last_i, first_j) {
Some((false, false))
} else if meets(last_i, last_j) {
Some((false, true))
} else if meets(first_i, first_j) {
Some((true, false))
} else {
None
};
if let Some((flip_i, flip_j)) = joined {
let mut tail = chains.remove(j);
let i = if j < i { i - 1 } else { i };
if flip_j {
tail.reverse();
}
if flip_i {
chains[i].reverse();
}
chains[i].extend(tail);
continue 'join;
}
}
}
break;
}
chains
}
pub(super) struct Section<'a> {
pub off: &'a dyn Fn(Point3, &IntersectionPoint) -> Option<f64>,
pub settle: &'a dyn Fn(Point3, &IntersectionPoint) -> Option<IntersectionPoint>,
}
pub(super) fn build_curves_from_chains(
chains: &[Vec<IntersectionPoint>],
tolerance: f64,
section: &Section<'_>,
) -> Result<Vec<IntersectionCurve>, MathError> {
let mut curves = Vec::with_capacity(chains.len());
for chain in chains {
let mut deduped: Vec<IntersectionPoint> = Vec::new();
for pt in chain {
let is_dup = deduped
.last()
.is_some_and(|last: &IntersectionPoint| (last.point - pt.point).length() < 1e-6);
if !is_dup {
deduped.push(*pt);
}
}
if deduped.len() < 2 {
continue;
}
let curve = fit_within(&mut deduped, tolerance, section)?;
curves.push(IntersectionCurve {
curve,
points: deduped,
});
}
Ok(curves)
}
const MAX_FIT_POINTS: usize = 4096;
const MAX_FIT_ROUNDS: usize = 8;
pub(super) fn fit_within(
chain: &mut Vec<IntersectionPoint>,
tolerance: f64,
section: &Section<'_>,
) -> Result<NurbsCurve, MathError> {
let mut split_at = vec![[f64::INFINITY; 2]; chain.len() - 1];
let mut round = 0;
loop {
let positions: Vec<Point3> = chain.iter().map(|p| p.point).collect();
let degree = if positions.len() <= 3 { 1 } else { 3 };
let curve = interpolate(&positions, degree)?;
round += 1;
if round > MAX_FIT_ROUNDS || chain.len() >= MAX_FIT_POINTS {
return Ok(curve);
}
let mut budget = MAX_FIT_POINTS - chain.len();
let params = chord_length_params(&positions);
let mut grown = Vec::with_capacity(chain.len() * 2);
let mut grown_at = Vec::with_capacity(split_at.len() * 2);
for ((pair, t), &[last, before]) in chain.windows(2).zip(params.windows(2)).zip(&split_at) {
grown.push(pair[0]);
let at = |f: f64| curve.evaluate(t[0] + f * (t[1] - t[0]));
let stray = if last > 0.0 {
[(0.25, &pair[0]), (0.5, &pair[0]), (0.75, &pair[1])]
.into_iter()
.filter_map(|(f, near)| (section.off)(at(f), near))
.fold(None, |far: Option<f64>, off| {
Some(far.map_or(off, |far| far.max(off)))
})
} else {
None
};
let Some(stray) = stray.filter(|&stray| stray > tolerance) else {
grown_at.push(if last > 0.0 {
[f64::INFINITY; 2]
} else {
[0.0; 2]
});
continue;
};
let gap = (pair[1].point - pair[0].point).length();
let split = (budget > 0 && stray <= 0.5 * before)
.then(|| (section.settle)(at(0.5), &pair[0]))
.flatten()
.filter(|q| {
(q.point - pair[0].point).length() < gap
&& (q.point - pair[1].point).length() < gap
});
if let Some(q) = split {
grown.push(q);
grown_at.extend([[stray, last], [stray, last]]);
budget -= 1;
} else {
grown_at.push([0.0; 2]);
}
}
grown.push(chain[chain.len() - 1]);
if grown.len() == chain.len() {
return Ok(curve);
}
*chain = grown;
split_at = grown_at;
}
}
#[must_use]
pub fn chain_intersection_points(
points: &[IntersectionPoint],
threshold: f64,
) -> Vec<Vec<IntersectionPoint>> {
if points.is_empty() {
return Vec::new();
}
let n = points.len();
let threshold_sq = threshold * threshold;
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for i in 0..n {
for j in (i + 1)..n {
let d = points[i].point - points[j].point;
if d.x().mul_add(d.x(), d.y().mul_add(d.y(), d.z() * d.z())) < threshold_sq {
adj[i].push(j);
adj[j].push(i);
}
}
}
let mut visited = vec![false; n];
let mut components: Vec<Vec<usize>> = Vec::new();
for start in 0..n {
if visited[start] {
continue;
}
let mut component = Vec::new();
let mut queue = std::collections::VecDeque::new();
queue.push_back(start);
visited[start] = true;
while let Some(idx) = queue.pop_front() {
component.push(idx);
for &neighbor in &adj[idx] {
if !visited[neighbor] {
visited[neighbor] = true;
queue.push_back(neighbor);
}
}
}
components.push(component);
}
let mut chains = Vec::with_capacity(components.len());
for comp in &components {
if comp.is_empty() {
continue;
}
let start_idx = comp
.iter()
.copied()
.min_by_key(|&i| adj[i].iter().filter(|&&j| comp.contains(&j)).count())
.unwrap_or(comp[0]);
let mut chain = Vec::with_capacity(comp.len());
let mut used = vec![false; n];
let mut current = start_idx;
used[current] = true;
chain.push(points[current]);
for _ in 1..comp.len() {
let mut best_dist = f64::MAX;
let mut best_idx = None;
for &idx in comp {
if used[idx] {
continue;
}
let d = points[current].point - points[idx].point;
let dist_sq = d.x().mul_add(d.x(), d.y().mul_add(d.y(), d.z() * d.z()));
if dist_sq < best_dist {
best_dist = dist_sq;
best_idx = Some(idx);
}
}
if let Some(next) = best_idx {
used[next] = true;
chain.push(points[next]);
current = next;
} else {
break;
}
}
chains.push(chain);
}
chains
}