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.