use {
super::*,
crate::{
geometry::{DistPoint, Point, Vector, VectorExt, P2},
line_search::LineSearch,
sdf,
},
rand::prelude::*,
};
fn ball<const D: usize>(
centre: Point<f64, D>,
radius: f64,
) -> impl Fn(Point<f64, D>) -> f64 + Send + Sync + 'static {
move |p| (p - centre).length() - radius
}
fn climb<const D: usize>(
f: impl Fn(Point<f64, D>) -> f64,
rng: &mut impl Rng,
) -> DistPoint<f64, f64, D> {
let start = Point::from(
Vector::<f64, D>::from_fn(|_, _| rng.random_range(0.0..1.0)));
let point = LineSearch::default().optimize(&f, start);
DistPoint { distance: f(point), point }
}
#[test] fn sphere_packing_3d() {
let mut adf = ADF::<f64, 3>::new(4, vec![Primitive::new(sdf::boundary_rect)]);
let mut rng = rand_pcg::Pcg64::seed_from_u64(0);
let mut spheres: Vec<(Point<f64, 3>, f64)> = vec![];
let mut attempts = 0;
while spheres.len() < 100 {
attempts += 1;
assert!(attempts < 10_000, "field saturated after {} spheres", spheres.len());
let peak = climb(|p| adf.sdf(p), &mut rng);
if peak.distance <= 1e-9 { continue; }
let r = peak.distance / 2.0;
if adf.insert_at_maximum(peak, Primitive::new(ball(peak.point, r))) {
spheres.push((peak.point, r));
}
}
for i in 0..spheres.len() {
for j in i + 1..spheres.len() {
let ((c1, r1), (c2, r2)) = (spheres[i], spheres[j]);
let gap = (c1 - c2).length() - (r1 + r2);
assert!(gap > -1e-9, "spheres {i} and {j} intersect by {:.3e}", -gap);
}
}
for &(c, r) in &spheres {
assert!((adf.sdf(c) + r).abs() < 1e-9);
}
}
#[test] fn insertion_domain() {
let x0 = P2::new(0.5, 0.75);
let d = 0.05;
let full = Aabb::unit();
let mut adf = ADF::<f64, 2>::new(7, vec![Primitive::new(sdf::boundary_rect)]);
for angle in [90f64, 210., 330.] {
let (s, c) = angle.to_radians().sin_cos();
let obstacle = ball(P2::new(x0.x + c * d, x0.y + s * d), 1e-4);
adf.insert_sdf_domain(full, Arc::new(obstacle));
}
let local_max = DistPoint { point: x0, distance: adf.sdf(x0) };
let r = 0.01;
let placed = P2::new(x0.x, x0.y - (local_max.distance - r));
let f: Arc<dyn Fn(P2<f64>) -> f64 + Send + Sync> = Arc::new(ball(placed, r));
let probe = P2::new(0.48, 0.45); let truth = f(probe).min(adf.sdf(probe));
assert!(f(probe) < adf.sdf(probe), "the insertion must lower the field at the probe");
{ let mut adf = adf.clone();
let half = local_max.distance * 2.0 * 2f64.sqrt();
let empirical = Aabb::new(
P2::new(x0.x - half, x0.y - half),
P2::new(x0.x + half, x0.y + half));
adf.insert_sdf_domain(empirical, f.clone());
assert!(adf.sdf(probe) - truth > 0.02,
"expected the historical constant domain to corrupt the field");
}
{ let mut adf = adf.clone();
adf.insert_sdf_domain(adf.update_domain(local_max), f.clone());
assert!((adf.sdf(probe) - truth).abs() < 1e-12);
}
{ let mut adf = adf.clone();
adf.insert_at_maximum(local_max, Primitive { f: f.clone(), lipschitz: 1.0 });
assert!((adf.sdf(probe) - truth).abs() < 1e-12);
}
}