boxddd 0.3.0

Safe, ergonomic Rust bindings for Box3D
Documentation
use boxddd::error::InvalidValueReason;
use boxddd::{
    BodyDef, BodyType, BoxHull, Capsule, Error, ExplosionDef, QueryFilter, ShapeDef, Sphere, Vec3,
    World, WorldDef,
};

#[test]
fn cast_mover_returns_fraction_for_simple_static_scene() {
    let mut world = World::new(WorldDef::default()).unwrap();
    let body = world
        .create_body(
            BodyDef::builder()
                .body_type(BodyType::Static)
                .build()
                .unwrap(),
        )
        .unwrap();
    world
        .create_hull_shape(
            body,
            &ShapeDef::default(),
            &BoxHull::new(1.0, 0.5, 1.0).unwrap(),
        )
        .unwrap();
    let mover = Capsule::new([0.0, 2.0, 0.0], [0.0, 3.0, 0.0], 0.25);
    let fraction = world
        .cast_mover(
            [0.0, 0.0, 0.0],
            &mover,
            [0.0, -4.0, 0.0],
            QueryFilter::default(),
        )
        .unwrap();
    assert!((0.0..=1.0).contains(&fraction));
}

#[test]
fn collide_mover_returns_world_and_body_planes() {
    let mut world = World::new(WorldDef::default()).unwrap();
    let body = world
        .create_body(
            BodyDef::builder()
                .body_type(BodyType::Static)
                .build()
                .unwrap(),
        )
        .unwrap();
    let shape = world
        .create_hull_shape(
            body,
            &ShapeDef::default(),
            &BoxHull::new(0.5, 0.5, 0.5).unwrap(),
        )
        .unwrap();
    let mover = Capsule::new([-0.3, 0.6, 0.0], [0.3, 0.6, 0.0], 0.2);

    let world_planes = world
        .collide_mover([0.0, 0.0, 0.0], &mover, QueryFilter::default())
        .unwrap();
    assert!(world_planes.iter().any(|plane| plane.shape_id == shape));

    let body_planes = world
        .body_collide_mover(body, [0.0, 0.0, 0.0], &mover, QueryFilter::default())
        .unwrap();
    assert!(body_planes.iter().any(|plane| plane.shape_id == shape));
}

#[test]
fn collide_mover_callback_panic_is_reported() {
    let mut world = World::new(WorldDef::default()).unwrap();
    let body = world
        .create_body(
            BodyDef::builder()
                .body_type(BodyType::Static)
                .build()
                .unwrap(),
        )
        .unwrap();
    world
        .create_hull_shape(
            body,
            &ShapeDef::default(),
            &BoxHull::new(0.5, 0.5, 0.5).unwrap(),
        )
        .unwrap();
    let mover = Capsule::new([-0.3, 0.6, 0.0], [0.3, 0.6, 0.0], 0.2);

    assert_eq!(
        world
            .visit_collide_mover([0.0, 0.0, 0.0], &mover, QueryFilter::default(), |_| {
                panic!("callback panic should be contained");
            })
            .unwrap_err(),
        Error::CallbackPanicked
    );
}

#[test]
fn explode_changes_near_dynamic_body_velocity_and_rejects_invalid_input() {
    let mut world = World::new(WorldDef::builder().gravity(Vec3::ZERO).build().unwrap()).unwrap();
    let near_body = world
        .create_body(
            BodyDef::builder()
                .body_type(BodyType::Dynamic)
                .position([1.0, 0.0, 0.0])
                .build()
                .unwrap(),
        )
        .unwrap();
    world
        .create_sphere_shape(
            near_body,
            &ShapeDef::builder().density(1.0).build().unwrap(),
            &Sphere::new(Vec3::ZERO, 0.5),
        )
        .unwrap();
    let far_body = world
        .create_body(
            BodyDef::builder()
                .body_type(BodyType::Dynamic)
                .position([10.0, 0.0, 0.0])
                .build()
                .unwrap(),
        )
        .unwrap();
    world
        .create_sphere_shape(
            far_body,
            &ShapeDef::builder().density(1.0).build().unwrap(),
            &Sphere::new(Vec3::ZERO, 0.5),
        )
        .unwrap();

    let explosion = ExplosionDef::builder()
        .position(Vec3::ZERO)
        .radius(2.0)
        .falloff(0.0)
        .impulse_per_area(20.0)
        .build()
        .unwrap();
    world.explode(&explosion).unwrap();

    assert!(world.body_linear_velocity(near_body).unwrap().x > 0.0);
    assert_eq!(world.body_linear_velocity(far_body).unwrap(), Vec3::ZERO);

    assert_eq!(
        ExplosionDef::builder().radius(-1.0).build().unwrap_err(),
        Error::InvalidValue {
            context: "explosion.radius",
            reason: InvalidValueReason::OutOfRange,
        }
    );
}