use crate::compiled::{
eval_compiled, eval_compiled_distance_and_normal, eval_compiled_normal, CompiledSdf,
};
use alice_physics::SdfField;
use glam::Vec3;
use std::sync::Arc;
pub struct CompiledSdfField {
sdf: Arc<CompiledSdf>,
pub epsilon: f32,
}
impl CompiledSdfField {
pub fn new(sdf: CompiledSdf) -> Self {
Self {
sdf: Arc::new(sdf),
epsilon: 0.001,
}
}
pub const fn from_arc(sdf: Arc<CompiledSdf>) -> Self {
Self {
sdf,
epsilon: 0.001,
}
}
pub const fn with_epsilon(mut self, epsilon: f32) -> Self {
self.epsilon = epsilon;
self
}
pub fn compiled(&self) -> &CompiledSdf {
&self.sdf
}
pub fn arc(&self) -> Arc<CompiledSdf> {
Arc::clone(&self.sdf)
}
}
impl SdfField for CompiledSdfField {
#[inline]
fn distance(&self, x: f32, y: f32, z: f32) -> f32 {
eval_compiled(&self.sdf, Vec3::new(x, y, z))
}
#[inline]
fn normal(&self, x: f32, y: f32, z: f32) -> (f32, f32, f32) {
let n = eval_compiled_normal(&self.sdf, Vec3::new(x, y, z), self.epsilon);
(n.x, n.y, n.z)
}
#[inline]
fn distance_and_normal(&self, x: f32, y: f32, z: f32) -> (f32, (f32, f32, f32)) {
let p = Vec3::new(x, y, z);
let (dist, n) = eval_compiled_distance_and_normal(&self.sdf, p, self.epsilon);
(dist, (n.x, n.y, n.z))
}
}
pub fn sdf_to_physics_field(node: &crate::types::SdfNode) -> CompiledSdfField {
let compiled = CompiledSdf::compile(node);
CompiledSdfField::new(compiled)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::SdfNode;
use alice_physics::sdf_collider::{collide_point_sdf, SdfCollider};
use alice_physics::{Fix128, QuatFix, Vec3Fix};
#[test]
fn test_compiled_sdf_as_physics_field() {
let node = SdfNode::sphere(1.0);
let field = sdf_to_physics_field(&node);
let d = field.distance(0.0, 0.0, 0.0);
assert!((d - (-1.0)).abs() < 0.01, "d={}", d);
let d2 = field.distance(2.0, 0.0, 0.0);
assert!((d2 - 1.0).abs() < 0.01, "d2={}", d2);
let (nx, ny, nz) = field.normal(1.0, 0.0, 0.0);
assert!(nx > 0.99, "nx={}", nx);
assert!(ny.abs() < 0.01, "ny={}", ny);
assert!(nz.abs() < 0.01, "nz={}", nz);
}
#[test]
fn test_sphere_collider_via_bridge() {
let node = SdfNode::sphere(2.0);
let field = sdf_to_physics_field(&node);
let collider = SdfCollider::new_static(Box::new(field), Vec3Fix::ZERO, QuatFix::IDENTITY);
let point_inside = Vec3Fix::from_f32(0.5, 0.0, 0.0);
let result = collide_point_sdf(point_inside, &collider);
assert!(result.is_some(), "Point inside should collide");
let point_outside = Vec3Fix::from_f32(5.0, 0.0, 0.0);
let result2 = collide_point_sdf(point_outside, &collider);
assert!(result2.is_none(), "Point outside should not collide");
}
#[test]
fn test_csg_collider_via_bridge() {
let node = SdfNode::sphere(2.0).subtract(SdfNode::box3d(0.5, 0.5, 0.5));
let field = sdf_to_physics_field(&node);
let collider = SdfCollider::new_static(Box::new(field), Vec3Fix::ZERO, QuatFix::IDENTITY);
let origin = Vec3Fix::ZERO;
let result = collide_point_sdf(origin, &collider);
assert!(
result.is_none(),
"Origin inside box hole should not collide"
);
let shell_point = Vec3Fix::from_f32(1.5, 0.0, 0.0);
let result2 = collide_point_sdf(shell_point, &collider);
assert!(
result2.is_some(),
"Point inside sphere shell should collide"
);
}
#[test]
fn test_physics_world_with_sdf() {
use alice_physics::{PhysicsConfig, PhysicsWorld, RigidBody};
let node = SdfNode::Plane {
normal: glam::Vec3::Y,
distance: 0.0,
};
let field = sdf_to_physics_field(&node);
let config = PhysicsConfig::default();
let mut world = PhysicsWorld::new(config);
world.set_sdf_collision_radius(Fix128::from_f32(0.5));
let body = RigidBody::new_dynamic(Vec3Fix::from_f32(0.0, 5.0, 0.0), Fix128::ONE);
world.add_body(body);
world.add_sdf_collider(SdfCollider::new_static(
Box::new(field),
Vec3Fix::ZERO,
QuatFix::IDENTITY,
));
let dt = Fix128::from_f32(1.0 / 60.0);
for _ in 0..120 {
world.step(dt);
}
let y = world.bodies[0].position.y.to_f32();
assert!(y < 5.0, "Body should have fallen");
assert!(y > -1.0, "Body should not have fallen through, y={}", y);
}
}