use super::*;
use super::chain::weld_chain;
use super::geometry::{ellipse_point, sample_bezier_polyline, Loop, SegKind, Segment};
pub(super) fn assemble_loops(segments: Vec<Segment>, plans: Vec<Vec<(usize, bool)>>) -> Vec<Loop> {
let mut slots: Vec<Option<Segment>> = segments.into_iter().map(Some).collect();
let mut loops = Vec::with_capacity(plans.len());
for plan in plans {
let mut entries = Vec::with_capacity(plan.len());
for (index, reversed) in plan {
let segment = slots[index]
.take()
.expect("each segment is claimed by exactly one loop");
entries.push((segment, reversed));
}
let polygon = sample_loop_polygon(&entries);
loops.push(Loop {
entries,
polygon,
loop_id: None,
});
}
loops
}
fn sample_loop_polygon(entries: &[(Segment, bool)]) -> Vec<[f64; 2]> {
let mut polygon = Vec::new();
for (segment, reversed) in entries {
let (from, to) = if *reversed {
(segment.b, segment.a)
} else {
(segment.a, segment.b)
};
polygon.push(from);
match &segment.kind {
SegKind::Arc { center, radius, .. } => {
let a0 = (from[1] - center[1]).atan2(from[0] - center[0]);
let a1 = (to[1] - center[1]).atan2(to[0] - center[0]);
let mut delta = a1 - a0;
while delta > PI {
delta -= TAU;
}
while delta < -PI {
delta += TAU;
}
for step in 1..4 {
let angle = a0 + delta * (step as f64 / 4.0);
polygon.push([
center[0] + radius * angle.cos(),
center[1] + radius * angle.sin(),
]);
}
}
SegKind::Bezier { controls } => {
let mut samples = sample_bezier_polyline(controls, 8);
samples.remove(0);
samples.pop();
if *reversed {
samples.reverse();
}
polygon.extend(samples);
}
SegKind::EllipseHalf {
center,
major,
minor,
upper,
} => {
let base = if *upper { PI } else { 0.0 };
let mut samples: Vec<[f64; 2]> = (1..8)
.map(|step| {
ellipse_point(*center, *major, *minor, base + PI * (step as f64 / 8.0))
})
.collect();
if *reversed {
samples.reverse();
}
polygon.extend(samples);
}
SegKind::Line => {}
}
}
polygon
}
pub(super) fn classify_regions(loops: Vec<Loop>) -> Result<Vec<Vec<Loop>>, String> {
let count = loops.len();
if count == 1 {
return Ok(vec![loops]);
}
let representative: Vec<[f64; 2]> = loops
.iter()
.map(|l| l.polygon.first().copied().unwrap_or([0.0, 0.0]))
.collect();
let mut contains = vec![vec![false; count]; count];
for i in 0..count {
for j in 0..count {
if i != j && point_in_polygon(representative[i], &loops[j].polygon) {
contains[i][j] = true;
}
}
}
let depth: Vec<usize> = (0..count)
.map(|i| contains[i].iter().filter(|inside| **inside).count())
.collect();
let mut plans: Vec<(usize, Vec<usize>)> = (0..count)
.filter(|&i| depth[i] % 2 == 0)
.map(|i| (i, Vec::new()))
.collect();
for hole in 0..count {
if depth[hole] % 2 == 0 {
continue;
}
let parent = (0..count)
.find(|&j| contains[hole][j] && depth[j] + 1 == depth[hole])
.ok_or("sketch: loop containment is inconsistent (crossing loops?)")?;
plans
.iter_mut()
.find(|(outer, _)| *outer == parent)
.expect("a hole's direct parent is depth-even, so it opened a region")
.1
.push(hole);
}
let mut slots: Vec<Option<Loop>> = loops.into_iter().map(Some).collect();
let mut regions = Vec::with_capacity(plans.len());
for (outer, holes) in plans {
let mut region = Vec::with_capacity(holes.len() + 1);
region.push(slots[outer].take().expect("each loop lands in one region"));
for hole in holes {
region.push(slots[hole].take().expect("each loop lands in one region"));
}
regions.push(region);
}
Ok(regions)
}
fn point_in_polygon(point: [f64; 2], polygon: &[[f64; 2]]) -> bool {
polygon.len() >= 3 && crate::polygon::parity_inside(point, polygon.len(), |i| polygon[i])
}
pub(super) fn emit_loop(loop_data: &Loop, frame: &Frame) -> Result<ProfileLoop, String> {
let mut curves = Vec::with_capacity(loop_data.entries.len());
let mut edge_names = Vec::with_capacity(loop_data.entries.len());
for (segment, reversed) in &loop_data.entries {
curves.push(segment.to_curve(frame, *reversed)?);
edge_names.push(segment.name.clone());
}
weld_chain(&mut curves, true);
if curves.len() == 1 {
let [start, end] = curves[0].domain()?;
let (first, second) = curves[0].split((start + end) * 0.5)?;
curves = vec![first, second];
edge_names.push(edge_names[0].clone());
}
Ok(ProfileLoop {
curves,
edge_names,
loop_id: loop_data.loop_id,
})
}