Skip to main content

avian3d/collision/collider/collider_transform/
plugin.rs

1use crate::{
2    ancestor_marker::{AncestorMarker, AncestorMarkerPlugin},
3    physics_transform::PhysicsTransformSystems,
4    prelude::*,
5    utils::{MIN_PAR_ITER_ENTITIES, ParallelQueryForEach},
6};
7use bevy::{
8    ecs::{intern::Interned, schedule::ScheduleLabel},
9    prelude::*,
10};
11
12/// A plugin for propagating and updating transforms for colliders.
13///
14/// - Propagates [`Transform`] to [`GlobalTransform`] and [`ColliderTransform`].
15/// - Updates [`Position`] and [`Rotation`] for colliders.
16///
17/// This plugin requires that colliders have the [`ColliderMarker`] component,
18/// which is added automatically for colliders if the [`ColliderBackendPlugin`] is enabled.
19pub struct ColliderTransformPlugin {
20    schedule: Interned<dyn ScheduleLabel>,
21}
22
23impl ColliderTransformPlugin {
24    /// Creates a [`ColliderTransformPlugin`] with the schedule that is used for running the [`PhysicsSchedule`].
25    ///
26    /// The default schedule is `FixedPostUpdate`.
27    pub fn new(schedule: impl ScheduleLabel) -> Self {
28        Self {
29            schedule: schedule.intern(),
30        }
31    }
32}
33
34impl Default for ColliderTransformPlugin {
35    fn default() -> Self {
36        Self {
37            schedule: FixedPostUpdate.intern(),
38        }
39    }
40}
41
42impl Plugin for ColliderTransformPlugin {
43    fn build(&self, app: &mut App) {
44        // Mark ancestors of colliders with `AncestorMarker<ColliderMarker>`.
45        // This is used to speed up `ColliderTransform` propagation by skipping
46        // trees that have no colliders.
47        app.add_plugins(AncestorMarkerPlugin::<ColliderMarker>::default());
48
49        // Propagate `ColliderTransform`s if there are new colliders.
50        // Only traverses trees with `AncestorMarker<ColliderMarker>`.
51        app.add_systems(
52            self.schedule,
53            propagate_collider_transforms.in_set(PhysicsTransformSystems::Propagate),
54        );
55
56        let physics_schedule = app
57            .get_schedule_mut(PhysicsSchedule)
58            .expect("add PhysicsSchedule first");
59
60        // Update child collider positions before narrow phase collision detection.
61        // Only traverses trees with `AncestorMarker<ColliderMarker>`.
62        physics_schedule
63            .add_systems(update_child_collider_position.in_set(PhysicsStepSystems::First));
64    }
65}
66
67#[allow(clippy::type_complexity)]
68pub(crate) fn update_child_collider_position(
69    mut collider_query: Query<
70        (
71            &ColliderTransform,
72            &mut Position,
73            &mut Rotation,
74            &ColliderOf,
75        ),
76        Without<RigidBody>,
77    >,
78    rb_query: Query<(&Position, &Rotation), (With<RigidBody>, With<Children>)>,
79) {
80    for (collider_transform, mut position, mut rotation, collider_of) in &mut collider_query {
81        let Ok((rb_pos, rb_rot)) = rb_query.get(collider_of.body) else {
82            continue;
83        };
84
85        position.0 = rb_pos.0 + (rb_rot * collider_transform.translation).real();
86        #[cfg(feature = "2d")]
87        {
88            *rotation = (Rot2::from(*rb_rot) * collider_transform.rotation).into();
89        }
90        #[cfg(feature = "3d")]
91        {
92            *rotation = (rb_rot.0 * collider_transform.rotation).normalize().into();
93        }
94    }
95}
96
97// `ColliderTransform` propagation should only be continued if the child
98// is a collider or is a `AncestorMarker<ColliderMarker>`.
99type ShouldPropagate = Or<(With<AncestorMarker<ColliderMarker>>, With<ColliderMarker>)>;
100
101/// Updates [`ColliderTransform`]s based on entity hierarchies. Each transform is computed by recursively
102/// traversing the children of each rigid body and adding their transforms together to form
103/// the total transform relative to the body.
104///
105/// This is largely a clone of `propagate_transforms` in `bevy_transform`.
106#[allow(clippy::type_complexity)]
107pub(crate) fn propagate_collider_transforms(
108    mut root_query: Query<
109        (Entity, Ref<Transform>, &Children),
110        (Without<ChildOf>, With<AncestorMarker<ColliderMarker>>),
111    >,
112    collider_query: Query<
113        (
114            Ref<Transform>,
115            Option<&mut ColliderTransform>,
116            Option<&Children>,
117        ),
118        (With<ChildOf>, ShouldPropagate),
119    >,
120    parent_query: Query<(Entity, Ref<Transform>, Has<RigidBody>, Ref<ChildOf>), ShouldPropagate>,
121) {
122    root_query.par_for_each_mut(
123        MIN_PAR_ITER_ENTITIES,
124        |(entity, transform, children)| {
125            for (child, child_transform, is_child_rb, child_of) in parent_query.iter_many(children).matched() {
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).matched() {
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: transform.rotation * child_transform.rotation,
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.