bevy_mod_physx 0.9.0

PhysX plugin for Bevy
Documentation
mod common;

use std::sync::mpsc::channel;

use bevy::prelude::*;
use bevy_mod_physx::prelude::{self as bpx, *};
use bevy_mod_physx::types::OnCollision;
use bevy_mod_physx::utils::{get_actor_entity_from_ptr, get_shape_entity_from_ptr};
use physx::scene::FilterShaderDescriptor;
use physx_sys::{
    FilterShaderCallbackInfo,
    PxContactPairFlags,
    PxContactPair_extractContacts,
    PxFilterFlags,
    PxPairFlags,
};

#[derive(Resource)]
struct DemoMaterials {
    normal: Handle<StandardMaterial>,
    highlighted: Handle<StandardMaterial>,
}

#[derive(Component)]
struct Highlightable;

#[derive(Component)]
#[component(storage = "SparseSet")]
struct Highlighted;

unsafe extern "C" fn simulation_filter_shader(s: *mut FilterShaderCallbackInfo) -> PxFilterFlags {
    unsafe {
        let s = &mut *s as &mut FilterShaderCallbackInfo;
        let pair_flags = &mut *(s.pairFlags) as &mut PxPairFlags;

        *pair_flags = PxPairFlags::SolveContact |
            PxPairFlags::DetectDiscreteContact |
            PxPairFlags::NotifyTouchFound |
            PxPairFlags::NotifyTouchLost |
            PxPairFlags::NotifyContactPoints;

        PxFilterFlags::empty()
    }
}

#[derive(Message)]
pub struct CollisionEvent {
    actor0: Entity,
    actor1: Entity,
}

fn main() {
    let (mpsc_sender, mpsc_receiver) = channel();

    let on_collision = OnCollision::new(move |header, pairs| {
        assert!(!header.flags.contains(physx_sys::PxContactPairHeaderFlags::RemovedActor0));
        let actor0 = unsafe { get_actor_entity_from_ptr(header.actors[0] as *const _) };

        assert!(!header.flags.contains(physx_sys::PxContactPairHeaderFlags::RemovedActor1));
        let actor1 = unsafe { get_actor_entity_from_ptr(header.actors[1] as *const _) };

        for pair in pairs {
            // this example shows how to extract contact details
            assert!(!pair.flags.contains(PxContactPairFlags::RemovedShape0));
            let shape0 = unsafe { get_shape_entity_from_ptr(pair.shapes[0] as *const _) };

            assert!(!pair.flags.contains(PxContactPairFlags::RemovedShape0));
            let shape1 = unsafe { get_shape_entity_from_ptr(pair.shapes[1] as *const _) };

            let contacts = unsafe {
                let mut buffer = Vec::with_capacity(pairs.len());
                PxContactPair_extractContacts(pair, buffer.as_mut_ptr(), pairs.len() as u32);
                buffer.set_len(pairs.len());
                buffer
            };

            let mut status = "Contact";
            if pair.flags.contains(PxContactPairFlags::ActorPairHasFirstTouch) {
                status = "New contact";
            }
            if pair.flags.contains(PxContactPairFlags::ActorPairLostTouch) {
                status = "Lost contact";
            }

            println!("{status} between {shape0:?} and {shape1:?}:");
            for contact in contacts {
                println!(
                    "position: {:?}, separation: {:?}, normal: {:?}, impulse: {:?}",
                    contact.position.to_bevy(),
                    contact.separation,
                    contact.normal.to_bevy(),
                    contact.impulse.to_bevy(),
                );
            }
            println!("----------");
        }

        // this callback must not do anything with the scene,
        // we need to extract required data and send it through a channel into
        // a system that handles things
        mpsc_sender.send(CollisionEvent { actor0, actor1 }).unwrap();
    });

    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(PhysicsPlugins.set(
            PhysicsCore {
                scene: bpx::SceneDescriptor {
                    // simulation filter shader will filter details that we get in on_collision callback,
                    // by default on_collision callback doesn't do anything
                    simulation_filter_shader: FilterShaderDescriptor::CallDefaultFirst(simulation_filter_shader),

                    // callback is a closure, where we pass Sender to
                    on_collision: Some(on_collision),
                    ..default()
                },
                ..default()
            }.with_pvd()
        ))
        .add_plugins(common::DemoUtils) // optional
        .add_physics_event_channel(mpsc_receiver)
        .add_systems(Startup, (
            init_materials,
            ApplyDeferred,
            (
                spawn_plane,
                spawn_tiles,
                spawn_dynamic,
                spawn_camera_and_light,
            ),
        ).chain())
        .add_systems(Update, highlight_on_hit)
        .run();
}

fn init_materials(
    mut commands: Commands,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    commands.insert_resource(DemoMaterials {
        normal: materials.add(Color::srgb(0.8, 0.7, 0.6)),
        highlighted: materials.add(Color::srgb(0.3, 0.4, 0.9)),
    });
}

fn spawn_plane(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut physics: ResMut<Physics>,
    mut px_geometries: ResMut<Assets<bpx::Geometry>>,
    mut px_materials: ResMut<Assets<bpx::Material>>,
) {
    let primitive = Plane3d::default();
    let mesh = meshes.add(primitive.mesh().size(500., 500.));
    let material = materials.add(Color::srgb(0.3, 0.5, 0.3));
    let px_geometry = px_geometries.add(primitive);
    let px_material = px_materials.add(bpx::Material::new(&mut physics, 0.5, 0.5, 0.6));

    commands.spawn_empty()
        .insert((
            Mesh3d::from(mesh.clone()),
            MeshMaterial3d::from(material.clone()),
        ))
        .insert(bpx::RigidBody::Static)
        .insert(bpx::Shape {
            geometry: px_geometry,
            material: px_material,
            ..default()
        })
        .insert(Name::new("Plane"));
}

fn spawn_tiles(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    materials: Res<DemoMaterials>,
    mut px_geometries: ResMut<Assets<bpx::Geometry>>,
) {
    let num = 32;
    let rad = 1.0;
    let height = 0.1;
    let primitive = Cuboid::new(rad * 2., height * 2., rad * 2.);
    let px_geometry = px_geometries.add(primitive);
    let mesh = meshes.add(primitive);
    let material = materials.normal.clone();

    let shift = rad * 2.5;
    let centerx = shift * (num / 2) as f32;
    let centerz = shift * (num / 2) as f32;

    for i in 0..num {
        for j in 0..num {
            let x = i as f32 * shift - centerx;
            let y = height / 2.;
            let z = j as f32 * shift - centerz;

            commands.spawn((
                Mesh3d::from(mesh.clone()),
                MeshMaterial3d::from(material.clone()),
                Transform::from_xyz(x, y, z),
                RigidBody::Static,
                bpx::Shape {
                    geometry: px_geometry.clone(),
                    ..default()
                },
                Highlightable,
            ));
        }
    }
}

fn spawn_dynamic(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut physics: ResMut<Physics>,
    mut px_geometries: ResMut<Assets<bpx::Geometry>>,
    mut px_materials: ResMut<Assets<bpx::Material>>,
) {
    let primitive = Sphere::new(1.25);
    let mesh = meshes.add(primitive);
    let material = materials.add(Color::srgb(0.8, 0.7, 0.6));

    let px_geometry = px_geometries.add(primitive);
    let px_material = px_materials.add(bpx::Material::new(&mut physics, 0., 0., 1.));

    let transform = Transform::from_xyz(0., 5., 32.5);

    commands.spawn_empty()
        .insert((
            Mesh3d::from(mesh.clone()),
            MeshMaterial3d::from(material.clone()),
            transform,
        ))
        .insert(bpx::RigidBody::Dynamic)
        .insert(MassProperties::density(10.))
        .insert(bpx::Shape {
            material: px_material,
            geometry: px_geometry,
            ..default()
        })
        .insert(Velocity::linear(Vec3::new(2.5, -5., -10.)))
        .insert(Name::new("Ball"));
}

fn spawn_camera_and_light(mut commands: Commands) {
    commands
        .spawn((
            Name::new("Camera"),
            Transform::from_xyz(-21., 0., 0.),
            Visibility::default(),
        ))
        .with_children(|builder| {
            builder.spawn((
                Camera3d::default(),
                Transform::from_translation(Vec3::new(-41.7, 33., 0.)).looking_at(Vec3::ZERO, Vec3::Y),
            ));
        });

    commands.spawn((
        Name::new("Light"),
        DirectionalLight::default(),
        Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, -1.2, -0.2, 0.)),
    ));
}


fn highlight_on_hit(
    mut commands: Commands,
    materials: Res<DemoMaterials>,
    mut events: MessageReader<CollisionEvent>,
    highlighable: Query<(), With<Highlightable>>,
) {
    for event in events.read() {
        for entity in [ event.actor0, event.actor1 ] {
            if highlighable.get(entity).is_ok() {
                commands.entity(entity)
                    .insert(MeshMaterial3d::from(materials.highlighted.clone()))
                    .insert(Highlighted);
            }
        }
    }
}