makepad/libs/box3d/tests/test_large_world.rs
Admin 60ec705ab2 box3d: SIMD (SSE2/NEON), double-precision large world, snapshots, benchmarks
- contact solver wide ops + V32 now have real SSE2 and NEON paths selected
  by target arch; scalar fallback behind the disable-simd feature. All three
  paths are bit-identical (cross-arch determinism verified: same ragdoll
  hash on NEON, SSE2 under Rosetta, and scalar).
- double-precision feature (C BOX3D_DOUBLE_PRECISION): f64 world positions
  with the exact C boundary-function semantics; enables the far-from-origin
  test halves (157 tests in DP mode, 151 default).
- world snapshots: recording substrate subset (buffer/writers/geometry
  registry/readers) + world_snapshot.c port; bit-identical continuation
  after restore, corrupt-image rejection.
- examples/benchmark.rs: all 10 C benchmark scenarios; serial Rust runs
  1.05-1.55x slower than C -O2 at one worker (geomean ~1.3x with fat LTO).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 16:06:18 +02:00

287 lines
10 KiB
Rust

// Port of box3d/test/test_large_world.c
// The Rust port is single precision only (BOX3D_DOUBLE_PRECISION off), so the
// far-from-origin halves of each C subtest (gated behind the define) are not
// ported; the origin halves are kept in full.
use makepad_box3d::body::*;
use makepad_box3d::hull::{make_box_hull, make_cube_hull};
use makepad_box3d::math_functions::{offset_pos, pos, sub_pos, vec3, Pos, Vec3};
use makepad_box3d::physics_world::*;
use makepad_box3d::shape::{create_hull_shape, create_sphere_shape, shape_ray_cast};
use makepad_box3d::types::*;
use makepad_box3d::{ensure, ensure_small};
const STACK_COUNT: usize = 6;
const MAX_STEPS: i32 = 400;
struct StackResult {
// Final body positions relative to the base, so origin and far runs are directly comparable
relative_positions: [Vec3; STACK_COUNT],
// First step on which the top body fell asleep, or -1 if it never settled
sleep_step: i32,
}
// Drop a short stack of boxes onto a ground box centered at baseX. Records each body's final
// position relative to the base and the step on which the stack settles.
fn run_stack(base_x: f32) -> StackResult {
let base: Pos = pos(base_x, 0.0, 0.0);
let world_def = default_world_def();
let mut world = create_world(&world_def);
let mut ground_def = default_body_def();
ground_def.position = base;
let ground_id = create_body(&mut world, &ground_def);
let ground_box = make_box_hull(10.0, 1.0, 10.0);
let ground_shape_def = default_shape_def();
create_hull_shape(&mut world, ground_id, &ground_shape_def, &ground_box);
let mut bodies = [makepad_box3d::id::NULL_BODY_ID; STACK_COUNT];
for i in 0..STACK_COUNT {
let mut body_def = default_body_def();
body_def.body_type = BodyType::Dynamic;
body_def.position = offset_pos(base, vec3(0.0, 2.0 + 1.05 * i as f32, 0.0));
bodies[i] = create_body(&mut world, &body_def);
let cube = make_cube_hull(0.5);
let mut shape_def = default_shape_def();
shape_def.density = 1.0;
create_hull_shape(&mut world, bodies[i], &shape_def, &cube);
}
let mut result = StackResult { relative_positions: [Vec3::ZERO; STACK_COUNT], sleep_step: -1 };
for step in 0..MAX_STEPS {
world_step(&mut world, 1.0 / 60.0, 4);
if result.sleep_step < 0 && !body_is_awake(&world, bodies[STACK_COUNT - 1]) {
result.sleep_step = step;
}
}
for i in 0..STACK_COUNT {
let p = body_get_position(&world, bodies[i]);
result.relative_positions[i] = sub_pos(p, base);
}
destroy_world(world);
result
}
// A stack at the origin should settle. (The C far-from-origin comparison is
// double precision only and is not ported.)
#[test]
fn large_world_stack_test() {
let origin = run_stack(0.0);
ensure!(origin.sleep_step >= 0);
}
// Fire a fast bullet at a thin wall. Returns the bullet's final x relative to the base. If
// continuous collision works the bullet stops at the wall instead of tunneling past it.
fn run_bullet(base_x: f32) -> f32 {
let base: Pos = pos(base_x, 0.0, 0.0);
let world_def = default_world_def();
let mut world = create_world(&world_def);
// Thin static wall at x = base + 5, spanning y and z
let mut wall_def = default_body_def();
wall_def.body_type = BodyType::Static;
wall_def.position = offset_pos(base, vec3(5.0, 0.0, 0.0));
let wall_id = create_body(&mut world, &wall_def);
let wall_box = make_box_hull(0.05, 5.0, 5.0);
let wall_shape_def = default_shape_def();
create_hull_shape(&mut world, wall_id, &wall_shape_def, &wall_box);
// Small fast bullet aimed at the wall, no gravity
let mut bullet_def = default_body_def();
bullet_def.body_type = BodyType::Dynamic;
bullet_def.is_bullet = true;
bullet_def.gravity_scale = 0.0;
bullet_def.position = base;
bullet_def.linear_velocity = vec3(200.0, 0.0, 0.0);
let bullet_id = create_body(&mut world, &bullet_def);
let sphere = Sphere { center: vec3(0.0, 0.0, 0.0), radius: 0.1 };
let mut bullet_shape_def = default_shape_def();
bullet_shape_def.density = 1.0;
create_sphere_shape(&mut world, bullet_id, &bullet_shape_def, &sphere);
for _step in 0..30 {
world_step(&mut world, 1.0 / 60.0, 4);
}
let relative = sub_pos(body_get_position(&world, bullet_id), base);
destroy_world(world);
relative.x
}
// The bullet must be caught by the wall, not tunnel through it.
#[test]
fn large_world_bullet_test() {
// Wall front face is at x = 5 - 0.05; the bullet radius is 0.1, so a caught bullet stays well
// short of the wall center at x = 5.
let origin_x = run_bullet(0.0);
ensure!(origin_x < 5.0);
}
struct QueryResult {
cast_hit: bool,
cast_rel_x: f32, // shape cast hit point x relative to the base
overlap_hit: bool,
mover_fraction: f32,
plane_count: i32,
ray_hit: bool,
ray_rel_x: f32, // world ray cast hit point x relative to the base
shape_ray_hit: bool,
shape_ray_rel_x: f32, // direct shape ray cast hit point x relative to the base
}
// Run the four origin relative spatial queries against a static box centered at the base. The query
// inputs are all relative to the base, so passing base as the origin keeps them precise far out.
fn run_queries(base_x: f32) -> QueryResult {
let base: Pos = pos(base_x, 0.0, 0.0);
let world_def = default_world_def();
let mut world = create_world(&world_def);
let mut body_def = default_body_def();
body_def.body_type = BodyType::Static;
body_def.position = base;
let body_id = create_body(&mut world, &body_def);
let box_hull = make_box_hull(1.0, 1.0, 1.0);
let shape_def = default_shape_def();
let shape_id = create_hull_shape(&mut world, body_id, &shape_def, &box_hull);
world_step(&mut world, 1.0 / 60.0, 1);
// Sphere proxy swept from the left into the box, hitting the left face at relative x = -1
let cast_point = [vec3(-5.0, 0.0, 0.0)];
let cast_proxy = ShapeProxy { points: &cast_point, radius: 0.25 };
let mut cast_hit = false;
let mut cast_rel_x = 0.0f32;
world_cast_shape(
&world,
base,
&cast_proxy,
vec3(10.0, 0.0, 0.0),
default_query_filter(),
&mut |_shape_id, point, _normal, fraction, _material_id, _triangle_index, _child_index| {
cast_hit = true;
cast_rel_x = sub_pos(point, base).x;
fraction
},
);
// Sphere proxy sitting at the box center
let overlap_point = [vec3(0.0, 0.0, 0.0)];
let overlap_proxy = ShapeProxy { points: &overlap_point, radius: 0.5 };
let mut overlap_hit = false;
world_overlap_shape(&world, base, &overlap_proxy, default_query_filter(), &mut |_shape_id| {
overlap_hit = true;
true
});
// Capsule swept from the left into the box
let mover_cast = Capsule { center1: vec3(-5.0, -0.3, 0.0), center2: vec3(-5.0, 0.3, 0.0), radius: 0.25 };
let mover_fraction =
world_cast_mover(&world, base, &mover_cast, vec3(10.0, 0.0, 0.0), default_query_filter(), None);
// Capsule overlapping the left face, should report a contact plane
let mover_collide = Capsule { center1: vec3(-1.1, -0.3, 0.0), center2: vec3(-1.1, 0.3, 0.0), radius: 0.3 };
let mut plane_count = 0i32;
world_collide_mover(&world, base, &mover_collide, default_query_filter(), &mut |_shape_id, planes| {
plane_count += planes.len() as i32;
true
});
// World ray cast from the left into the box, hitting the left face at relative x = -1
let ray_origin = offset_pos(base, vec3(-5.0, 0.0, 0.0));
let ray = world_cast_ray_closest(&world, ray_origin, vec3(10.0, 0.0, 0.0), default_query_filter());
let ray_hit = ray.hit;
let ray_rel_x = if ray.hit { sub_pos(ray.point, base).x } else { 0.0 };
// Direct shape ray cast against the same box
let shape_ray = shape_ray_cast(&world, shape_id, ray_origin, vec3(10.0, 0.0, 0.0));
let shape_ray_hit = shape_ray.hit;
let shape_ray_rel_x = if shape_ray.hit { sub_pos(shape_ray.point, base).x } else { 0.0 };
destroy_world(world);
QueryResult {
cast_hit,
cast_rel_x,
overlap_hit,
mover_fraction,
plane_count,
ray_hit,
ray_rel_x,
shape_ray_hit,
shape_ray_rel_x,
}
}
// The origin relative queries hit at the origin. (The C far-from-origin
// comparison is double precision only and is not ported.)
#[test]
fn large_world_query_test() {
let origin = run_queries(0.0);
ensure!(origin.cast_hit);
ensure!(origin.overlap_hit);
ensure!(origin.mover_fraction < 1.0);
ensure!(origin.plane_count > 0);
ensure_small!(origin.cast_rel_x + 1.0, 0.05);
ensure!(origin.ray_hit);
ensure_small!(origin.ray_rel_x + 1.0, 0.05);
ensure!(origin.shape_ray_hit);
ensure_small!(origin.shape_ray_rel_x + 1.0, 0.05);
}
// Port of the BOX3D_DOUBLE_PRECISION halves: a stack, a bullet, and the queries far from
// the origin must behave identically to the origin runs in double precision mode.
#[cfg(feature = "double-precision")]
#[test]
fn large_world_stack_far_test() {
let origin = run_stack(0.0);
ensure!(origin.sleep_step >= 0);
let far = run_stack(1.0e7);
ensure!(far.sleep_step >= 0);
// Sleeps on the same frame and lands in the same relative configuration
ensure!(far.sleep_step == origin.sleep_step);
for i in 0..STACK_COUNT {
ensure_small!(far.relative_positions[i].x - origin.relative_positions[i].x, 1.0e-3);
ensure_small!(far.relative_positions[i].y - origin.relative_positions[i].y, 1.0e-3);
ensure_small!(far.relative_positions[i].z - origin.relative_positions[i].z, 1.0e-3);
}
}
#[cfg(feature = "double-precision")]
#[test]
fn large_world_bullet_far_test() {
// The blocking check is that the catch still holds far from the origin where the swept
// query box rounds back to float with large ULP.
let far_x = run_bullet(1.0e7);
ensure!(far_x < 5.0);
}
#[cfg(feature = "double-precision")]
#[test]
fn large_world_query_far_test() {
let origin = run_queries(0.0);
let far = run_queries(1.0e7);
ensure!(far.cast_hit);
ensure!(far.overlap_hit);
ensure!(far.mover_fraction < 1.0);
ensure!(far.plane_count > 0);
ensure!(far.ray_hit);
ensure!(far.shape_ray_hit);
ensure_small!(far.cast_rel_x - origin.cast_rel_x, 1.0e-3);
ensure_small!(far.mover_fraction - origin.mover_fraction, 1.0e-3);
ensure!(far.plane_count == origin.plane_count);
ensure_small!(far.ray_rel_x - origin.ray_rel_x, 1.0e-3);
ensure_small!(far.shape_ray_rel_x - origin.shape_ray_rel_x, 1.0e-3);
}