use alice_physics::math::{Fix128, QuatFix, Vec3Fix};
use alice_physics::sdf_collider::{ClosureSdf, SdfCollider};
use alice_physics::sdf_force::{apply_sdf_force_fields, compute_sdf_force};
use alice_physics::{RigidBody, SdfForceField, SdfForceType};
use std::panic::{self, AssertUnwindSafe};
fn unit_sphere() -> ClosureSdf {
ClosureSdf::new(
|x, y, z| z.mul_add(z, x.mul_add(x, y * y)).sqrt() - 1.0,
|x, y, z| {
let len = z.mul_add(z, x.mul_add(x, y * y)).sqrt();
if len < 1e-10 {
(0.0, 1.0, 0.0)
} else {
(x / len, y / len, z / len)
}
},
)
}
fn ground_plane() -> ClosureSdf {
ClosureSdf::new(|_x, y, _z| y, |_x, _y, _z| (0.0, 1.0, 0.0))
}
fn sphere_collider() -> SdfCollider {
SdfCollider::new_static(Box::new(unit_sphere()), Vec3Fix::ZERO, QuatFix::IDENTITY)
}
fn ground_collider() -> SdfCollider {
SdfCollider::new_static(Box::new(ground_plane()), Vec3Fix::ZERO, QuatFix::IDENTITY)
}
fn main() {
let sphere = sphere_collider();
let ground = ground_collider();
let attract = SdfForceField::attract(0, Fix128::from_int(3));
let body_out = RigidBody::new(Vec3Fix::from_int(3, 0, 0), Fix128::ONE); let f_attract = compute_sdf_force(&body_out, &sphere, &attract.force_type);
println!("[sdf_force] attract(strength=3) at dist=2: force={f_attract}");
assert_eq!(f_attract, Vec3Fix::from_int(-6, 0, 0));
let body_far = RigidBody::new(Vec3Fix::from_int(21, 0, 0), Fix128::ONE); let f_clamped = compute_sdf_force(&body_far, &sphere, &attract.force_type);
println!("[sdf_force] attract(strength=3) at dist=20 (clamped): force={f_clamped}");
assert_eq!(f_clamped, Vec3Fix::from_int(-30, 0, 0));
let repel = SdfForceField::repel(0, Fix128::from_int(8), Fix128::from_int(4));
let body_mid = RigidBody::new(Vec3Fix::from_int(2, 0, 0), Fix128::ONE); let f_repel = compute_sdf_force(&body_mid, &sphere, &repel.force_type);
let factor = Fix128::ONE - Fix128::ONE / Fix128::from_int(4); let expect_repel = Fix128::from_int(8) * factor * factor;
println!(
"[sdf_force] repel(strength=8, range=4) at dist=1: force={f_repel} (expect {expect_repel})"
);
assert_eq!(
f_repel,
Vec3Fix::new(expect_repel, Fix128::ZERO, Fix128::ZERO)
);
let body_outside_range = RigidBody::new(Vec3Fix::from_int(10, 0, 0), Fix128::ONE); let f_repel_out = compute_sdf_force(&body_outside_range, &sphere, &repel.force_type);
println!("[sdf_force] repel() beyond range (dist=9 > range=4): force={f_repel_out}");
assert_eq!(f_repel_out, Vec3Fix::ZERO);
let contain = SdfForceField::contain(1, Fix128::from_int(10));
let damping = Fix128::from_ratio(1, 10);
let mut body_inside = RigidBody::new(Vec3Fix::from_int(0, -2, 0), Fix128::ONE);
body_inside.velocity = Vec3Fix::from_int(8, 0, 0);
let f_in = compute_sdf_force(&body_inside, &ground, &contain.force_type);
let expect_in = body_inside.velocity * (-damping);
println!("[sdf_force] contain(strength=10) inside (dist=-2): force={f_in} (expect {expect_in}, damping-only)");
assert_eq!(f_in, expect_in);
let mut body_outside = RigidBody::new(Vec3Fix::from_int(0, 3, 0), Fix128::ONE); body_outside.velocity = Vec3Fix::from_int(8, 0, 0);
let f_out = compute_sdf_force(&body_outside, &ground, &contain.force_type);
let push = -Vec3Fix::UNIT_Y * (Fix128::from_int(10) * Fix128::from_int(3));
let damp = body_outside.velocity * (-damping);
println!(
"[sdf_force] contain(strength=10) outside (dist=3): force={f_out} (expect {})",
push + damp
);
assert_eq!(f_out, push + damp);
let flow = SdfForceField::surface_flow(0, Vec3Fix::from_int(0, 0, 8), Fix128::from_int(5));
let body_flow = RigidBody::new(Vec3Fix::from_int(3, 0, 0), Fix128::ONE); let f_flow = compute_sdf_force(&body_flow, &sphere, &flow.force_type);
println!("[sdf_force] surface_flow(dir=(0,0,8), strength=5) at dist=2 (== influence_distance): force={f_flow}");
assert_eq!(f_flow, Vec3Fix::ZERO);
let body_flow_mid = RigidBody::new(Vec3Fix::from_f32(2.0, 0.0, 0.0), Fix128::ONE); let f_flow_mid = compute_sdf_force(&body_flow_mid, &sphere, &flow.force_type);
let falloff = Fix128::ONE - Fix128::ONE / Fix128::from_int(2);
let expect_flow_mid = Vec3Fix::from_int(0, 0, 1) * (Fix128::from_int(5) * falloff);
println!("[sdf_force] surface_flow() at dist=1: force={f_flow_mid} (expect {expect_flow_mid})");
assert_eq!(f_flow_mid, expect_flow_mid);
let radial_flow =
SdfForceField::surface_flow(0, Vec3Fix::from_int(5, 0, 0), Fix128::from_int(5));
let f_radial = compute_sdf_force(&body_flow_mid, &sphere, &radial_flow.force_type);
println!("[sdf_force] surface_flow(dir=(5,0,0)) parallel to normal: force={f_radial} (expect zero, no tangent)");
assert_eq!(f_radial, Vec3Fix::ZERO);
let push_field = SdfForceField::new(
0,
SdfForceType::Contain {
strength: Fix128::from_int(4),
damping: Fix128::ZERO,
},
);
let dt = Fix128::ONE;
let make_body = || {
let mut b = RigidBody::new_dynamic(Vec3Fix::from_int(0, 3, 0), Fix128::ONE); b.velocity = Vec3Fix::ZERO;
b
};
let mut all_bodies = vec![make_body(), make_body()];
apply_sdf_force_fields(
std::slice::from_ref(&push_field),
&[ground_collider()],
&mut all_bodies,
dt,
);
println!(
"[sdf_force] with_affected_bodies: default scope -> body0.v={} body1.v={}",
all_bodies[0].velocity, all_bodies[1].velocity
);
let expect_push = -Vec3Fix::UNIT_Y * (Fix128::from_int(4) * Fix128::from_int(3));
assert_eq!(all_bodies[0].velocity, expect_push);
assert_eq!(all_bodies[1].velocity, expect_push);
let empty_scope = push_field.clone().with_affected_bodies(vec![]);
let mut none_bodies = vec![make_body(), make_body()];
apply_sdf_force_fields(&[empty_scope], &[ground_collider()], &mut none_bodies, dt);
println!(
"[sdf_force] with_affected_bodies(vec![]): body0.v={} body1.v={} (expect both zero, no-op)",
none_bodies[0].velocity, none_bodies[1].velocity
);
assert_eq!(none_bodies[0].velocity, Vec3Fix::ZERO);
assert_eq!(none_bodies[1].velocity, Vec3Fix::ZERO);
let scoped = push_field.with_affected_bodies(vec![1]);
let mut scoped_bodies = vec![make_body(), make_body()];
apply_sdf_force_fields(&[scoped], &[ground_collider()], &mut scoped_bodies, dt);
println!(
"[sdf_force] with_affected_bodies(vec![1]): body0.v={} body1.v={}",
scoped_bodies[0].velocity, scoped_bodies[1].velocity
);
assert_eq!(scoped_bodies[0].velocity, Vec3Fix::ZERO);
assert_eq!(scoped_bodies[1].velocity, expect_push);
let combo_attract = SdfForceField::attract(0, Fix128::from_int(3)); let combo_repel = SdfForceField::repel(0, Fix128::from_int(8), Fix128::from_int(4));
let colliders = [sphere_collider()];
let dt2 = Fix128::from_ratio(1, 4);
let mut dyn_body = RigidBody::new_dynamic(Vec3Fix::from_int(3, 0, 0), Fix128::ONE); dyn_body.inv_mass = Fix128::from_int(2);
dyn_body.velocity = Vec3Fix::from_int(5, 0, 0);
let mut static_body = RigidBody::new_static(Vec3Fix::from_int(3, 0, 0));
static_body.velocity = Vec3Fix::from_int(5, 0, 0); let mut bodies = vec![dyn_body, static_body];
apply_sdf_force_fields(
&[combo_attract.clone(), combo_repel.clone()],
&colliders,
&mut bodies,
dt2,
);
println!(
"[sdf_force] apply_sdf_force_fields(attract+repel) dyn.v={} static.v={}",
bodies[0].velocity, bodies[1].velocity
);
assert_eq!(bodies[0].velocity, Vec3Fix::from_int(3, 0, 0));
assert_eq!(bodies[1].velocity, Vec3Fix::from_int(5, 0, 0));
let mut untouched = vec![RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE)];
apply_sdf_force_fields(&[], &colliders, &mut untouched, dt2);
println!(
"[sdf_force] apply_sdf_force_fields(no fields): v={}",
untouched[0].velocity
);
assert_eq!(untouched[0].velocity, Vec3Fix::ZERO);
let mut no_bodies: Vec<RigidBody> = Vec::new();
apply_sdf_force_fields(
std::slice::from_ref(&combo_attract),
&colliders,
&mut no_bodies,
dt2,
);
println!(
"[sdf_force] apply_sdf_force_fields(no bodies): len={}",
no_bodies.len()
);
assert_eq!(no_bodies.len(), 0);
let body_on_surface = RigidBody::new(Vec3Fix::from_int(1, 0, 0), Fix128::ONE);
let f_on_surface = compute_sdf_force(&body_on_surface, &sphere, &attract.force_type);
println!(
"[sdf_force] attract() exactly on surface (dist=0): force={f_on_surface} (expect zero)"
);
assert_eq!(f_on_surface, Vec3Fix::ZERO);
let large_pos = Vec3Fix::new(Fix128::from_int(1_000_000_000), Fix128::ZERO, Fix128::ZERO);
let body_large = RigidBody::new(large_pos, Fix128::ONE);
let large_attract = panic::catch_unwind(AssertUnwindSafe(|| {
compute_sdf_force(&body_large, &sphere, &attract.force_type)
}));
println!("[sdf_force] large position (dist~1e9), attract(): {large_attract:?} (expect clamped to max_force=30)");
assert!(large_attract.is_ok());
assert_eq!(large_attract.unwrap(), Vec3Fix::from_int(-30, 0, 0));
let extreme_pos = Vec3Fix::new(
Fix128::from_raw(i64::MAX / 2, u64::MAX),
Fix128::ZERO,
Fix128::ZERO,
);
let body_extreme = RigidBody::new(extreme_pos, Fix128::ONE);
let extreme_attract = panic::catch_unwind(AssertUnwindSafe(|| {
compute_sdf_force(&body_extreme, &sphere, &attract.force_type)
}));
let (lx, _, _) = extreme_pos.to_f32();
let dist_f64 = f64::from(lx) - 1.0; assert!(
3.0 * dist_f64 > 9.223_372_036_854_776e18, "the product must exceed Fix128's integer range for this case to test the overflow path"
);
let expect_extreme = Vec3Fix::from_int(-30, 0, 0);
println!("[sdf_force] extreme position, attract(): {extreme_attract:?} (expect {expect_extreme}, clamped to max_force=30)");
assert!(extreme_attract.is_ok());
assert_eq!(extreme_attract.unwrap(), expect_extreme);
let extreme_repel = panic::catch_unwind(AssertUnwindSafe(|| {
compute_sdf_force(&body_extreme, &sphere, &repel.force_type)
}));
println!(
"[sdf_force] extreme position, repel(): {extreme_repel:?} (expect zero, beyond range)"
);
assert!(extreme_repel.is_ok());
assert_eq!(extreme_repel.unwrap(), Vec3Fix::ZERO);
println!("[sdf_force] all production-entry checks passed");
}