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

AnyCollider context #527

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
10 changes: 9 additions & 1 deletion crates/avian2d/examples/custom_collider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,15 @@ impl CircleCollider {
}

impl AnyCollider for CircleCollider {
fn aabb(&self, position: Vector, _rotation: impl Into<Rotation>) -> ColliderAabb {
type Context = ();

fn aabb(
&self,
position: Vector,
_rotation: impl Into<Rotation>,
_entity: Entity,
_context: &Self::Context,
) -> ColliderAabb {
ColliderAabb::new(position, Vector::splat(self.radius))
}

Expand Down
49 changes: 42 additions & 7 deletions src/collision/collider/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
//!
//! See [`ColliderBackendPlugin`].

use std::any::type_name;
use std::marker::PhantomData;

use crate::{broad_phase::BroadPhaseSet, prelude::*, prepare::PrepareSet, sync::SyncConfig};
#[cfg(all(feature = "bevy_scene", feature = "default-collider"))]
use bevy::scene::SceneInstance;
use bevy::{
ecs::{intern::Interned, schedule::ScheduleLabel, system::SystemId},
ecs::{
intern::Interned,
schedule::ScheduleLabel,
system::{StaticSystemParam, SystemId, SystemState},
},
prelude::*,
};

Expand Down Expand Up @@ -85,12 +90,18 @@ impl<C: ScalableCollider> Default for ColliderBackendPlugin<C> {

impl<C: ScalableCollider> Plugin for ColliderBackendPlugin<C> {
fn build(&self, app: &mut App) {
#[derive(Resource)]
struct ContextState<C: ScalableCollider>(SystemState<C::Context>);

// Register the one-shot system that is run for all removed colliders.
if !app.world().contains_resource::<ColliderRemovalSystem>() {
let collider_removed_id = app.world_mut().register_system(collider_removed);
app.insert_resource(ColliderRemovalSystem(collider_removed_id));
}

let context_state = SystemState::new(app.world_mut());
app.insert_resource(ContextState::<C>(context_state));

let hooks = app.world_mut().register_component_hooks::<C>();

// Initialize missing components for colliders.
Expand Down Expand Up @@ -142,10 +153,31 @@ impl<C: ScalableCollider> Plugin for ColliderBackendPlugin<C> {
let entity_ref = world.entity(entity);
let collider = entity_ref.get::<C>().unwrap();

let aabb = entity_ref
.get::<ColliderAabb>()
.copied()
.unwrap_or(collider.aabb(Vector::ZERO, Rotation::default()));
let aabb = {
let mut context_state = {
let cell = world.as_unsafe_world_cell_readonly();
// SAFETY: No other code takes a ref to this resource,
// and `ContextState` is not publicly visible,
// so `C::Context` is unable to borrow this resource.
// This does not perform any structural world changes,
// so reading mutably through a read-only cell is OK.
// (We can't get a non-readonly cell from a DeferredWorld)
unsafe { cell.get_resource_mut::<ContextState<C>>() }
}
.unwrap_or_else(|| {
panic!(
"context state for `{}` was removed",
type_name::<C::Context>()
)
});
let context = context_state.0.get(&world);

entity_ref
.get::<ColliderAabb>()
.copied()
.unwrap_or(collider.aabb(Vector::ZERO, Rotation::default(), entity, &context))
};

let density = entity_ref
.get::<ColliderDensity>()
.copied()
Expand Down Expand Up @@ -533,6 +565,7 @@ fn pretty_name(name: Option<&Name>, entity: Entity) -> String {
fn update_aabb<C: AnyCollider>(
mut colliders: Query<
(
Entity,
&C,
&mut ColliderAabb,
&Position,
Expand All @@ -559,12 +592,14 @@ fn update_aabb<C: AnyCollider>(
narrow_phase_config: Res<NarrowPhaseConfig>,
length_unit: Res<PhysicsLengthUnit>,
time: Res<Time>,
context: StaticSystemParam<'_, '_, C::Context>,
) {
let delta_secs = time.delta_seconds_adjusted();
let default_speculative_margin = length_unit.0 * narrow_phase_config.default_speculative_margin;
let contact_tolerance = length_unit.0 * narrow_phase_config.contact_tolerance;

for (
entity,
collider,
mut aabb,
pos,
Expand All @@ -586,7 +621,7 @@ fn update_aabb<C: AnyCollider>(

if speculative_margin <= 0.0 {
*aabb = collider
.aabb(pos.0, *rot)
.aabb(pos.0, *rot, entity, &context)
.grow(Vector::splat(contact_tolerance + collision_margin));
continue;
}
Expand Down Expand Up @@ -642,7 +677,7 @@ fn update_aabb<C: AnyCollider>(
// Compute swept AABB, the space that the body would occupy if it was integrated for one frame
// TODO: Should we expand the AABB in all directions for speculative contacts?
*aabb = collider
.swept_aabb(start_pos.0, start_rot, end_pos, end_rot)
.swept_aabb(start_pos.0, start_rot, end_pos, end_rot, entity, &context)
.grow(Vector::splat(collision_margin));
}
}
Expand Down
83 changes: 79 additions & 4 deletions src/collision/collider/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

use crate::prelude::*;
use bevy::{
ecs::entity::{EntityMapper, MapEntities},
ecs::{
entity::{EntityMapper, MapEntities},
system::{ReadOnlySystemParam, SystemParam},
},
prelude::*,
utils::HashSet,
};
Expand Down Expand Up @@ -45,13 +48,80 @@ pub trait IntoCollider<C: AnyCollider> {
/// A trait that generalizes over colliders. Implementing this trait
/// allows colliders to be used with the physics engine.
pub trait AnyCollider: Component {
/// A type providing additional context for collider operations.
///
/// `Context` allows you to access an arbitrary [`ReadOnlySystemParam`] on
/// the world, for context-sensitive behavior in collider operations. You
/// can use this to query components on the collider entity, or get any
/// other necessary context from the world.
///
/// # Example
///
/// ```
/// use avian3d::prelude::*;
/// use avian3d::math::{Vector, Scalar};
/// use bevy::prelude::*;
/// use bevy::ecs::system::SystemParam;
///
/// #[derive(Component)]
/// pub struct VoxelData {
/// // collider voxel data...
/// }
///
/// #[derive(Component)]
/// pub struct VoxelCollider;
///
/// impl AnyCollider for VoxelCollider {
/// type Context = (
/// // use `'static` in place of lifetimes
/// Query<'static, 'static, &'static VoxelData>,
/// // you can put any read-only system param here
/// Res<'static, Time>,
/// );
///
/// # fn aabb(
/// # &self,
/// # _: Vector,
/// # _: impl Into<Rotation>,
/// # _: Entity,
/// # _: &<Self::Context as SystemParam>::Item<'_, '_>,
/// # ) -> ColliderAabb { unimplemented!() }
/// # fn mass_properties(&self, _: Scalar) -> ColliderMassProperties { unimplemented!() }
/// fn contact_manifolds(
/// &self,
/// other: &Self,
/// position1: Vector,
/// rotation1: impl Into<Rotation>,
/// position2: Vector,
/// rotation2: impl Into<Rotation>,
/// entity1: Entity,
/// entity2: Entity,
/// (voxel_data, time): &<Self::Context as SystemParam>::Item<'_, '_>,
/// prediction_distance: Scalar,
/// ) -> Vec<ContactManifold> {
/// let [voxels1, voxels2] = voxel_data.get_many([entity1, entity2])
/// .expect("our own `VoxelCollider` entities should have `VoxelData`");
/// let elapsed = time.elapsed();
/// // do some computation...
/// # unimplemented!()
/// }
/// }
/// ```
type Context: for<'w, 's> ReadOnlySystemParam<Item<'w, 's>: Send + Sync>;

/// Computes the [Axis-Aligned Bounding Box](ColliderAabb) of the collider
/// with the given position and rotation.
#[cfg_attr(
feature = "2d",
doc = "\n\nThe rotation is counterclockwise and in radians."
)]
fn aabb(&self, position: Vector, rotation: impl Into<Rotation>) -> ColliderAabb;
fn aabb(
&self,
position: Vector,
rotation: impl Into<Rotation>,
entity: Entity,
context: &<Self::Context as SystemParam>::Item<'_, '_>,
) -> ColliderAabb;

/// Computes the swept [Axis-Aligned Bounding Box](ColliderAabb) of the collider.
/// This corresponds to the space the shape would occupy if it moved from the given
Expand All @@ -66,9 +136,11 @@ pub trait AnyCollider: Component {
start_rotation: impl Into<Rotation>,
end_position: Vector,
end_rotation: impl Into<Rotation>,
entity: Entity,
context: &<Self::Context as SystemParam>::Item<'_, '_>,
) -> ColliderAabb {
self.aabb(start_position, start_rotation)
.merged(self.aabb(end_position, end_rotation))
self.aabb(start_position, start_rotation, entity, context)
.merged(self.aabb(end_position, end_rotation, entity, context))
}

/// Computes the collider's mass properties based on its shape and a given density.
Expand All @@ -85,6 +157,9 @@ pub trait AnyCollider: Component {
rotation1: impl Into<Rotation>,
position2: Vector,
rotation2: impl Into<Rotation>,
entity1: Entity,
entity2: Entity,
context: &<Self::Context as SystemParam>::Item<'_, '_>,
prediction_distance: Scalar,
) -> Vec<ContactManifold>;
}
Expand Down
15 changes: 13 additions & 2 deletions src/collision/collider/parry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use crate::{make_isometry, prelude::*};
#[cfg(feature = "collider-from-mesh")]
use bevy::render::mesh::{Indices, VertexAttributeValues};
use bevy::{log, prelude::*};
use bevy::{ecs::system::SystemParam, log, prelude::*};
use collision::contact_query::UnsupportedShape;
use itertools::Either;
use parry::shape::{RoundShape, SharedShape, TypedShape};
Expand Down Expand Up @@ -439,7 +439,15 @@ impl std::fmt::Debug for Collider {
}

impl AnyCollider for Collider {
fn aabb(&self, position: Vector, rotation: impl Into<Rotation>) -> ColliderAabb {
type Context = ();

fn aabb(
&self,
position: Vector,
rotation: impl Into<Rotation>,
_entity: Entity,
_context: &<Self::Context as SystemParam>::Item<'_, '_>,
) -> ColliderAabb {
let aabb = self
.shape_scaled()
.compute_aabb(&make_isometry(position, rotation));
Expand Down Expand Up @@ -477,6 +485,9 @@ impl AnyCollider for Collider {
rotation1: impl Into<Rotation>,
position2: Vector,
rotation2: impl Into<Rotation>,
_entity1: Entity,
_entity2: Entity,
_context: &<Self::Context as SystemParam>::Item<'_, '_>,
prediction_distance: Scalar,
) -> Vec<ContactManifold> {
contact_query::contact_manifolds(
Expand Down
6 changes: 5 additions & 1 deletion src/collision/narrow_phase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use bevy::{
ecs::{
intern::Interned,
schedule::{ExecutorKind, LogLevel, ScheduleBuildSettings, ScheduleLabel},
system::SystemParam,
system::{StaticSystemParam, SystemParam},
},
prelude::*,
};
Expand Down Expand Up @@ -357,6 +357,7 @@ pub struct NarrowPhase<'w, 's, C: AnyCollider> {
// These are scaled by the length unit.
default_speculative_margin: Local<'s, Scalar>,
contact_tolerance: Local<'s, Scalar>,
context: StaticSystemParam<'w, 's, <C as AnyCollider>::Context>,
}

impl<'w, 's, C: AnyCollider> NarrowPhase<'w, 's, C> {
Expand Down Expand Up @@ -542,6 +543,9 @@ impl<'w, 's, C: AnyCollider> NarrowPhase<'w, 's, C> {
*collider1.rotation,
position2,
*collider2.rotation,
collider1.entity,
collider2.entity,
&self.context,
max_distance,
);

Expand Down