Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add physics picking backend using bevy_picking #554

Merged
merged 10 commits into from
Nov 11, 2024
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Run cargo test
run: cargo test --no-default-features --features enhanced-determinism,collider-from-mesh,serialize,debug-plugin,avian2d/2d,avian3d/3d,avian2d/f64,avian3d/f64,default-collider,parry-f64,bevy_scene
run: cargo test --no-default-features --features enhanced-determinism,collider-from-mesh,serialize,debug-plugin,avian2d/2d,avian3d/3d,avian2d/f64,avian3d/f64,default-collider,parry-f64,bevy_scene,bevy_picking

lints:
name: Lints
Expand Down
11 changes: 10 additions & 1 deletion crates/avian2d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ keywords = ["gamedev", "physics", "simulation", "bevy"]
categories = ["game-development", "science", "simulation"]

[features]
default = ["2d", "f32", "parry-f32", "debug-plugin", "parallel", "bevy_scene"]
default = [
"2d",
"f32",
"parry-f32",
"debug-plugin",
"parallel",
"bevy_scene",
"bevy_picking",
]
2d = []
f32 = []
f64 = []
Expand All @@ -34,6 +42,7 @@ parry-f32 = ["f32", "dep:parry2d", "default-collider"]
parry-f64 = ["f64", "dep:parry2d-f64", "default-collider"]

bevy_scene = ["bevy/bevy_scene"]
bevy_picking = ["bevy/bevy_picking"]
serialize = [
"dep:serde",
"bevy/serialize",
Expand Down
6 changes: 6 additions & 0 deletions crates/avian3d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ default = [
"parry-f32",
"collider-from-mesh",
"bevy_scene",
"bevy_picking",
"debug-plugin",
"parallel",
]
Expand All @@ -43,6 +44,7 @@ parry-f64 = ["f64", "dep:parry3d-f64", "default-collider"]

collider-from-mesh = ["bevy/bevy_render", "3d"]
bevy_scene = ["bevy/bevy_scene"]
bevy_picking = ["bevy/bevy_picking"]
serialize = [
"dep:serde",
"bevy/serialize",
Expand Down Expand Up @@ -128,6 +130,10 @@ required-features = ["3d", "default-collider"]
name = "revolute_joint_3d"
required-features = ["3d", "default-collider"]

[[example]]
name = "picking"
required-features = ["3d", "default-collider", "bevy_picking"]

[[example]]
name = "trimesh_shapes_3d"
required-features = ["3d", "default-collider", "bevy_scene"]
Expand Down
172 changes: 172 additions & 0 deletions crates/avian3d/examples/picking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
//! A simple 3D scene to demonstrate physics picking for colliders.
//!
//! By default, the [`PhysicsPickingPlugin`] will test intersections with the pointer against all colliders.
//! If you want physics picking to be opt-in, you can set [`PhysicsPickingSettings::require_markers`] to `true`
//! and add a [`PhysicsPickable`] component to the desired camera and target entities.

use std::f32::consts::PI;

use avian3d::{math::Vector, prelude::*};
use bevy::{color::palettes::tailwind::*, picking::pointer::PointerInteraction, prelude::*};

fn main() {
App::new()
.add_plugins((
DefaultPlugins,
PhysicsPlugins::default(),
// `PhysicsPickingPlugin` is not a default plugin
PhysicsPickingPlugin,
))
.add_systems(Startup, setup_scene)
.add_systems(Update, draw_pointer_intersections)
.run();
}

/// A marker component for our shapes so we can query them separately from the ground plane.
#[derive(Component)]
struct Shape;

const SHAPES_X_EXTENT: f32 = 12.0;
const Z_EXTENT: f32 = 5.0;

fn setup_scene(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Set up the materials.
let white_matl = materials.add(Color::WHITE);
let ground_matl = materials.add(Color::from(GRAY_300));
let hover_matl = materials.add(Color::from(CYAN_300));
let pressed_matl = materials.add(Color::from(YELLOW_300));

// Meshes and colliders for the shapes.
let shapes = [
(
meshes.add(Cuboid::default()),
Collider::from(Cuboid::default()),
),
(
meshes.add(Tetrahedron::default()),
Collider::convex_hull_from_mesh(&Tetrahedron::default().mesh().build()).unwrap(),
),
(
meshes.add(Capsule3d::default()),
Collider::from(Capsule3d::default()),
),
(
meshes.add(Torus::default()),
Collider::trimesh_from_mesh(&Torus::default().mesh().build()).unwrap(),
),
(
meshes.add(Cylinder::default()),
Collider::from(Cylinder::default()),
),
(meshes.add(Cone::default()), Collider::from(Cone::default())),
(
meshes.add(ConicalFrustum::default()),
Collider::trimesh_from_mesh(&ConicalFrustum::default().mesh().build()).unwrap(),
),
(
meshes.add(Sphere::default().mesh().ico(5).unwrap()),
Collider::from(Sphere::default()),
),
];

let num_shapes = shapes.len();

// Spawn the shapes. The colliders will be pickable by default.
for (i, (mesh, collider)) in shapes.into_iter().enumerate() {
commands
.spawn((
Mesh3d(mesh),
MeshMaterial3d(white_matl.clone()),
RigidBody::Kinematic,
collider,
AngularVelocity(Vector::new(0.0, 0.5, 0.0)),
Transform::from_xyz(
-SHAPES_X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * SHAPES_X_EXTENT,
2.0,
Z_EXTENT / 2.,
)
.with_rotation(Quat::from_rotation_x(-PI / 4.)),
Shape,
))
.observe(update_material_on::<Pointer<Over>>(hover_matl.clone()))
.observe(update_material_on::<Pointer<Out>>(white_matl.clone()))
.observe(update_material_on::<Pointer<Down>>(pressed_matl.clone()))
.observe(update_material_on::<Pointer<Up>>(hover_matl.clone()))
.observe(rotate_on_drag);
}

// Ground
commands.spawn((
Mesh3d(meshes.add(Plane3d::default().mesh().size(50.0, 50.0).subdivisions(10))),
MeshMaterial3d(ground_matl.clone()),
RigidBody::Static,
Collider::cuboid(50.0, 0.1, 50.0),
PickingBehavior::IGNORE, // Disable picking for the ground plane.
));

// Light
commands.spawn((
PointLight {
shadows_enabled: true,
intensity: 10_000_000.,
range: 100.0,
shadow_depth_bias: 0.2,
..default()
},
Transform::from_xyz(8.0, 16.0, 8.0),
));

// Camera
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 7., 14.0).looking_at(Vec3::new(0., 1., 0.), Vec3::Y),
));

// Instructions
commands.spawn((
Text::new("Hover over the shapes to pick them\nDrag to rotate"),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..default()
},
));
}

/// Returns an observer that updates the entity's material to the one specified.
fn update_material_on<E>(
new_material: Handle<StandardMaterial>,
) -> impl Fn(Trigger<E>, Query<&mut MeshMaterial3d<StandardMaterial>>) {
// An observer closure that captures `new_material`. We do this to avoid needing to write four
// versions of this observer, each triggered by a different event and with a different hardcoded
// material. Instead, the event type is a generic, and the material is passed in.
move |trigger, mut query| {
if let Ok(mut material) = query.get_mut(trigger.entity()) {
material.0 = new_material.clone();
}
}
}

/// A system that draws hit indicators for every pointer.
fn draw_pointer_intersections(pointers: Query<&PointerInteraction>, mut gizmos: Gizmos) {
for (point, normal) in pointers
.iter()
.filter_map(|interaction| interaction.get_nearest_hit())
.filter_map(|(_entity, hit)| hit.position.zip(hit.normal))
{
gizmos.sphere(point, 0.05, RED_500);
gizmos.arrow(point, point + normal.normalize() * 0.5, PINK_100);
}
}

/// An observer to rotate an entity when it is dragged.
fn rotate_on_drag(drag: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>) {
let mut transform = transforms.get_mut(drag.entity()).unwrap();
transform.rotate_y(drag.delta.x * 0.02);
transform.rotate_x(drag.delta.y * 0.02);
}
37 changes: 21 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,26 @@
//!
//! ### Feature flags
//!
//! | Feature | Description | Default feature |
//! | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
//! | `2d` | Enables 2D physics. Incompatible with `3d`. | Yes (`avian2d`) |
//! | `3d` | Enables 3D physics. Incompatible with `2d`. | Yes (`avian3d`) |
//! | `f32` | Enables `f32` precision for physics. Incompatible with `f64`. | Yes |
//! | `f64` | Enables `f64` precision for physics. Incompatible with `f32`. | No |
//! | `default-collider` | Enables the default [`Collider`]. Required for [spatial queries](spatial_query). Requires either the `parry-f32` or `parry-f64` feature. | Yes |
//! | `parry-f32` | Enables the `f32` version of the Parry collision detection library. Also enables the `default-collider` feature. | Yes |
//! | `parry-f64` | Enables the `f64` version of the Parry collision detection library. Also enables the `default-collider` feature. | No |
//! | Feature | Description | Default feature |
//! | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
//! | `2d` | Enables 2D physics. Incompatible with `3d`. | Yes (`avian2d`) |
//! | `3d` | Enables 3D physics. Incompatible with `2d`. | Yes (`avian3d`) |
//! | `f32` | Enables `f32` precision for physics. Incompatible with `f64`. | Yes |
//! | `f64` | Enables `f64` precision for physics. Incompatible with `f32`. | No |
//! | `default-collider` | Enables the default [`Collider`]. Required for [spatial queries](spatial_query). Requires either the `parry-f32` or `parry-f64` feature. | Yes |
//! | `parry-f32` | Enables the `f32` version of the Parry collision detection library. Also enables the `default-collider` feature. | Yes |
//! | `parry-f64` | Enables the `f64` version of the Parry collision detection library. Also enables the `default-collider` feature. | No |
#![cfg_attr(
feature = "3d",
doc = "| `collider-from-mesh` | Allows you to create [`Collider`]s from `Mesh`es. | Yes |"
doc = "| `collider-from-mesh` | Allows you to create [`Collider`]s from `Mesh`es. | Yes |"
)]
//! | `bevy_scene` | Enables [`ColliderConstructorHierarchy`] to wait until a [`Scene`] has loaded before processing it. | Yes |
//! | `debug-plugin` | Enables physics debug rendering using the [`PhysicsDebugPlugin`]. The plugin must be added separately. | Yes |
//! | `enhanced-determinism` | Enables increased determinism. | No |
//! | `parallel` | Enables some extra multithreading, which improves performance for larger simulations but can add some overhead for smaller ones. | Yes |
//! | `simd` | Enables [SIMD] optimizations. | No |
//! | `serialize` | Enables support for serialization and deserialization using Serde. | No |
//! | `bevy_scene` | Enables [`ColliderConstructorHierarchy`] to wait until a [`Scene`] has loaded before processing it. | Yes |
//! | `bevy_picking` | Enables physics picking support for `bevy_picking` using the [`PhysicsPickingPlugin`]. The plugin must be added separately. | Yes |
//! | `debug-plugin` | Enables physics debug rendering using the [`PhysicsDebugPlugin`]. The plugin must be added separately. | Yes |
//! | `enhanced-determinism` | Enables increased determinism. | No |
//! | `parallel` | Enables some extra multithreading, which improves performance for larger simulations but can add some overhead for smaller ones. | Yes |
//! | `simd` | Enables [SIMD] optimizations. | No |
//! | `serialize` | Enables support for serialization and deserialization using Serde. | No |
//!
//! [SIMD]: https://en.wikipedia.org/wiki/Single_instruction,_multiple_data
//!
Expand Down Expand Up @@ -451,6 +452,8 @@ pub mod collision;
pub mod debug_render;
pub mod dynamics;
pub mod math;
#[cfg(feature = "bevy_picking")]
pub mod picking;
pub mod position;
pub mod prepare;
pub mod schedule;
Expand All @@ -464,6 +467,8 @@ pub use type_registration::PhysicsTypeRegistrationPlugin;
pub mod prelude {
#[cfg(feature = "debug-plugin")]
pub use crate::debug_render::*;
#[cfg(feature = "bevy_picking")]
pub use crate::picking::{PhysicsPickable, PhysicsPickingPlugin, PhysicsPickingSettings};
#[cfg(feature = "default-collider")]
pub(crate) use crate::position::RotationValue;
pub use crate::{
Expand Down
Loading