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.