avian2d/collision/collider/collider_transform/
plugin.rs

1use crate::{
2    ancestor_marker::{AncestorMarker, AncestorMarkerPlugin},
3    physics_transform::PhysicsTransformSystems,
4    prelude::*,
5};
6use bevy::{
7    ecs::{intern::Interned, schedule::ScheduleLabel},
8    prelude::*,
9};
10
11/// A plugin for propagating and updating transforms for colliders.
12///
13/// - Propagates [`Transform`] to [`GlobalTransform`] and [`ColliderTransform`].
14/// - Updates [`Position`] and [`Rotation`] for colliders.
15///
16/// This plugin requires that colliders have the [`ColliderMarker`] component,
17/// which is added automatically for colliders if the [`ColliderBackendPlugin`] is enabled.
18pub struct ColliderTransformPlugin {
19    schedule: Interned<dyn ScheduleLabel>,
20}
21
22impl ColliderTransformPlugin {
23    /// Creates a [`ColliderTransformPlugin`] with the schedule that is used for running the [`PhysicsSchedule`].
24    ///
25    /// The default schedule is `FixedPostUpdate`.
26    pub fn new(schedule: impl ScheduleLabel) -> Self {
27        Self {
28            schedule: schedule.intern(),
29        }
30    }
31}
32
33impl Default for ColliderTransformPlugin {
34    fn default() -> Self {
35        Self {
36            schedule: FixedPostUpdate.intern(),
37        }
38    }
39}
40
41impl Plugin for ColliderTransformPlugin {
42    fn build(&self, app: &mut App) {
43        // Mark ancestors of colliders with `AncestorMarker<ColliderMarker>`.
44        // This is used to speed up `ColliderTransform` propagation by skipping
45        // trees that have no colliders.
46        app.add_plugins(AncestorMarkerPlugin::<ColliderMarker>::default());
47
48        // Propagate `ColliderTransform`s if there are new colliders.
49        // Only traverses trees with `AncestorMarker<ColliderMarker>`.
50        app.add_systems(
51            self.schedule,
52            propagate_collider_transforms.in_set(PhysicsTransformSystems::Propagate),
53        );
54
55        let physics_schedule = app
56            .get_schedule_mut(PhysicsSchedule)
57            .expect("add PhysicsSchedule first");
58
59        // Update child collider positions before narrow phase collision detection.
60        // Only traverses trees with `AncestorMarker<ColliderMarker>`.
61        physics_schedule
62            .add_systems(update_child_collider_position.in_set(PhysicsStepSystems::First));
63    }
64}
65
66#[allow(clippy::type_complexity)]
67pub(crate) fn update_child_collider_position(
68    mut collider_query: Query<
69        (
70            &ColliderTransform,
71            &mut Position,
72            &mut Rotation,
73            &ColliderOf,
74        ),
75        Without<RigidBody>,
76    >,
77    rb_query: Query<(&Position, &Rotation), (With<RigidBody>, With<Children>)>,
78) {
79    for (collider_transform, mut position, mut rotation, collider_of) in &mut collider_query {
80        let Ok((rb_pos, rb_rot)) = rb_query.get(collider_of.body) else {
81            continue;
82        };
83
84        position.0 = rb_pos.0 + rb_rot * collider_transform.translation;
85        #[cfg(feature = "2d")]
86        {
87            *rotation = *rb_rot * collider_transform.rotation;
88        }
89        #[cfg(feature = "3d")]
90        {
91            *rotation = (rb_rot.0 * collider_transform.rotation.0)
92                .normalize()
93                .into();
94        }
95    }
96}
97
98// `ColliderTransform` propagation should only be continued if the child
99// is a collider or is a `AncestorMarker<ColliderMarker>`.
100type ShouldPropagate = Or<(With<AncestorMarker<ColliderMarker>>, With<ColliderMarker>)>;
101
102/// Updates [`ColliderTransform`]s based on entity hierarchies. Each transform is computed by recursively
103/// traversing the children of each rigid body and adding their transforms together to form
104/// the total transform relative to the body.
105///
106/// This is largely a clone of `propagate_transforms` in `bevy_transform`.
107#[allow(clippy::type_complexity)]
108pub(crate) fn propagate_collider_transforms(
109    mut root_query: Query<
110        (Entity, Ref<Transform>, &Children),
111        (Without<ChildOf>, With<AncestorMarker<ColliderMarker>>),
112    >,
113    collider_query: Query<
114        (
115            Ref<Transform>,
116            Option<&mut ColliderTransform>,
117            Option<&Children>,
118        ),
119        (With<ChildOf>, ShouldPropagate),
120    >,
121    parent_query: Query<(Entity, Ref<Transform>, Has<RigidBody>, Ref<ChildOf>), ShouldPropagate>,
122) {
123    root_query.par_iter_mut().for_each(
124        |(entity, transform, children)| {
125            for (child, child_transform, is_child_rb, child_of) in parent_query.iter_many(children) {
126                assert_eq!(
127                    child_of.parent(), entity,
128                    "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
129                );
130                let changed = transform.is_changed() || child_of.is_changed();
131                let parent_transform = ColliderTransform::from(*transform);
132                let child_transform = ColliderTransform::from(*child_transform);
133                let scale = parent_transform.scale * child_transform.scale;
134
135                // SAFETY:
136                // - `child` must have consistent parentage, or the above assertion would panic.
137                // Since `child` is parented to a root entity, the entire hierarchy leading to it is consistent.
138                // - We may operate as if all descendants are consistent, since `propagate_collider_transform_recursive` will panic before
139                //   continuing to propagate if it encounters an entity with inconsistent parentage.
140                // - Since each root entity is unique and the hierarchy is consistent and forest-like,
141                //   other root entities' `propagate_collider_transform_recursive` calls will not conflict with this one.
142                // - Since this is the only place where `collider_query` gets used, there will be no conflicting fetches elsewhere.
143                unsafe {
144                    propagate_collider_transforms_recursive(
145                        if is_child_rb {
146                            ColliderTransform {
147                                scale,
148                                ..default()
149                            }
150                        } else {
151                            ColliderTransform {
152                                translation: parent_transform.scale * child_transform.translation,
153                                rotation: child_transform.rotation,
154                                scale,
155                            }
156                        },
157                        &collider_query,
158                        &parent_query,
159                        child,
160                        changed,
161                    );
162                }
163            }
164        },
165    );
166}
167
168/// Recursively computes the [`ColliderTransform`] for `entity` and all of its descendants
169/// by propagating transforms.
170///
171/// This is largely a clone of `propagate_recursive` in `bevy_transform`.
172///
173/// # Panics
174///
175/// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before propagating
176/// the transforms of any malformed entities and their descendants.
177///
178/// # Safety
179///
180/// - While this function is running, `collider_query` must not have any fetches for `entity`,
181///   nor any of its descendants.
182/// - The caller must ensure that the hierarchy leading to `entity`
183///   is well-formed and must remain as a tree or a forest. Each entity must have at most one parent.
184#[allow(clippy::type_complexity)]
185unsafe fn propagate_collider_transforms_recursive(
186    transform: ColliderTransform,
187    collider_query: &Query<
188        (
189            Ref<Transform>,
190            Option<&mut ColliderTransform>,
191            Option<&Children>,
192        ),
193        (With<ChildOf>, ShouldPropagate),
194    >,
195    parent_query: &Query<(Entity, Ref<Transform>, Has<RigidBody>, Ref<ChildOf>), ShouldPropagate>,
196    entity: Entity,
197    mut changed: bool,
198) {
199    let children = {
200        // SAFETY: This call cannot create aliased mutable references.
201        //   - The top level iteration parallelizes on the roots of the hierarchy.
202        //   - The caller ensures that each child has one and only one unique parent throughout the entire
203        //     hierarchy.
204        //
205        // For example, consider the following malformed hierarchy:
206        //
207        //     A
208        //   /   \
209        //  B     C
210        //   \   /
211        //     D
212        //
213        // D has two parents, B and C. If the propagation passes through C, but the ChildOf component on D points to B,
214        // the above check will panic as the origin parent does match the recorded parent.
215        //
216        // Also consider the following case, where A and B are roots:
217        //
218        //  A       B
219        //   \     /
220        //    C   D
221        //     \ /
222        //      E
223        //
224        // Even if these A and B start two separate tasks running in parallel, one of them will panic before attempting
225        // to mutably access E.
226        let Ok((transform_ref, collider_transform, children)) =
227            (unsafe { collider_query.get_unchecked(entity) })
228        else {
229            return;
230        };
231
232        changed |=
233            transform_ref.is_changed() || collider_transform.as_ref().is_some_and(|t| t.is_added());
234        if changed
235            && let Some(mut collider_transform) = collider_transform
236            && *collider_transform != transform
237        {
238            *collider_transform = transform;
239        }
240
241        children
242    };
243
244    let Some(children) = children else { return };
245    for (child, child_transform, is_rb, child_of) in parent_query.iter_many(children) {
246        assert_eq!(
247            child_of.parent(),
248            entity,
249            "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
250        );
251
252        let child_transform = ColliderTransform::from(*child_transform);
253        let scale = transform.scale * child_transform.scale;
254
255        // SAFETY: The caller guarantees that `collider_query` will not be fetched
256        // for any descendants of `entity`, so it is safe to call `propagate_collider_transforms_recursive` for each child.
257        //
258        // The above assertion ensures that each child has one and only one unique parent throughout the
259        // entire hierarchy.
260        unsafe {
261            propagate_collider_transforms_recursive(
262                if is_rb {
263                    ColliderTransform { scale, ..default() }
264                } else {
265                    ColliderTransform {
266                        translation: transform.transform_point(child_transform.translation),
267                        #[cfg(feature = "2d")]
268                        rotation: transform.rotation * child_transform.rotation,
269                        #[cfg(feature = "3d")]
270                        rotation: Rotation(transform.rotation.0 * child_transform.rotation.0),
271                        scale,
272                    }
273                },
274                collider_query,
275                parent_query,
276                child,
277                changed || child_of.is_changed(),
278            );
279        }
280    }
281}
282
283// TODO: Add thorough tests for propagation. It's pretty error-prone and changes are risky.