use crate::{
utils::{AABB, Circle, CollisionParameters, generate_circles},
vector::Vector,
};
pub struct CollisionResult<const N: usize, V: Vector<N>> {
pub point: V,
pub gradient: V,
}
pub trait SDF<const N: usize, V: Vector<N>> {
fn dist(&self, p: V) -> f32;
fn aabb(&self) -> AABB<N, V>;
fn gradient(&self, p: V, params: &CollisionParameters) -> V {
let mut res = [0.0; N];
let k = self.dist(p);
for i in 0..N {
let mut a = [0.0; N];
a[i] = params.normal_epsilon;
res[i] = k - self.dist(p.add_self(&V::from_arr(a)));
}
(V::from_arr(res).scale(-1.0)).normalized()
}
fn combined<T: SDF<N, V>>(&self, other: &T, p: V, params: &CollisionParameters) -> (f32, V) {
let d1 = self.dist(p);
let d2 = other.dist(p);
let g1 = self.gradient(p, params);
let g2 = other.gradient(p, params);
let grad = g1.scale(d1).scale(2.0).add_self(&g2.scale(d2).scale(2.0));
(d1.powi(2) + d2.powi(2), grad)
}
fn gradient_descent<T: SDF<N, V>>(
&self,
other: &T,
ip: V,
params: &CollisionParameters,
) -> Option<V> {
let mut x = ip.clone();
for _ in 0..params.max_gds_iter {
let dg = self.combined(other, x, params);
x = x.sub_self(&dg.1.scale(params.learning_rate));
if dg.0 <= params.collision_epsilon {
return Some(x);
}
}
None
}
fn get_coll_point<T: SDF<N, V>>(
&self,
other: &T,
params: &CollisionParameters,
) -> Option<CollisionResult<N, V>> {
if let Some(inter) = self.aabb().intersect(&other.aabb()) {
if inter.get_dim().min_elem() <= 0.0 {
return None;
}
let circles = generate_circles(
&inter,
params.area_percentage,
params.max_packing_iter,
params.minimum_radius,
25,
);
let dim = inter.get_dim();
let top_left = inter.get_center().sub_self(&dim.scale(0.5));
let circles_filtered = circles
.iter()
.map(|x| Circle {
c: x.c.add_self(&top_left),
r: x.r,
})
.filter(|x| other.dist(x.c) - x.r < 0.0)
.filter(|x| self.dist(x.c) - x.r < 0.0);
let mut average_grad = V::from_arr([0.0; N]);
let mut average_point = V::from_arr([0.0; N]);
let mut num: f32 = 0.0;
for i in circles_filtered {
let conv_p = self.gradient_descent(other, i.c, params);
if let Some(coll_point) = conv_p {
let dg = other.gradient(coll_point, params);
let grad = dg;
average_grad = average_grad.add_self(&grad);
average_point = average_point.add_self(&coll_point);
num += 1.0;
break;
}
}
if num > 0.0 {
average_grad = average_grad.scale(num.recip());
average_point = average_point.scale(num.recip());
return Some(CollisionResult {
point: average_point,
gradient: average_grad,
});
}
}
None
}
fn sum_gradient_depth<T: SDF<N, V>>(
&self,
other: &T,
params: &CollisionParameters,
res: &CollisionResult<N, V>,
) -> f32 {
let p = res.point;
let mgrad = self.gradient(p, params).normalized();
let ograd = other.gradient(p, params).normalized();
let avg = (mgrad.add_self(&ograd)).scale(0.5).normalized();
let mut t = 0.0;
for _ in 0..10 {
t += params.learning_rate;
let md = self.dist(p.add_self(&avg.scale(t)));
let od = other.dist(p.add_self(&avg.scale(t)));
if md.max(od) > params.collision_epsilon {
break;
}
}
return t;
}
}