diff --git a/CHANGELOG.md b/CHANGELOG.md index ae873e9..edb1c46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ * Bump `bevy` to `0.12.x` * Bump `bevy_rapier3d` to `0.23.x` * Bump `bevy_inspector_egui` to `0.21.x` +* [bevy_xpbd](https://github.com/Jondolf/bevy_xpbd) collision support (#22): + * Added `xpbd_collisions` feature + * Added `xpbd_collision` example + * Split `collisions` into `rapier` and `xpbd` sub-modules * Fixed clippy warnings for rust 1.72.0 (#19) * Added rustfmt config (#19) * Added vertex colors in flag example (#19) diff --git a/Cargo.toml b/Cargo.toml index 4b848c3..da769e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ documentation = "https://docs.rs/bevy_silk" [features] default = [] rapier_collisions = ["bevy_rapier3d"] +xpbd_collisions = ["bevy_xpbd_3d"] [dependencies] # Error handling @@ -31,6 +32,12 @@ optional = true default-features = false features = ["dim3", "async-collider"] +[dependencies.bevy_xpbd_3d] +version = "0.3" +optional = true +default-features = false +features = ["3d", "f32", "async-collider"] + [dev-dependencies] bevy-inspector-egui = "0.21" bevy_rapier3d = "0.23" @@ -71,6 +78,11 @@ name = "rapier_collision" path = "examples/rapier_collision_example.rs" required-features = ["rapier_collisions"] +[[example]] +name = "xpbd_collision" +path = "examples/xpbd_collision_example.rs" +required-features = ["xpbd_collisions"] + [[example]] name = "anchors" path = "examples/anchors_example.rs" diff --git a/README.md b/README.md index a8a764c..57480b5 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,7 @@ fn spawn(mut commands: Commands) { ``` Custom anchoring allows to : + - pin vertices to various entities, like skeletal mesh joints - define custom offsets to customize the distance between the anchored vertices an the target @@ -188,6 +189,7 @@ configuration. You may add wind forces to the simulation for a more dynamic clothing effect, for each force you may choose from: + - `Wind::Constant` for constant wind force - `Wind::SinWave` for a sin wave following wind intensity with custom force and frequency. @@ -219,9 +221,16 @@ fn main() { ## Collisions -Enabling the `rapier_collisions` features enable cloth interaction with -other colliders. Add the `bevy_rapier3d::RapierPhysicsPlugin` to your app -and a `ClothCollider` to your entity to enable collisions: +Both [`bevy_rapier`] and [`bevy_xpbd`] are supported for cloth interactions with colliders. +They can be enabled with the `rapier_collisions` and `xpbd_collisions` features respectively. + +> Note: Collision support is still experimental for now and is not suited +> for production use. Feedback is welcome! + +### `bevy_rapier` + +Add `bevy_rapier3d::RapierPhysicsPlugin` to your app and a `ClothCollider` +to your entity to enable collisions: ```rust use bevy::prelude::*; @@ -231,7 +240,7 @@ fn spawn(mut commands: Commands) { commands.spawn(( PbrBundle { // Add your mesh, material and your custom PBR data - ..Default::default() + ..default() }, ClothBuilder::new(), ClothCollider::default(), @@ -239,17 +248,47 @@ fn spawn(mut commands: Commands) { } ``` -Three [`bevy_rapier`](https://github.com/dimforge/bevy_rapier) components will be automatically inserted: +Three `bevy_rapier` components will be automatically inserted: + - a `RigidBody::KinematicPositionBased` - a `Collider` which will be updated every frame to follow the cloth bounds (AABB) - a `SolverGroup` set to 0 (`Group::NONE`) in everything, avoiding default - collision solving. + collision solving. -You can customize what collisions will be checked through a `CollisionGroups` (See the [rapier docs](https://rapier.rs/docs/user_guides/bevy_plugin/colliders#collision-groups-and-solver-groups)). +You can customize what collisions will be checked by specifying `CollisionGroups`. +(See the [`bevy_rapier` docs](https://rapier.rs/docs/user_guides/bevy_plugin/colliders#collision-groups-and-solver-groups)). -> Note: Collision support is still experimental for now and is not suited -> for production use. Feedback is welcome ! +### `bevy_xpbd` + +Add `bevy_xpbd_3d::PhysicsPlugins` to your app and a `ClothCollider` +to your entity to enable collisions: + +```rust +use bevy::prelude::*; +use bevy_silk::prelude::*; + +fn spawn(mut commands: Commands) { + commands.spawn(( + PbrBundle { + // Add your mesh, material and your custom PBR data + ..default() + }, + ClothBuilder::new(), + ClothCollider::default(), + )); +} +``` + +Three `bevy_xpbd` components will be automatically inserted: + +- a `RigidBody::Kinematic` +- a `Collider` which will be updated every frame to follow the cloth bounds + (AABB) +- a `Sensor` used for avoiding default collision solving. + +You can customize what collisions will be checked by specifying `CollisionLayers`. +(See the [`bevy_xpbd` docs](https://docs.rs/bevy_xpbd_3d/latest/bevy_xpbd_3d/components/struct.CollisionLayers.html)). ## Mesh utils @@ -296,13 +335,17 @@ run `cargo run --example balloon` run `cargo run --example moving` -4. [Rapier] Collision example +4. [`bevy_rapier`] collision example run `cargo run --example rapier_collision --features rapier_collisions` -5. Anchors example +5. [`bevy_xpbd`] collision example + +run `cargo run --example xpbd_collision --features xpbd_collisions` + +6. Anchors example run `cargo run --example anchors` -[Rapier]: https://github.com/dimforge/bevy_rapier -[Heron]: https://github.com/jcornaz/heron +[`bevy_rapier`]: https://github.com/dimforge/bevy_rapier +[`bevy_xpbd`]: https://github.com/Jondolf/bevy_xpbd diff --git a/examples/xpbd_collision_example.rs b/examples/xpbd_collision_example.rs new file mode 100644 index 0000000..8dba330 --- /dev/null +++ b/examples/xpbd_collision_example.rs @@ -0,0 +1,164 @@ +use std::time::Duration; + +use bevy::{prelude::*, time::common_conditions::on_timer}; +use bevy_inspector_egui::quick::{ResourceInspectorPlugin, WorldInspectorPlugin}; +use bevy_silk::prelude::*; +use bevy_xpbd_3d::prelude::*; +use rand::{thread_rng, Rng}; + +mod camera_plugin; + +#[derive(Debug, Resource)] +struct ClothMovement { + sign: f32, + t: f32, +} + +fn main() { + App::new() + .insert_resource(AmbientLight { + color: Color::WHITE, + brightness: 1.0, + }) + .add_plugins(DefaultPlugins) + .add_plugins(PhysicsPlugins::default()) + .add_plugins(ResourceInspectorPlugin::::new()) + .add_plugins(WorldInspectorPlugin::default()) + .add_plugins(ClothPlugin) + .add_plugins(camera_plugin::CameraPlugin) + .insert_resource(ClothMovement { sign: -1.0, t: 0.0 }) + .add_systems(Startup, (spawn_cloth, setup)) + .add_systems( + Update, + ( + shoot_balls.run_if(on_timer(Duration::from_secs(6))), + move_cloth, + ), + ) + .run(); +} + +fn setup( + mut commands: Commands, + mut materials: ResMut>, + mut meshes: ResMut>, +) { + commands.spawn(DirectionalLightBundle { + transform: Transform::from_rotation(Quat::from_rotation_y(5.0)), + ..Default::default() + }); + let mesh_handle = meshes.add(shape::Cube::new(2.0).into()); + [ + (Color::BLUE, [-10.0, 0.0]), + (Color::GREEN, [10.0, 0.0]), + (Color::YELLOW, [0.0, -10.0]), + (Color::RED, [0.0, 10.0]), + ] + .map(|(color, [x, z])| { + commands.spawn(( + PbrBundle { + mesh: mesh_handle.clone(), + transform: Transform::from_xyz(x, 1.0, z), + material: materials.add(StandardMaterial { + base_color: color, + double_sided: true, + ..Default::default() + }), + ..Default::default() + }, + Collider::cuboid(2.0, 2.0, 2.0), + RigidBody::Static, + )); + }); + commands.spawn(( + PbrBundle { + mesh: meshes.add(shape::Cube { size: 24.0 }.into()), + material: materials.add(Color::WHITE.into()), + transform: Transform::from_xyz(0.0, -12.0, 0.0), + ..Default::default() + }, + Collider::cuboid(24.0, 24.0, 24.0), + RigidBody::Static, + )); +} + +fn spawn_cloth( + mut commands: Commands, + mut materials: ResMut>, + mut meshes: ResMut>, + asset_server: Res, +) { + let flag_texture = asset_server.load("Bevy.png"); + let (size_x, size_y) = (60, 40); + let mesh = rectangle_mesh((size_x, size_y), (-Vec3::X * 0.5, -Vec3::Y * 0.5), Vec3::Z); + let cloth = ClothBuilder::new() + .with_pinned_vertex_ids(0..size_x) + .with_stick_generation(StickGeneration::Triangles); + commands.spawn(( + PbrBundle { + mesh: meshes.add(mesh), + material: materials.add(StandardMaterial { + base_color_texture: Some(flag_texture), + cull_mode: None, // Option required to render back faces correctly + double_sided: true, // Option required to render back faces correctly + ..Default::default() + }), + transform: Transform::from_xyz(15.0, 15.0, 15.0), + ..Default::default() + }, + cloth, + ClothCollider { + dampen_others: Some(0.02), + ..Default::default() + }, + Name::new("Cloth"), + )); +} + +fn move_cloth( + time: Res