use super::{BLANK_HALF_EXTENT, EPS_FEAS, MIN_TRIPLE_DET};
use glam::DVec3;
struct PlaneRec {
n: DVec3,
m: f64,
owner: usize,
instance: usize,
}
pub(super) struct CachedCandidate {
pub(super) v: DVec3,
pub(super) violated: Option<usize>,
key: [(usize, usize); 3],
}
pub(super) struct Phase1Cache {
soa: crate::simd::PlanesSoA64,
real_planes: Vec<PlaneRec>,
pub(super) candidates: Vec<CachedCandidate>,
}
impl Phase1Cache {
pub(super) fn new() -> Self {
let mut soa = crate::simd::PlanesSoA64::with_capacity(6);
for n in [
DVec3::X,
DVec3::NEG_X,
DVec3::Y,
DVec3::NEG_Y,
DVec3::Z,
DVec3::NEG_Z,
] {
soa.push(n, BLANK_HALF_EXTENT, crate::simd::BLANK_OWNER);
}
Self {
soa,
real_planes: Vec::new(),
candidates: Vec::new(),
}
}
pub(super) fn add_tier(&mut self, owner: usize, normals_j: &[DVec3], mast: f64) {
let new_planes: Vec<PlaneRec> = normals_j
.iter()
.enumerate()
.map(|(instance, &n)| PlaneRec {
n,
m: mast,
owner,
instance,
})
.collect();
revalidate(&mut self.candidates, &new_planes, owner);
for p in &new_planes {
self.soa.push(p.n, p.m, owner as u32);
}
let mut new_candidates: Vec<CachedCandidate> = Vec::new();
let mut batch = crate::simd::TripleBatch::default();
let mut key_meta = [[(0usize, 0usize); 3]; crate::simd::TRIPLE_LANES];
let k = new_planes.len();
for ja in 0..k {
for jb in (ja + 1)..k {
for jc in (jb + 1)..k {
push_triple(
&mut batch,
&mut key_meta,
&new_planes[ja],
&new_planes[jb],
&new_planes[jc],
&self.soa,
&mut new_candidates,
);
}
}
}
for ja in 0..k {
for jb in (ja + 1)..k {
for o in &self.real_planes {
push_triple(
&mut batch,
&mut key_meta,
&new_planes[ja],
&new_planes[jb],
o,
&self.soa,
&mut new_candidates,
);
}
}
}
let old_len = self.real_planes.len();
for new_plane in &new_planes {
for ia in 0..old_len {
for ib in (ia + 1)..old_len {
push_triple(
&mut batch,
&mut key_meta,
new_plane,
&self.real_planes[ia],
&self.real_planes[ib],
&self.soa,
&mut new_candidates,
);
}
}
}
if batch.len > 0 {
flush_keyed(&batch, &self.soa, &key_meta, &mut new_candidates);
}
new_candidates.sort_by_key(|cc| cc.key);
self.candidates = merge_sorted(std::mem::take(&mut self.candidates), new_candidates);
self.real_planes.extend(new_planes);
}
}
fn revalidate(existing: &mut Vec<CachedCandidate>, new_planes: &[PlaneRec], owner: usize) {
if new_planes.is_empty() {
return;
}
let mut mini = crate::simd::PlanesSoA64::with_capacity(new_planes.len());
for p in new_planes {
mini.push(p.n, p.m, owner as u32);
}
existing.retain_mut(
|cc| match crate::simd::classify_feasibility(&mini, cc.v, EPS_FEAS) {
crate::simd::Feasibility::Dead => {
debug_assert!(
false,
"single-owner mini-scan against one tier's planes cannot classify Dead"
);
false
}
crate::simd::Feasibility::Ok(None) => true,
crate::simd::Feasibility::Ok(Some(_)) => match cc.violated {
None => {
cc.violated = Some(owner);
true
}
Some(prior) => prior == owner,
},
},
);
}
fn push_triple(
batch: &mut crate::simd::TripleBatch,
key_meta: &mut [[(usize, usize); 3]; crate::simd::TRIPLE_LANES],
p0: &PlaneRec,
p1: &PlaneRec,
p2: &PlaneRec,
soa: &crate::simd::PlanesSoA64,
out: &mut Vec<CachedCandidate>,
) {
let mut trio = [p0, p1, p2];
trio.sort_by_key(|p| (p.owner, p.instance));
let [a, b, c] = trio;
key_meta[batch.len] = [
(a.owner, a.instance),
(b.owner, b.instance),
(c.owner, c.instance),
];
if batch.push((a.n, a.m), (b.n, b.m), (c.n, c.m)) {
flush_keyed(batch, soa, key_meta, out);
*batch = crate::simd::TripleBatch::default();
}
}
fn flush_keyed(
batch: &crate::simd::TripleBatch,
soa: &crate::simd::PlanesSoA64,
key_meta: &[[(usize, usize); 3]; crate::simd::TRIPLE_LANES],
out: &mut Vec<CachedCandidate>,
) {
let sol = crate::simd::solve_triple_batch(batch);
for (lane, key) in key_meta.iter().enumerate().take(batch.len) {
let det_abs = sol.det[lane].abs();
if det_abs.is_nan() || det_abs < MIN_TRIPLE_DET {
continue;
}
let v = DVec3::new(sol.vx[lane], sol.vy[lane], sol.vz[lane]);
if !v.is_finite() {
continue;
}
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(CachedCandidate {
v,
violated: violated.map(|o| o as usize),
key: *key,
});
}
}
}
}
fn merge_sorted(a: Vec<CachedCandidate>, b: Vec<CachedCandidate>) -> Vec<CachedCandidate> {
let mut out = Vec::with_capacity(a.len() + b.len());
let mut a = a.into_iter();
let mut b = b.into_iter();
let mut next_a = a.next();
let mut next_b = b.next();
loop {
match (next_a.take(), next_b.take()) {
(Some(x), Some(y)) => {
if x.key <= y.key {
next_b = Some(y);
out.push(x);
next_a = a.next();
} else {
next_a = Some(x);
out.push(y);
next_b = b.next();
}
}
(Some(x), None) => {
out.push(x);
out.extend(a);
break;
}
(None, Some(y)) => {
out.push(y);
out.extend(b);
break;
}
(None, None) => break,
}
}
out
}
#[cfg(test)]
mod tests {
use super::{CachedCandidate, Phase1Cache};
use crate::geometry::meet_solver::{
MeetConstraint, MeetTierInput,
candidates::{
CandidateVertex, SolvePlane, blank_planes, enumerate_candidate_vertices_cancellable,
},
tier_instance_normals,
};
use glam::DVec3;
fn tier(angle_deg: f64, first_index: f64, count: u32) -> MeetTierInput {
MeetTierInput {
angle_deg,
indices: (0..count)
.map(|k| f64::from(k).mul_add(12.0, first_index))
.collect(),
constraint: MeetConstraint::MeetExisting,
names: Vec::new(),
}
}
fn synthetic_schedule() -> Vec<(MeetTierInput, f64)> {
vec![
(tier(90.0, 0.0, 8), 1.0),
(tier(0.0, 0.0, 0), 0.55),
(tier(40.0, 6.0, 8), 0.96),
(tier(25.0, 0.0, 8), 0.86),
(tier(-41.0, 0.0, 8), 1.0),
(tier(-38.0, 6.0, 8), 0.97),
(tier(70.0, 6.0, 8), 0.99),
(tier(-60.0, 6.0, 8), 0.98),
]
}
fn fresh_enumeration(
normals: &[Vec<DVec3>],
masts: &[f64],
settled: &[bool],
) -> Vec<CandidateVertex> {
let mut planes = blank_planes();
let first_real = planes.len();
for (owner, ((tier_normals, &m), &is_settled)) in
normals.iter().zip(masts).zip(settled).enumerate()
{
if is_settled {
planes.extend(tier_normals.iter().map(|&n| SolvePlane { n, m, owner }));
}
}
enumerate_candidate_vertices_cancellable(&planes, first_real, None)
.expect("no cancel flag was passed")
}
fn vertex_bits(v: DVec3) -> [u64; 3] {
[v.x.to_bits(), v.y.to_bits(), v.z.to_bits()]
}
fn assert_same(step: usize, cached: &[CachedCandidate], fresh: &[CandidateVertex]) {
assert_eq!(cached.len(), fresh.len(), "step {step}: candidate count");
for (i, (c, f)) in cached.iter().zip(fresh).enumerate() {
assert_eq!(
vertex_bits(c.v),
vertex_bits(f.v),
"step {step}, candidate {i}: vertex bits"
);
assert_eq!(
c.violated, f.violated,
"step {step}, candidate {i}: violator"
);
assert_eq!(
c.key.map(|(tier, _)| tier),
f.owners,
"step {step}, candidate {i}: defining tiers"
);
}
}
#[test]
fn cache_equals_a_fresh_enumeration_after_every_settle() {
let schedule = synthetic_schedule();
let tiers: Vec<MeetTierInput> = schedule.iter().map(|(t, _)| t.clone()).collect();
let masts: Vec<f64> = schedule.iter().map(|&(_, m)| m).collect();
let normals = tier_instance_normals(96, &tiers);
for order in [[0, 1, 2, 3, 4, 5, 6, 7], [0, 2, 1, 5, 4, 7, 3, 6]] {
let mut cache = Phase1Cache::new();
let mut settled = vec![false; tiers.len()];
for (step, &tier_index) in order.iter().enumerate() {
cache.add_tier(tier_index, &normals[tier_index], masts[tier_index]);
settled[tier_index] = true;
let fresh = fresh_enumeration(&normals, &masts, &settled);
assert_same(step, &cache.candidates, &fresh);
}
assert!(
cache.candidates.iter().any(|c| c.violated.is_none()),
"the closed synthetic stone must have feasible vertices"
);
}
}
}