use std::collections::HashMap;
use core::ops::Range;
use axiolid_contracts::GeomResult;
use axiolid_core::Point2;
use super::validate::{check_edges, folds_back, name, refuse, Touch};
use super::{by_value, orient, pinch, segments_touch, within, PinchPolicy};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct Seam {
pub(super) ring: usize,
pub(super) open: usize,
pub(super) close: usize,
}
type Key = (u64, u64);
fn key(p: Point2) -> Key {
((p.x + 0.0).to_bits(), (p.y + 0.0).to_bits())
}
pub(super) fn find(points: &[Point2], rings: &[Range<usize>]) -> Vec<Seam> {
type Use = (usize, usize, bool);
let mut uses: HashMap<(Key, Key), Vec<Use>> = HashMap::new();
for (r, ring) in rings.iter().enumerate() {
let n = ring.len();
for k in 0..n {
let (a, b) = (
key(points[ring.start + k]),
key(points[ring.start + (k + 1) % n]),
);
let forward = a < b;
let segment = if forward { (a, b) } else { (b, a) };
uses.entry(segment).or_default().push((r, k, forward));
}
}
let mut pairs: Vec<Seam> = uses
.into_values()
.filter_map(|list| match list[..] {
[(r, i, fi), (s, j, fj)] if r == s && fi != fj => Some(Seam {
ring: r,
open: i.min(j),
close: i.max(j),
}),
_ => None,
})
.collect();
pairs.sort_unstable_by_key(|s| (s.ring, s.open));
let mut good = vec![true; pairs.len()];
let mut events: Vec<(usize, usize, usize)> = Vec::with_capacity(2 * pairs.len());
for (id, seam) in pairs.iter().enumerate() {
events.push((seam.ring, seam.open, id));
events.push((seam.ring, seam.close, id));
}
events.sort_unstable();
let mut stack: Vec<usize> = Vec::new();
let mut ring = usize::MAX;
for (r, k, id) in events {
if r != ring {
ring = r;
stack.clear();
}
if pairs[id].open == k {
stack.push(id);
continue;
}
let Some(at) = stack.iter().rposition(|&other| other == id) else {
continue;
};
if at + 1 != stack.len() {
for &other in &stack[at..] {
good[other] = false;
}
}
stack.remove(at);
}
pairs
.into_iter()
.zip(good)
.filter_map(|(seam, good)| good.then_some(seam))
.collect()
}
pub(super) fn refuse_for_solid<T>(seam: &Seam) -> GeomResult<T> {
refuse(format!(
"profile {} runs along a seam both ways (edges {} and {}), which a solid cannot extrude",
name(seam.ring),
seam.open,
seam.close
))
}
pub(super) fn triangulate(
points: &[Point2],
rings: &[Range<usize>],
seams: &[Seam],
) -> GeomResult<Vec<[u32; 3]>> {
let (parts, touches) = validated_parts(points, rings, seams)?;
let identity = |n: usize| (0..n as u32).collect::<Vec<u32>>();
let canon =
pinch::canonical(&parts.points, &touches).unwrap_or_else(|| identity(parts.points.len()));
let triangles =
pinch::triangulate(&parts.points, &parts.rings, &parts.source, &canon, &touches)?;
let first = pinch::canonical(points, &[]).unwrap_or_else(|| identity(points.len()));
Ok(triangles
.into_iter()
.map(|t| t.map(|v| first[parts.back[v as usize] as usize]))
.collect())
}
pub(super) fn ends(points: &[Point2], rings: &[Range<usize>], seams: &[Seam]) -> Vec<[u32; 2]> {
let first = pinch::canonical(points, &[])
.unwrap_or_else(|| (0..points.len() as u32).collect::<Vec<u32>>());
seams
.iter()
.map(|seam| {
let ring = &rings[seam.ring];
let at = |k: usize| first[ring.start + k % ring.len()];
[at(seam.open), at(seam.open + 1)]
})
.collect()
}
fn validated_parts(
points: &[Point2],
rings: &[Range<usize>],
seams: &[Seam],
) -> GeomResult<(Parts, Vec<Touch>)> {
check_clear(points, rings, seams)?;
let parts = split(points, rings, seams)?;
let touches = check_edges(
&parts.points,
&parts.rings,
&parts.source,
PinchPolicy::Accept,
)?;
Ok((parts, touches))
}
pub(super) fn touches(
points: &[Point2],
rings: &[Range<usize>],
seams: &[Seam],
) -> GeomResult<Vec<(usize, usize, usize)>> {
let (parts, touches) = validated_parts(points, rings, seams)?;
Ok(touches
.into_iter()
.map(|touch| {
let source = parts.source[touch.ring];
let start = parts.back[parts.rings[touch.ring].start + touch.index] as usize;
(
source,
start - rings[source].start,
parts.back[touch.vertex as usize] as usize,
)
})
.collect())
}
fn roles(seams: &[Seam]) -> HashMap<(usize, usize), bool> {
let mut role = HashMap::with_capacity(2 * seams.len());
for seam in seams {
role.insert((seam.ring, seam.open), true);
role.insert((seam.ring, seam.close), false);
}
role
}
pub(super) fn check_clear(
points: &[Point2],
rings: &[Range<usize>],
seams: &[Seam],
) -> GeomResult<()> {
let role = roles(seams);
let mut segments: Vec<(Point2, Point2, usize, bool)> = Vec::with_capacity(points.len());
for (r, ring) in rings.iter().enumerate() {
let n = ring.len();
for k in 0..n {
let is_seam = match role.get(&(r, k)) {
Some(false) => continue,
Some(true) => true,
None => false,
};
segments.push((
points[ring.start + k],
points[ring.start + (k + 1) % n],
r,
is_seam,
));
}
}
segments.sort_by(|e, f| by_value(e.0.x.min(e.1.x), f.0.x.min(f.1.x)));
for (i, e) in segments.iter().enumerate() {
let right = e.0.x.max(e.1.x);
let (low, high) = (e.0.y.min(e.1.y), e.0.y.max(e.1.y));
for f in &segments[i + 1..] {
if f.0.x.min(f.1.x) > right {
break;
}
if !(e.3 || f.3) || f.0.y.max(f.1.y) < low || f.0.y.min(f.1.y) > high {
continue;
}
let (seam, other) = if e.3 { (e, f) } else { (f, e) };
let clear = meets_only_at_ends(seam.0, seam.1, other.0, other.1)
&& (!other.3 || meets_only_at_ends(other.0, other.1, seam.0, seam.1));
if !clear {
let what = if seam.2 == other.2 {
"itself".to_owned()
} else {
name(other.2)
};
return refuse(format!(
"profile {} has a seam from ({}, {}) to ({}, {}) that crosses or overlaps {what}",
name(seam.2),
seam.0.x,
seam.0.y,
seam.1.x,
seam.1.y
));
}
}
}
Ok(())
}
fn meets_only_at_ends(a: Point2, b: Point2, c: Point2, d: Point2) -> bool {
if !segments_touch(a, b, c, d) {
return true;
}
if orient(a, b, c) == 0 && orient(a, b, d) == 0 {
for (p, far_seam) in [(a, b), (b, a)] {
for (q, far_other) in [(c, d), (d, c)] {
if p == q {
return far_seam != far_other && !folds_back(far_seam, p, far_other);
}
}
}
return false;
}
let on = |p: Point2| orient(c, d, p) == 0 && within(c, d, p);
on(a) || on(b)
}
#[derive(Debug, Clone)]
pub(super) struct Parts {
pub(super) points: Vec<Point2>,
pub(super) rings: Vec<Range<usize>>,
pub(super) source: Vec<usize>,
pub(super) back: Vec<u32>,
}
pub(super) fn split(
points: &[Point2],
rings: &[Range<usize>],
seams: &[Seam],
) -> GeomResult<Parts> {
let role = roles(seams);
let mut parts = Parts {
points: Vec::with_capacity(points.len()),
rings: Vec::new(),
source: Vec::new(),
back: Vec::with_capacity(points.len()),
};
for (r, ring) in rings.iter().enumerate() {
let n = ring.len();
let mut open: Vec<(usize, Vec<usize>)> = vec![(n, Vec::new())];
let mut closed: Vec<Vec<usize>> = Vec::new();
for k in 0..n {
match role.get(&(r, k)) {
Some(true) => open.push((k, Vec::new())),
Some(false) => {
let Some((first, edges)) = open.pop() else {
continue;
};
if edges.is_empty() {
return refuse(format!(
"profile {} has seams in a row through vertex {}",
name(r),
(first + 1) % n
));
}
closed.push(edges);
}
None => {
if let Some((_, top)) = open.last_mut() {
top.push(k);
}
}
}
}
closed.extend(open.into_iter().map(|(_, edges)| edges));
for edges in closed {
if edges.len() < 3 {
return refuse(format!(
"profile {} splits at a seam into a loop of {} vertices",
name(r),
edges.len()
));
}
let m = edges.len();
for (i, &k) in edges.iter().enumerate() {
let at = |j: usize| points[ring.start + edges[(i + j) % m]];
if folds_back(at(m - 1), at(0), at(1)) {
return refuse(format!(
"profile {} folds back on itself at vertex {k} once its seam is removed",
name(r)
));
}
}
let start = parts.points.len();
for k in edges {
parts.points.push(points[ring.start + k]);
parts.back.push((ring.start + k) as u32);
}
parts.rings.push(start..parts.points.len());
parts.source.push(r);
}
}
Ok(parts)
}