Skip to content

Commit

Permalink
Rework deferred collider initialization (#378)
Browse files Browse the repository at this point in the history
# Objective

As we all know, `Collider` is not `Reflect`. This is a bit of a problem for people wanting to load collider shapes from code-external sources. Notably, people using @kaosat-dev 's excellent Blender-GLTF workflow as an editor need to setup their own `Reflect` version of `Collider` all the time, usually something like this:
```rust
#[derive(Debug, Component, Reflect)
#[reflect(Component)]
enum Collider {
  Cuboid {...},
  Sphere {...},
  Trimesh,
  ...
}
```
and then attach XPBD colliders to entities holding the above fake collider.
I've been thinking about opening a PR on the Blender workflow to do this automatically based on a cargo feature, but then I thought that maybe this would be better in XPBD / Avian proper so that other reflect-heavy workflows may profit from it as well. I'm thinking about scene structures, embedding colliders in BSN and future Bevy editor prototypes.

## Solution

My approach is to extend ComputedCollider to cover all variants of creating a collider. Some of these require a Mesh, some don't. When going through AsyncColliders, I can see if the specified creation method needs a Mesh. If so, the entity that the AsyncCollider is on must also hold a Handle<Mesh>


---

## Changelog

- Reworked deferred / "async" collider initialization. Renamed most involved types.
	- `ComputedCollider` and `AsyncCollider` have been combined into `ColliderConstructor` and support all possible collider shapes except compounds. 
		- `ColliderConstructor` is `Reflect`, `Serialize` and `Deserialize`. You can use it to statically configure your colliders when loading them from code external sources such as save files or serialized level definitions.
	  	- `ColliderConstructor` supports creating primitive shapes, so it does not need to be placed on an entity holding a mesh.
	  	- `ColliderConstructor` can be used in 2D when not using computed shapes
	- `AsyncSceneCollider` is now called `ColliderConstructorHierarchy` and no longer requires being placed on a `Scene`. It will generally generate a collider for all available descendants.
	  - Changes to `ColliderConstructor` also apply here
	  - If the new default feature `bevy_scene` is enabled, placing `ColliderConstructorHierarchy` on a `Scene` will still wait until the scene is loaded
	  - When the desired collider shape should be created from a mesh, only descendants holding a mesh will receive a collider
	  - This type is now especially useful for generating colliders on objects defined with the [Blender to Bevy workflow](https://github.com/kaosat-dev/Blender_bevy_components_workflow)
	  - `ColliderConstructorHierarchy::new` now takes an `impl Into<Option>` instead of an `Option`, so you can drop the `Some` from your calls if you want: `AsyncSceneCollider::new(Some(Foo))` becomes `ColliderConstructorHierarchy::new(Foo)`
	  - `ColliderConstructorHierarchy` can be used in 2D when not using computed shapes
	- `bevy_scene` can now be feature gated off while still using deferred colliders
- `bevy_gltf` is no longer pulled in

## Migration Guide

- Remove feature `async-collider`. If you need to use computed shapes, use the feature `collider-from-mesh`. If you depend on `ColliderConstructorHierarchy` waiting for a scene to load, use the feature `bevy_scene`
- Remove `AsyncCollider` and use `ColliderConstructor` directly
- Rename `AsyncSceneCollider ` to `ColliderConstructorHierarchy`
  - Rename `AsyncSceneCollider::default_shape` to `ColliderConstructorHierarchy::default_constructor`
  - Rename `AsyncSceneCollider::meshes_by_name` to `ColliderConstructorHierarchy::config`
  - Rename `AsyncSceneCollider::with_shape_for_name` to `ColliderConstructorHierarchy::with_constructor_for_name`
  - Rename `AsyncSceneCollider::without_shape_for_name` to `ColliderConstructorHierarchy::without_constructor_for_name`
- Rename `AsyncSceneColliderData` to `ColliderConstructorHierarchyConfig`
  - Rename `AsyncSceneColliderData::shape` to `ColliderConstructorHierarchyConfig::constructor`
- Rename `ComputedCollider` to `ColliderConstructor`.
  - Rename `ComputedCollider::TriMesh` to `ColliderConstructor::TrimeshFromMesh`
  - Rename `ComputedCollider::TriMeshWithFlags` to `ColliderConstructor::TrimeshFromMeshWithConfig`
  - Rename `ComputedCollider::ConvexHull` to `ColliderConstructor::ConvexHullFromMesh`
  - Rename `ComputedCollider::ConvexDecomposition` to `ColliderConstructor::ConvexDecompositionFromMeshWithConfig`
- Rename `VHACDParameters` to `VhacdParameters`
- Rename `Collider::halfspace` to `Collider::half_space`

## Linked Issues

- Indirectly resolves #309 by verifying that something like that is not really possible in the current version
- Resolves #306

---------

Co-authored-by: Joona Aalto <jondolf.dev@gmail.com>
  • Loading branch information
janhohenheim and Jondolf committed Jun 24, 2024
1 parent dae903b commit 5eba1c8
Show file tree
Hide file tree
Showing 14 changed files with 1,252 additions and 294 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ jobs:
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.78
- name: Run cargo test
run: cargo test --no-default-features --features enhanced-determinism,collider-from-mesh,serialize,bevy_xpbd_2d/2d,bevy_xpbd_3d/3d,bevy_xpbd_2d/f64,bevy_xpbd_3d/f64,default-collider,parry-f64
run: cargo test --no-default-features --features enhanced-determinism,collider-from-mesh,serialize,bevy_xpbd_2d/2d,bevy_xpbd_3d/3d,bevy_xpbd_2d/f64,bevy_xpbd_3d/f64,default-collider,parry-f64,bevy_scene

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

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

bevy_scene = ["bevy/bevy_scene"]
serialize = [
"dep:serde",
"bevy/serialize",
Expand All @@ -56,6 +64,7 @@ derive_more = "0.99"
indexmap = "2.0.0"
fxhash = "0.2.1"
itertools = "0.12"
bitflags = "2.5.0"

[dev-dependencies]
examples_common_2d = { path = "../examples_common_2d" }
Expand Down
19 changes: 11 additions & 8 deletions crates/bevy_xpbd_3d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ default = [
"3d",
"f32",
"parry-f32",
"async-collider",
"collider-from-mesh",
"bevy_scene",
"debug-plugin",
"parallel",
]
Expand All @@ -39,8 +40,8 @@ default-collider = ["dep:nalgebra"]
parry-f32 = ["f32", "dep:parry3d", "default-collider"]
parry-f64 = ["f64", "dep:parry3d-f64", "default-collider"]

collider-from-mesh = ["bevy/bevy_render"]
async-collider = ["bevy/bevy_scene", "bevy/bevy_gltf", "collider-from-mesh"]
collider-from-mesh = ["bevy/bevy_render", "3d"]
bevy_scene = ["bevy/bevy_scene"]
serialize = [
"dep:serde",
"bevy/serialize",
Expand All @@ -65,8 +66,10 @@ derive_more = "0.99"
indexmap = "2.0.0"
fxhash = "0.2.1"
itertools = "0.12"
bitflags = "2.5.0"

[dev-dependencies]
bevy = { version = "0.13", default-features = false, features = ["bevy_gltf"] }
examples_common_3d = { path = "../examples_common_3d" }
benches_common_3d = { path = "../benches_common_3d" }
bevy_math = { version = "0.13", features = ["approx"] }
Expand All @@ -76,11 +79,11 @@ insta = "1.0"

[[example]]
name = "dynamic_character_3d"
required-features = ["3d", "default-collider", "async-collider"]
required-features = ["3d", "default-collider", "bevy_scene"]

[[example]]
name = "kinematic_character_3d"
required-features = ["3d", "default-collider", "async-collider"]
required-features = ["3d", "default-collider", "bevy_scene"]

[[example]]
name = "cast_ray_predicate"
Expand Down Expand Up @@ -120,11 +123,11 @@ required-features = ["3d", "default-collider"]

[[example]]
name = "trimesh_shapes_3d"
required-features = ["3d", "default-collider"]
required-features = ["3d", "default-collider", "bevy_scene"]

[[example]]
name = "async_colliders"
required-features = ["3d", "default-collider", "async-collider"]
name = "collider_constructors"
required-features = ["3d", "default-collider", "bevy_scene"]

[[bench]]
name = "cubes"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! An example showcasing how to create colliders for meshes and scenes
//! using `AsyncCollider` and `AsyncSceneCollider` respectively.
//! using `ColliderConstructor` and `ColliderConstructorHierarchy` respectively.

use bevy::prelude::*;
use bevy_xpbd_3d::prelude::*;
Expand All @@ -18,18 +18,18 @@ fn setup(
mut meshes: ResMut<Assets<Mesh>>,
assets: ResMut<AssetServer>,
) {
// Spawn ground and generate a collider for the mesh using AsyncCollider
// Spawn ground and generate a collider for the mesh using ColliderConstructor
commands.spawn((
PbrBundle {
mesh: meshes.add(Plane3d::default().mesh().size(8.0, 8.0)),
material: materials.add(Color::rgb(0.3, 0.5, 0.3)),
..default()
},
AsyncCollider(ComputedCollider::TriMesh),
ColliderConstructor::TrimeshFromMesh,
RigidBody::Static,
));

// Spawn Ferris the crab and generate colliders for the scene using AsyncSceneCollider
// Spawn Ferris the crab and generate colliders for the scene using ColliderConstructorHierarchy
commands.spawn((
SceneBundle {
// The model was made by RayMarch, licenced under CC0-1.0, and can be found here:
Expand All @@ -41,12 +41,10 @@ fn setup(
// Create colliders using convex decomposition.
// This takes longer than creating a trimesh or convex hull collider,
// but is more performant for collision detection.
AsyncSceneCollider::new(Some(ComputedCollider::ConvexDecomposition(
VHACDParameters::default(),
)))
// Make the arms heavier to make it easier to stand upright
.with_density_for_name("armL_mesh", 3.0)
.with_density_for_name("armR_mesh", 3.0),
ColliderConstructorHierarchy::new(ColliderConstructor::ConvexDecompositionFromMesh)
// Make the arms heavier to make it easier to stand upright
.with_density_for_name("armL_mesh", 3.0)
.with_density_for_name("armR_mesh", 3.0),
RigidBody::Dynamic,
));

Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_xpbd_3d/examples/dynamic_character_3d/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ fn setup(
},
));

// Environment (see `async_colliders` example for creating colliders from scenes)
// Environment (see the `collider_constructors` example for creating colliders from scenes)
commands.spawn((
SceneBundle {
scene: assets.load("character_controller_demo.glb#Scene0"),
transform: Transform::from_rotation(Quat::from_rotation_y(-std::f32::consts::PI * 0.5)),
..default()
},
AsyncSceneCollider::new(Some(ComputedCollider::ConvexHull)),
ColliderConstructorHierarchy::new(ColliderConstructor::ConvexHullFromMesh),
RigidBody::Static,
));

Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_xpbd_3d/examples/kinematic_character_3d/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ fn setup(
},
));

// Environment (see `async_colliders` example for creating colliders from scenes)
// Environment (see the `collider_constructors` example for creating colliders from scenes)
commands.spawn((
SceneBundle {
scene: assets.load("character_controller_demo.glb#Scene0"),
transform: Transform::from_rotation(Quat::from_rotation_y(-std::f32::consts::PI * 0.5)),
..default()
},
AsyncSceneCollider::new(Some(ComputedCollider::ConvexHull)),
ColliderConstructorHierarchy::new(ColliderConstructor::ConvexHullFromMesh),
RigidBody::Static,
));

Expand Down
1 change: 1 addition & 0 deletions crates/examples_common_3d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ bevy = { version = "0.13", default-features = false, features = [
"bevy_sprite",
"bevy_pbr",
"bevy_gizmos",
"bevy_gltf",
"default_font",
"tonemapping_luts",
"ktx2",
Expand Down
6 changes: 6 additions & 0 deletions src/components/mass_properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,12 @@ impl Default for ColliderDensity {
}
}

impl From<Scalar> for ColliderDensity {
fn from(density: Scalar) -> Self {
Self(density)
}
}

/// An automatically added component that contains the read-only mass properties of a [`Collider`].
/// The density used for computing the mass properties can be configured using the [`ColliderDensity`]
/// component.
Expand Down
8 changes: 3 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![allow(unexpected_cfgs, clippy::type_complexity, clippy::too_many_arguments)]
//! # Bevy XPBD
//!
//! **Bevy XPBD** is a 2D and 3D physics engine based on
Expand Down Expand Up @@ -59,10 +60,7 @@
feature = "3d",
doc = "| `collider-from-mesh` | Allows you to create [`Collider`]s from `Mesh`es. | Yes |"
)]
#![cfg_attr(
feature = "3d",
doc = "| `async-collider` | Allows you to generate [`Collider`]s from mesh handles and scenes. | 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 |
Expand Down Expand Up @@ -144,7 +142,7 @@
//! - [Sensors](Sensor)
#![cfg_attr(
feature = "3d",
doc = " - Creating colliders from meshes with [`AsyncCollider`] and [`AsyncSceneCollider`]"
doc = " - Creating colliders from meshes with [`ColliderConstructor`] and [`ColliderConstructorHierarchy`]"
)]
//! - [Get colliding entities](CollidingEntities)
//! - [Collision events](ContactReportingPlugin#collision-events)
Expand Down
Loading

0 comments on commit 5eba1c8

Please sign in to comment.