bevy_boxdd 0.5.0

Bevy integration for the boxdd Rust bindings to Box2D
Documentation
use std::collections::HashSet;

use bevy::ecs::system::NonSendMut;
use bevy::log::warn;
use bevy::prelude::*;
use bevy_boxdd::prelude::*;

const PROBE_HALF_EXTENTS: Vec2 = Vec2::new(0.8, 0.55);

#[derive(Component)]
struct Pickup;

#[derive(Component)]
struct Probe;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(BoxddPhysicsPlugin::new(BoxddPhysicsSettings {
            gravity: Vec2::ZERO,
            ..Default::default()
        }))
        .add_systems(Startup, setup)
        .add_systems(Update, (move_probe, highlight_pickups).chain())
        .run();
}

fn setup(mut commands: Commands) {
    commands.spawn(Camera2d);

    commands.spawn((
        Probe,
        Sprite::from_color(Color::srgba(0.9, 0.9, 1.0, 0.18), PROBE_HALF_EXTENTS * 2.0),
        Transform::from_xyz(-2.5, 0.0, 10.0),
    ));

    for (x, y) in [(-2.0, 0.2), (-0.6, -0.35), (0.8, 0.35), (2.1, -0.1)] {
        commands.spawn((
            Pickup,
            RigidBody::Static,
            Collider::circle(0.22),
            PhysicsMaterial {
                is_sensor: true,
                ..Default::default()
            },
            Sprite::from_color(Color::srgb(0.25, 0.55, 0.95), Vec2::splat(0.44)),
            Transform::from_xyz(x, y, 0.0),
        ));
    }
}

fn move_probe(time: Res<Time>, mut probe: Single<&mut Transform, With<Probe>>) {
    let x = (time.elapsed_secs() * 0.7).sin() * 2.6;
    probe.translation.x = x;
}

fn highlight_pickups(
    mut context: NonSendMut<BoxddPhysicsContext>,
    probe: Single<&Transform, With<Probe>>,
    mut hits: Local<Vec<BoxddShapeHit>>,
    mut pickups: Query<(Entity, &mut Sprite), With<Pickup>>,
) {
    let center = probe.translation.truncate();
    let aabb = boxdd::Aabb::from_center_half_extents(
        center.to_boxdd_vec2(),
        PROBE_HALF_EXTENTS.to_boxdd_vec2(),
    );

    if let Err(error) =
        context.try_overlap_aabb_entities_into(aabb, boxdd::QueryFilter::default(), &mut hits)
    {
        warn!(?error, "overlap query failed");
        return;
    }

    let active = hits
        .iter()
        .filter_map(|hit| hit.entity)
        .collect::<HashSet<_>>();
    for (entity, mut sprite) in &mut pickups {
        sprite.color = if active.contains(&entity) {
            Color::srgb(1.0, 0.8, 0.2)
        } else {
            Color::srgb(0.25, 0.55, 0.95)
        };
    }
}