boxddd 0.1.0

Safe, ergonomic Rust bindings for Box3D
Documentation
use boxddd::{
    BodyDef, BodyType, Error, MotionLocks, QueryFilter, ShapeDef, ShapeProxy, Sphere, Vec3, World,
    WorldDef,
};

#[test]
fn world_runtime_tuning_and_metrics_are_safe() {
    let mut world = World::new(WorldDef::builder().gravity([0.0, -9.8, 0.0]).build()).unwrap();

    world.try_enable_sleeping(false).unwrap();
    assert!(!world.sleeping_enabled());
    world.try_enable_sleeping(true).unwrap();
    assert!(world.sleeping_enabled());

    world.try_enable_continuous(false).unwrap();
    assert!(!world.continuous_enabled());
    world.try_enable_continuous(true).unwrap();
    assert!(world.continuous_enabled());

    world.try_enable_warm_starting(false).unwrap();
    assert!(!world.warm_starting_enabled());
    world.try_enable_warm_starting(true).unwrap();
    assert!(world.warm_starting_enabled());

    world.try_enable_speculative(true).unwrap();
    world.try_set_restitution_threshold(2.0).unwrap();
    assert_eq!(world.restitution_threshold(), 2.0);
    world.try_set_hit_event_threshold(3.0).unwrap();
    assert_eq!(world.hit_event_threshold(), 3.0);
    world.try_set_contact_tuning(60.0, 0.7, 10.0).unwrap();
    world.try_set_contact_recycle_distance(0.02).unwrap();
    assert_eq!(world.contact_recycle_distance(), 0.02);
    world.try_set_maximum_linear_speed(120.0).unwrap();
    assert_eq!(world.maximum_linear_speed(), 120.0);
    world.try_set_worker_count(0).unwrap();
    assert!(world.worker_count() >= 0);

    let counters = world.counters();
    assert_eq!(counters.body_count, 0);
    let profile = world.profile();
    assert!(profile.step >= 0.0);
    let capacity = world.max_capacity();
    assert!(capacity.dynamic_body_count >= 0);
    world.try_rebuild_static_tree().unwrap();
}

#[test]
fn body_runtime_setters_getters_and_buffers_work() {
    let mut world = World::new(WorldDef::builder().gravity([0.0, -9.8, 0.0]).build()).unwrap();
    let body = world.create_body(
        BodyDef::builder()
            .body_type(BodyType::Dynamic)
            .position([0.0, 3.0, 0.0])
            .build(),
    );
    let shape = world.create_sphere_shape(
        body,
        &ShapeDef::builder().density(1.0).build(),
        &Sphere::new([0.0, 0.0, 0.0], 0.5),
    );

    assert_eq!(world.body_type(body), BodyType::Dynamic);
    world.try_set_body_name(body, "runtime-body").unwrap();
    assert_eq!(
        world.try_body_name(body).unwrap().as_deref(),
        Some("runtime-body")
    );

    world
        .try_set_body_transform(body, [1.0, 4.0, 2.0], Default::default())
        .unwrap();
    assert_eq!(world.body_position(body).x, 1.0);
    assert_eq!(world.body_transform(body).p.z, 2.0);
    assert_eq!(world.body_rotation(body), Default::default());

    world
        .try_set_body_linear_velocity(body, [1.0, 2.0, 3.0])
        .unwrap();
    assert_eq!(world.body_linear_velocity(body), Vec3::new(1.0, 2.0, 3.0));
    assert_eq!(
        world
            .try_body_local_point_velocity(body, Vec3::ZERO)
            .unwrap(),
        world.body_linear_velocity(body)
    );
    assert_eq!(
        world
            .try_body_world_point_velocity(body, [1.0, 4.0, 2.0])
            .unwrap(),
        world.body_linear_velocity(body)
    );
    world
        .try_set_body_angular_velocity(body, [0.1, 0.2, 0.3])
        .unwrap();
    assert_eq!(world.body_angular_velocity(body), Vec3::new(0.1, 0.2, 0.3));

    world.try_set_body_linear_damping(body, 0.4).unwrap();
    assert_eq!(world.try_body_linear_damping(body).unwrap(), 0.4);
    world.try_set_body_angular_damping(body, 0.5).unwrap();
    assert_eq!(world.try_body_angular_damping(body).unwrap(), 0.5);
    world.try_set_body_gravity_scale(body, 0.25).unwrap();
    assert_eq!(world.try_body_gravity_scale(body).unwrap(), 0.25);

    let locks = MotionLocks::new(true, false, true, false, true, false);
    world.try_set_body_motion_locks(body, locks).unwrap();
    assert_eq!(world.try_body_motion_locks(body).unwrap(), locks);
    world
        .try_set_body_motion_locks(body, MotionLocks::default())
        .unwrap();
    world.try_set_body_bullet(body, true).unwrap();
    assert!(world.try_body_bullet(body).unwrap());
    world
        .try_enable_body_contact_recycling(body, false)
        .unwrap();
    assert!(!world.try_body_contact_recycling_enabled(body).unwrap());
    world.try_enable_body_hit_events(body, true).unwrap();

    assert!(world.try_body_mass(body).unwrap() > 0.0);
    assert!(world.try_body_inverse_mass(body).unwrap() > 0.0);
    let mass_data = world.try_body_mass_data(body).unwrap();
    world.try_set_body_mass_data(body, mass_data).unwrap();
    world.try_apply_mass_from_shapes(body).unwrap();
    let _ = world.try_body_local_rotational_inertia(body).unwrap();
    let _ = world
        .try_body_world_inverse_rotational_inertia(body)
        .unwrap();
    let _ = world.try_body_local_center_of_mass(body).unwrap();
    let _ = world.try_body_world_center_of_mass(body).unwrap();
    let _ = world.try_body_aabb(body).unwrap();

    world
        .try_apply_force_to_center(body, [1.0, 0.0, 0.0], true)
        .unwrap();
    world
        .try_apply_force(body, [0.0, 1.0, 0.0], world.body_position(body), true)
        .unwrap();
    world.try_apply_torque(body, [0.0, 0.0, 1.0], true).unwrap();
    world
        .try_apply_linear_impulse_to_center(body, [1.0, 0.0, 0.0], true)
        .unwrap();
    world
        .try_apply_linear_impulse(body, [0.0, 1.0, 0.0], world.body_position(body), true)
        .unwrap();
    world
        .try_apply_angular_impulse(body, [0.0, 0.0, 0.1], true)
        .unwrap();
    world.step(1.0 / 60.0, 4);
    assert!(world.body_linear_velocity(body).x > 0.0);

    let mut shapes = vec![shape];
    world.try_body_shapes_into(body, &mut shapes).unwrap();
    assert_eq!(shapes, vec![shape]);
    assert_eq!(world.body_shapes(body), vec![shape]);

    let mut joints = Vec::from([Default::default()]);
    world.try_body_joints_into(body, &mut joints).unwrap();
    assert!(joints.is_empty());

    let mut contacts = vec![Default::default()];
    world.try_body_contacts_into(body, &mut contacts).unwrap();
    assert!(contacts.is_empty());
}

#[test]
fn body_runtime_respects_callback_guard() {
    let mut world = World::new(WorldDef::default()).unwrap();
    let body = world.create_body(BodyDef::default());
    let _guard = boxddd::__private::enter_callback_guard_for_test();

    assert_eq!(
        world.try_body_linear_velocity(body).unwrap_err(),
        boxddd::Error::InCallback
    );
}

#[test]
fn world_rejects_foreign_body_and_shape_handles() {
    let mut world = World::new(WorldDef::default()).unwrap();
    let local = world.create_body(BodyDef::default());

    let mut other = World::new(WorldDef::default()).unwrap();
    let foreign_body = other.create_body(BodyDef::default());
    let foreign_shape = other.create_sphere_shape(
        foreign_body,
        &ShapeDef::default(),
        &Sphere::new(Vec3::ZERO, 0.5),
    );

    assert_eq!(
        world.try_body_position(foreign_body).unwrap_err(),
        Error::InvalidBodyId
    );
    assert_eq!(
        world
            .try_body_closest_point(foreign_body, Vec3::ZERO)
            .unwrap_err(),
        Error::InvalidBodyId
    );
    assert_eq!(
        world
            .try_body_overlap_shape(
                foreign_body,
                Vec3::ZERO,
                &ShapeProxy::sphere(0.5).unwrap(),
                QueryFilter::default()
            )
            .unwrap_err(),
        Error::InvalidBodyId
    );
    assert_eq!(
        world.try_destroy_body(foreign_body).unwrap_err(),
        Error::InvalidBodyId
    );
    assert!(foreign_body.is_valid());
    assert_eq!(
        world
            .try_create_sphere_shape(
                foreign_body,
                &ShapeDef::default(),
                &Sphere::new(Vec3::ZERO, 0.5)
            )
            .unwrap_err(),
        Error::InvalidBodyId
    );
    assert_eq!(
        world.try_shape_body(foreign_shape).unwrap_err(),
        Error::InvalidShapeId
    );
    assert_eq!(
        world.try_destroy_shape(foreign_shape, true).unwrap_err(),
        Error::InvalidShapeId
    );
    assert!(foreign_shape.is_valid());

    world.destroy_body(local);
}