boxddd 0.3.0

Safe, ergonomic Rust bindings for Box3D
Documentation
use boxddd::error::HandleKind;
use boxddd::{
    BodyDef, BodyType, DistanceJointDef, 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(),
    )
    .unwrap();

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

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

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

    world.enable_speculative(true).unwrap();
    world.set_restitution_threshold(2.0).unwrap();
    assert_eq!(world.restitution_threshold().unwrap(), 2.0);
    world.set_hit_event_threshold(3.0).unwrap();
    assert_eq!(world.hit_event_threshold().unwrap(), 3.0);
    world.set_contact_tuning(60.0, 0.7, 10.0).unwrap();
    world.set_contact_recycle_distance(0.02).unwrap();
    assert_eq!(world.contact_recycle_distance().unwrap(), 0.02);
    world.set_maximum_linear_speed(120.0).unwrap();
    assert_eq!(world.maximum_linear_speed().unwrap(), 120.0);
    world.set_worker_count(0).unwrap();
    assert!(world.worker_count().unwrap() >= 0);

    let counters = world.counters().unwrap();
    assert_eq!(counters.body_count, 0);
    let profile = world.profile().unwrap();
    assert!(profile.step >= 0.0);
    let capacity = world.max_capacity().unwrap();
    assert!(capacity.dynamic_body_count >= 0);
    world.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(),
    )
    .unwrap();
    let body = world
        .create_body(
            BodyDef::builder()
                .body_type(BodyType::Dynamic)
                .position([0.0, 3.0, 0.0])
                .build()
                .unwrap(),
        )
        .unwrap();
    let shape = world
        .create_sphere_shape(
            body,
            &ShapeDef::builder().density(1.0).build().unwrap(),
            &Sphere::new([0.0, 0.0, 0.0], 0.5),
        )
        .unwrap();

    assert_eq!(world.body_type(body).unwrap(), BodyType::Dynamic);
    world.set_body_name(body, "runtime-body").unwrap();
    assert_eq!(
        world.body_name(body).unwrap().as_deref(),
        Some("runtime-body")
    );
    world.set_body_fast_rotation_allowed(body, true).unwrap();
    assert!(world.body_fast_rotation_allowed(body).unwrap());
    world.set_shape_name(shape, "runtime-shape").unwrap();
    assert_eq!(world.shape_name(shape).unwrap(), "runtime-shape");

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

    world
        .set_body_linear_velocity(body, [1.0, 2.0, 3.0])
        .unwrap();
    assert_eq!(
        world.body_linear_velocity(body).unwrap(),
        Vec3::new(1.0, 2.0, 3.0)
    );
    assert_eq!(
        world.body_local_point_velocity(body, Vec3::ZERO).unwrap(),
        world.body_linear_velocity(body).unwrap()
    );
    assert_eq!(
        world
            .body_world_point_velocity(body, [1.0, 4.0, 2.0])
            .unwrap(),
        world.body_linear_velocity(body).unwrap()
    );
    world
        .set_body_angular_velocity(body, [0.1, 0.2, 0.3])
        .unwrap();
    assert_eq!(
        world.body_angular_velocity(body).unwrap(),
        Vec3::new(0.1, 0.2, 0.3)
    );

    world.set_body_linear_damping(body, 0.4).unwrap();
    assert_eq!(world.body_linear_damping(body).unwrap(), 0.4);
    world.set_body_angular_damping(body, 0.5).unwrap();
    assert_eq!(world.body_angular_damping(body).unwrap(), 0.5);
    world.set_body_gravity_scale(body, 0.25).unwrap();
    assert_eq!(world.body_gravity_scale(body).unwrap(), 0.25);

    let locks = MotionLocks::new(true, false, true, false, true, false);
    world.set_body_motion_locks(body, locks).unwrap();
    assert_eq!(world.body_motion_locks(body).unwrap(), locks);
    world
        .set_body_motion_locks(body, MotionLocks::default())
        .unwrap();
    world.set_body_bullet(body, true).unwrap();
    assert!(world.body_bullet(body).unwrap());
    world.enable_body_contact_recycling(body, false).unwrap();
    assert!(!world.body_contact_recycling_enabled(body).unwrap());
    world.enable_body_hit_events(body, true).unwrap();

    assert!(world.body_mass(body).unwrap() > 0.0);
    assert!(world.body_inverse_mass(body).unwrap() > 0.0);
    let mass_data = world.body_mass_data(body).unwrap();
    world.set_body_mass_data(body, mass_data).unwrap();
    world.apply_mass_from_shapes(body).unwrap();
    let _ = world.body_local_rotational_inertia(body).unwrap();
    let _ = world.body_world_inverse_rotational_inertia(body).unwrap();
    let _ = world.body_local_center_of_mass(body).unwrap();
    let _ = world.body_world_center_of_mass(body).unwrap();
    let _ = world.body_aabb(body).unwrap();

    world
        .apply_force_to_center(body, [1.0, 0.0, 0.0], true)
        .unwrap();
    world
        .apply_force(
            body,
            [0.0, 1.0, 0.0],
            world.body_position(body).unwrap(),
            true,
        )
        .unwrap();
    world.apply_torque(body, [0.0, 0.0, 1.0], true).unwrap();
    world
        .apply_linear_impulse_to_center(body, [1.0, 0.0, 0.0], true)
        .unwrap();
    world
        .apply_linear_impulse(
            body,
            [0.0, 1.0, 0.0],
            world.body_position(body).unwrap(),
            true,
        )
        .unwrap();
    world
        .apply_angular_impulse(body, [0.0, 0.0, 0.1], true)
        .unwrap();
    world.step(1.0 / 60.0, 4).unwrap();
    assert!(world.body_linear_velocity(body).unwrap().x > 0.0);

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

    let contact_body = world.create_body(BodyDef::default()).unwrap();
    world
        .create_sphere_shape(
            contact_body,
            &ShapeDef::default(),
            &Sphere::new(Vec3::ZERO, 0.5),
        )
        .unwrap();
    let touching_body = world
        .create_body(
            BodyDef::builder()
                .body_type(BodyType::Dynamic)
                .position([0.75, 0.0, 0.0])
                .gravity_scale(0.0)
                .build()
                .unwrap(),
        )
        .unwrap();
    world
        .create_sphere_shape(
            touching_body,
            &ShapeDef::builder().density(1.0).build().unwrap(),
            &Sphere::new(Vec3::ZERO, 0.5),
        )
        .unwrap();
    let mut contacts = Vec::new();
    for _ in 0..8 {
        world.step(1.0 / 60.0, 4).unwrap();
        contacts = world.body_contacts(contact_body).unwrap();
        if !contacts.is_empty() {
            break;
        }
    }
    assert!(!contacts.is_empty());

    let unrelated_joint = world
        .create_distance_joint(DistanceJointDef::new(contact_body, touching_body).length(0.75))
        .unwrap();
    let mut joints = vec![unrelated_joint];
    world.body_joints_into(body, &mut joints).unwrap();
    assert!(joints.is_empty());

    world.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()).unwrap();
    let _guard = boxddd::__private::enter_callback_guard_for_test();

    assert_eq!(
        world.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()).unwrap();

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

    assert!(!world.contains_body(foreign_body));
    assert!(other.contains_body(foreign_body));
    assert_eq!(
        world.body_position(foreign_body).unwrap_err(),
        Error::ForeignHandle {
            kind: HandleKind::Body,
        }
    );
    assert_eq!(
        world
            .body_closest_point(foreign_body, Vec3::ZERO)
            .unwrap_err(),
        Error::ForeignHandle {
            kind: HandleKind::Body,
        }
    );
    assert_eq!(
        world
            .body_overlap_shape(
                foreign_body,
                Vec3::ZERO,
                &ShapeProxy::sphere(0.5).unwrap(),
                QueryFilter::default()
            )
            .unwrap_err(),
        Error::ForeignHandle {
            kind: HandleKind::Body,
        }
    );
    assert_eq!(
        world.destroy_body(foreign_body).unwrap_err(),
        Error::ForeignHandle {
            kind: HandleKind::Body,
        }
    );
    assert!(other.contains_body(foreign_body));
    assert_eq!(
        world
            .create_sphere_shape(
                foreign_body,
                &ShapeDef::default(),
                &Sphere::new(Vec3::ZERO, 0.5)
            )
            .unwrap_err(),
        Error::ForeignHandle {
            kind: HandleKind::Body,
        }
    );
    assert!(!world.contains_shape(foreign_shape));
    assert!(other.contains_shape(foreign_shape));
    assert_eq!(
        world.shape_body(foreign_shape).unwrap_err(),
        Error::ForeignHandle {
            kind: HandleKind::Shape,
        }
    );
    assert_eq!(
        world.destroy_shape(foreign_shape, true).unwrap_err(),
        Error::ForeignHandle {
            kind: HandleKind::Shape,
        }
    );
    assert!(other.contains_shape(foreign_shape));

    world.destroy_body(local).unwrap();
}