use super::{
BLANK_HALF_EXTENT, EPS_FEAS, LEVEL_TOL, MIN_TRIPLE_DET, MeetTierInput, blocks::tier_sides,
};
use glam::DVec3;
#[derive(Clone, Copy)]
pub(super) struct SolvePlane {
pub(super) n: DVec3,
pub(super) m: f64,
pub(super) owner: usize,
}
pub(super) struct CandidateVertex {
pub(super) v: DVec3,
pub(super) violated: Option<usize>,
pub(super) owners: [usize; 3],
}
pub(super) fn tier_normals(
gear_teeth_abs: u32,
angle_deg: f64,
indices: &[f64],
is_crown: bool,
) -> Vec<DVec3> {
let theta = angle_deg.abs().to_radians();
let (sin_t, cos_t) = (theta.sin(), theta.cos());
let y = if is_crown { cos_t } else { -cos_t };
let gear = f64::from(gear_teeth_abs.max(1));
if indices.is_empty() {
return vec![DVec3::new(0.0, y, sin_t)];
}
indices
.iter()
.map(|&idx| {
let phi = 2.0 * std::f64::consts::PI * idx / gear;
DVec3::new(sin_t * phi.cos(), y, sin_t * phi.sin())
})
.collect()
}
#[must_use]
pub fn tier_instance_normals(gear_teeth_abs: u32, tiers: &[MeetTierInput]) -> Vec<Vec<DVec3>> {
let sides = tier_sides(tiers);
tiers
.iter()
.zip(&sides)
.map(|(t, &crown)| tier_normals(gear_teeth_abs, t.angle_deg, &t.indices, crown))
.collect()
}
pub(super) fn blank_planes() -> Vec<SolvePlane> {
[
DVec3::X,
DVec3::NEG_X,
DVec3::Y,
DVec3::NEG_Y,
DVec3::Z,
DVec3::NEG_Z,
]
.into_iter()
.map(|n| SolvePlane {
n,
m: BLANK_HALF_EXTENT,
owner: usize::MAX,
})
.collect()
}
fn flush_candidate_batch(
batch: &crate::simd::TripleBatch,
soa: &crate::simd::PlanesSoA64,
owner_meta: &[[usize; 3]; crate::simd::TRIPLE_LANES],
out: &mut Vec<CandidateVertex>,
) {
let sol = crate::simd::solve_triple_batch(batch);
for (lane, owners) in owner_meta.iter().enumerate().take(batch.len) {
if sol.det[lane].abs() < MIN_TRIPLE_DET {
continue;
}
let v = DVec3::new(sol.vx[lane], sol.vy[lane], sol.vz[lane]);
if v.x.abs() > BLANK_HALF_EXTENT + 1.0
|| v.y.abs() > BLANK_HALF_EXTENT + 1.0
|| v.z.abs() > BLANK_HALF_EXTENT + 1.0
{
continue;
}
match crate::simd::classify_feasibility(soa, v, EPS_FEAS) {
crate::simd::Feasibility::Dead => {}
crate::simd::Feasibility::Ok(violated) => {
out.push(CandidateVertex {
v,
violated: violated.map(|o| o as usize),
owners: *owners,
});
}
}
}
}
pub(super) fn enumerate_candidate_vertices_cancellable(
planes: &[SolvePlane],
first_real: usize,
cancel: Option<&std::sync::atomic::AtomicBool>,
) -> Option<Vec<CandidateVertex>> {
let mut soa = crate::simd::PlanesSoA64::with_capacity(planes.len());
for q in planes {
let owner = if q.owner == usize::MAX {
crate::simd::BLANK_OWNER
} else {
q.owner as u32
};
soa.push(q.n, q.m, owner);
}
let p = planes.len();
let mut out = Vec::new();
let mut batch = crate::simd::TripleBatch::default();
let mut owner_meta = [[0usize; 3]; crate::simd::TRIPLE_LANES];
for a in first_real..p {
if cancel.is_some_and(|c| c.load(std::sync::atomic::Ordering::Relaxed)) {
return None;
}
for b in (a + 1)..p {
if cancel.is_some_and(|c| c.load(std::sync::atomic::Ordering::Relaxed)) {
return None;
}
for c in (b + 1)..p {
let (pa, pb, pc) = (planes[a], planes[b], planes[c]);
owner_meta[batch.len] = [pa.owner, pb.owner, pc.owner];
if batch.push((pa.n, pa.m), (pb.n, pb.m), (pc.n, pc.m)) {
flush_candidate_batch(&batch, &soa, &owner_meta, &mut out);
batch = crate::simd::TripleBatch::default();
}
}
}
}
if batch.len > 0 {
flush_candidate_batch(&batch, &soa, &owner_meta, &mut out);
}
Some(out)
}
pub(super) fn sort_total_order(vals: &mut [f64]) {
vals.sort_unstable_by(f64::total_cmp);
}
pub(super) fn group_levels(mut vals: Vec<f64>) -> Vec<f64> {
sort_total_order(&mut vals);
let mut levels: Vec<f64> = Vec::new();
for &v in vals.iter().rev() {
match levels.last() {
Some(&head) if (head - v).abs() <= LEVEL_TOL => {}
_ => levels.push(v),
}
}
levels
}
pub(super) fn filter_levels_by_instance_support(
levels: Vec<f64>,
normals_i: &[DVec3],
usable: &[&CandidateVertex],
) -> Vec<f64> {
if normals_i.len() < 2 || levels.is_empty() {
return levels;
}
let sorted_per_instance: Vec<Vec<f64>> = normals_i[1..]
.iter()
.map(|&nj| {
let mut vals: Vec<f64> = usable.iter().map(|c| nj.dot(c.v)).collect();
sort_total_order(&mut vals);
vals
})
.collect();
let tol = 4.0 * LEVEL_TOL;
let supported: Vec<f64> = levels
.iter()
.copied()
.filter(|&head| {
sorted_per_instance
.iter()
.all(|vals| any_within_tol(vals, head, tol))
})
.collect();
if supported.is_empty() {
levels
} else {
supported
}
}
fn any_within_tol(sorted: &[f64], target: f64, tol: f64) -> bool {
let lower = target - tol;
let idx = sorted.partition_point(|v| v.total_cmp(&lower).is_lt());
idx < sorted.len() && sorted[idx].total_cmp(&(target + tol)).is_le()
}
#[cfg(test)]
mod tests {
use super::*;
fn lcg(state: &mut u64) -> f64 {
*state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1);
(((*state >> 33) as f64) / f64::from(u32::MAX)).mul_add(2.0, -1.0)
}
fn sample_normals() -> Vec<Vec<DVec3>> {
vec![
tier_normals(96, 90.0, &[0.0, 24.0, 48.0, 72.0], true), tier_normals(96, 41.0, &[12.0, 36.0, 60.0, 84.0], true),
tier_normals(96, 25.0, &[16.0, 32.0, 48.0, 64.0, 80.0], true),
tier_normals(96, 0.0, &[], true), tier_normals(96, -41.0, &[12.0, 36.0, 60.0, 84.0], false),
tier_normals(96, -30.0, &[4.0, 20.0, 44.0, 68.0, 92.0], false),
tier_normals(96, -90.0, &[], false), ]
}
fn arrangement(normals: &[Vec<DVec3>], mast: &[f64]) -> Vec<SolvePlane> {
let mut planes = blank_planes();
for (i, ns) in normals.iter().enumerate() {
for &nv in ns {
planes.push(SolvePlane {
n: nv,
m: mast[i],
owner: i,
});
}
}
planes
}
fn filter_levels_by_instance_support_linear(
levels: &[f64],
normals_i: &[DVec3],
usable: &[&CandidateVertex],
) -> Vec<f64> {
if normals_i.len() < 2 || levels.is_empty() {
return levels.to_vec();
}
let supported: Vec<f64> = levels
.iter()
.copied()
.filter(|&head| {
normals_i[1..].iter().all(|&nj| {
usable
.iter()
.any(|c| (nj.dot(c.v) - head).abs() <= 4.0 * LEVEL_TOL)
})
})
.collect();
if supported.is_empty() {
levels.to_vec()
} else {
supported
}
}
#[test]
fn sort_total_order_matches_std_total_cmp_sort() {
let mut state = 7u64;
for &len in &[0usize, 1, 2, 255, 256, 257, 1000, 20_000, 90_000] {
let mut vals: Vec<f64> = (0..len)
.map(|k| match k % 7 {
0 => lcg(&mut state) * 1e-9,
1 => -lcg(&mut state).abs(),
2 => 0.0,
3 => -0.0,
4 => lcg(&mut state) * 300.0,
5 => (lcg(&mut state) * 3.0).mul_add(
LEVEL_TOL,
(lcg(&mut state).abs() * 40.0).floor().mul_add(0.05, 0.1),
),
_ => lcg(&mut state).abs() + 0.5,
})
.collect();
let mut want = vals.clone();
want.sort_by(f64::total_cmp);
sort_total_order(&mut vals);
assert_eq!(want.len(), vals.len());
for (w, g) in want.iter().zip(&vals) {
assert_eq!(w.to_bits(), g.to_bits(), "len {len}");
}
}
}
#[test]
fn group_levels_matches_comparison_sort_reference() {
let mut state = 3u64;
for _ in 0..20 {
let vals: Vec<f64> = (0..5000)
.map(|_| {
let base = (lcg(&mut state).abs() * 40.0).floor().mul_add(0.05, 0.1);
(lcg(&mut state) * 3.0).mul_add(LEVEL_TOL, base)
})
.collect();
let mut sorted = vals.clone();
sorted.sort_by(|x, y| y.partial_cmp(x).unwrap_or(std::cmp::Ordering::Equal));
let mut want: Vec<f64> = Vec::new();
for v in sorted {
match want.last() {
Some(&head) if (head - v).abs() <= LEVEL_TOL => {}
_ => want.push(v),
}
}
let got = group_levels(vals);
assert_eq!(want.len(), got.len());
for (w, g) in want.iter().zip(&got) {
assert_eq!(w.to_bits(), g.to_bits());
}
}
}
#[test]
fn filter_levels_by_instance_support_matches_linear_reference() {
let mut state = 99u64;
for _case in 0..200 {
let n_normals = 1 + (lcg(&mut state).abs() * 4.0) as usize; let normals_i: Vec<DVec3> = (0..n_normals)
.map(|_| DVec3::new(lcg(&mut state), lcg(&mut state), lcg(&mut state)).normalize())
.collect();
let owned_verts: Vec<CandidateVertex> = (0..20)
.map(|_| CandidateVertex {
v: DVec3::new(lcg(&mut state), lcg(&mut state), lcg(&mut state)) * 2.0,
violated: None,
owners: [0, 1, 2],
})
.collect();
let usable: Vec<&CandidateVertex> = owned_verts.iter().collect();
let levels: Vec<f64> = {
let mut v: Vec<f64> = (0..8).map(|_| lcg(&mut state) * 2.0).collect();
v.sort_by(|x, y| y.partial_cmp(x).unwrap());
v
};
let want = filter_levels_by_instance_support_linear(&levels, &normals_i, &usable);
let got = filter_levels_by_instance_support(levels, &normals_i, &usable);
assert_eq!(want, got);
}
}
#[test]
fn filter_levels_by_instance_support_matches_linear_reference_on_real_arrangement() {
let normals = sample_normals();
let mast: Vec<f64> = normals.iter().map(|_| 1.0).collect();
let cands =
enumerate_candidate_vertices_cancellable(&arrangement(&normals, &mast), 6, None)
.expect("no cancel token: never cancels");
for (i, normals_i) in normals.iter().enumerate() {
let usable: Vec<&CandidateVertex> = cands
.iter()
.filter(|c| {
!c.owners.contains(&i) && (c.violated.is_none() || c.violated == Some(i))
})
.collect();
let n0 = normals_i[0];
let levels = group_levels(
usable
.iter()
.map(|c| n0.dot(c.v))
.filter(|v| *v > 1e-9)
.collect(),
);
let want = filter_levels_by_instance_support_linear(&levels, normals_i, &usable);
let got = filter_levels_by_instance_support(levels, normals_i, &usable);
assert_eq!(want, got, "tier {i}");
}
}
#[test]
fn batched_enumeration_matches_glam_reference_bitwise() {
let normals = sample_normals();
let mut state = 11u64;
for round in 0..4 {
let mast: Vec<f64> = (0..normals.len())
.map(|_| 0.6f64.mul_add(lcg(&mut state).abs(), 0.4))
.collect();
let planes = arrangement(&normals, &mast);
let mut soa = crate::simd::PlanesSoA64::with_capacity(planes.len());
for q in &planes {
let owner = if q.owner == usize::MAX {
crate::simd::BLANK_OWNER
} else {
q.owner as u32
};
soa.push(q.n, q.m, owner);
}
let mut want: Vec<CandidateVertex> = Vec::new();
let p = planes.len();
for a in 6..p {
for b in (a + 1)..p {
for c in (b + 1)..p {
let (pa, pb, pc) = (planes[a], planes[b], planes[c]);
let m = glam::DMat3::from_cols(pa.n, pb.n, pc.n).transpose();
if m.determinant().abs() < MIN_TRIPLE_DET {
continue;
}
let v = m.inverse() * DVec3::new(pa.m, pb.m, pc.m);
if v.x.abs() > BLANK_HALF_EXTENT + 1.0
|| v.y.abs() > BLANK_HALF_EXTENT + 1.0
|| v.z.abs() > BLANK_HALF_EXTENT + 1.0
{
continue;
}
match crate::simd::classify_feasibility(&soa, v, EPS_FEAS) {
crate::simd::Feasibility::Dead => {}
crate::simd::Feasibility::Ok(violated) => {
want.push(CandidateVertex {
v,
violated: violated.map(|o| o as usize),
owners: [pa.owner, pb.owner, pc.owner],
});
}
}
}
}
}
let got = enumerate_candidate_vertices_cancellable(&planes, 6, None)
.expect("no cancel token: never cancels");
assert_eq!(
want.len(),
got.len(),
"candidate count mismatch, round {round}"
);
for (w, g) in want.iter().zip(&got) {
assert_eq!(w.v.x.to_bits(), g.v.x.to_bits());
assert_eq!(w.v.y.to_bits(), g.v.y.to_bits());
assert_eq!(w.v.z.to_bits(), g.v.z.to_bits());
assert_eq!(w.violated, g.violated);
assert_eq!(w.owners, g.owners);
}
}
}
}