avian2d/dynamics/solver/joint_graph/
plugin.rs

1use core::marker::PhantomData;
2
3use crate::{
4    collision::contact_types::ContactId,
5    data_structures::pair_key::PairKey,
6    dynamics::{
7        joints::EntityConstraint,
8        solver::{
9            constraint_graph::ConstraintGraph,
10            islands::{BodyIslandNode, IslandId, PhysicsIslands},
11            joint_graph::{JointGraph, JointGraphEdge},
12        },
13    },
14    prelude::{
15        ContactGraph, JointCollisionDisabled, JointDisabled, PhysicsSchedule, PhysicsStepSystems,
16        RigidBodyColliders, WakeIslands,
17    },
18};
19use bevy::{
20    ecs::{
21        component::ComponentId, entity_disabling::Disabled, lifecycle::HookContext,
22        query::QueryFilter, world::DeferredWorld,
23    },
24    prelude::*,
25};
26
27/// A plugin that manages the [`JointGraph`] for a specific [joint] type.
28///
29/// [joint]: crate::dynamics::joints
30pub struct JointGraphPlugin<T: Component + EntityConstraint<2>>(PhantomData<T>);
31
32impl<T: Component + EntityConstraint<2>> Default for JointGraphPlugin<T> {
33    fn default() -> Self {
34        Self(PhantomData)
35    }
36}
37
38/// A component that holds the [`ComponentId`] of the [joint] component on this entity, if any.
39///
40/// [joint]: crate::dynamics::joints
41#[derive(Component, Clone, Debug, Default, PartialEq, Reflect)]
42pub struct JointComponentId(Option<ComponentId>);
43
44impl JointComponentId {
45    /// Creates a new [`JointComponentId`] component with no active joint.
46    pub fn new() -> Self {
47        Self(None)
48    }
49
50    /// Returns the [`ComponentId`] of the active joint component, if any.
51    pub fn id(&self) -> Option<ComponentId> {
52        self.0
53    }
54}
55
56#[derive(Resource, Default)]
57struct JointGraphPluginInitialized;
58
59impl<T: Component + EntityConstraint<2>> Plugin for JointGraphPlugin<T> {
60    fn build(&self, app: &mut App) {
61        let already_initialized = app
62            .world()
63            .is_resource_added::<JointGraphPluginInitialized>();
64
65        app.init_resource::<JointGraph>();
66        app.init_resource::<JointGraphPluginInitialized>();
67
68        // Automatically add the `JointComponentId` component when the joint is added.
69        app.register_required_components::<T, JointComponentId>();
70
71        // Register hooks for adding and removing joints.
72        app.world_mut()
73            .register_component_hooks::<T>()
74            .on_add(on_add_joint)
75            .on_remove(on_remove_joint);
76
77        // Add the joint to the joint graph when it is added and the joint is not disabled.
78        app.add_observer(
79            add_joint_to_graph::<T, Add, T, (With<JointComponentId>, Without<JointDisabled>)>,
80        );
81
82        // Remove the joint from the joint graph when it is removed.
83        app.add_observer(remove_joint_from_graph::<Remove, T>);
84
85        if !already_initialized {
86            // Remove the joint from the joint graph when it is disabled.
87            app.add_observer(remove_joint_from_graph::<Add, (Disabled, JointDisabled)>);
88
89            // Remove contacts between bodies when the `JointCollisionDisabled` component is added.
90            app.add_observer(on_disable_joint_collision);
91        }
92
93        // Add the joint back to the joint graph when `Disabled` is removed.
94        app.add_observer(
95            add_joint_to_graph::<
96                T,
97                Remove,
98                Disabled,
99                (
100                    With<JointComponentId>,
101                    Or<(With<Disabled>, Without<Disabled>)>,
102                    Without<JointDisabled>,
103                ),
104            >,
105        );
106
107        // Add the joint back to the joint graph when `JointDisabled` is removed.
108        app.add_observer(add_joint_to_graph::<T, Remove, JointDisabled, With<JointComponentId>>);
109
110        app.add_systems(
111            PhysicsSchedule,
112            on_change_joint_entities::<T>
113                .in_set(PhysicsStepSystems::First)
114                .ambiguous_with(PhysicsStepSystems::First),
115        );
116    }
117}
118
119fn add_joint_to_graph<
120    T: Component + EntityConstraint<2>,
121    E: EntityEvent,
122    B: Bundle,
123    F: QueryFilter,
124>(
125    trigger: On<E, B>,
126    query: Query<(&T, Has<JointCollisionDisabled>), F>,
127    mut commands: Commands,
128    mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
129    mut contact_graph: ResMut<ContactGraph>,
130    mut joint_graph: ResMut<JointGraph>,
131    mut islands: Option<ResMut<PhysicsIslands>>,
132) {
133    let entity = trigger.event_target();
134
135    let Ok((joint, collision_disabled)) = query.get(entity) else {
136        return;
137    };
138
139    let [body1, body2] = joint.entities();
140
141    // Add the joint to the joint graph.
142    let joint_edge = JointGraphEdge::new(entity, body1, body2, collision_disabled);
143    let joint_id = joint_graph.add_joint(body1, body2, joint_edge);
144
145    // Link the joint to an island.
146    if let Some(islands) = &mut islands {
147        let island = islands.add_joint(
148            joint_id,
149            &mut body_islands,
150            &mut contact_graph,
151            &mut joint_graph,
152        );
153
154        // Wake up the island if it was sleeping.
155        if let Some(island) = island
156            && island.is_sleeping
157        {
158            commands.queue(WakeIslands(vec![island.id]));
159        }
160    }
161}
162
163fn remove_joint_from_graph<E: EntityEvent, B: Bundle>(
164    trigger: On<E, B>,
165    mut commands: Commands,
166    mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
167    contact_graph: ResMut<ContactGraph>,
168    mut joint_graph: ResMut<JointGraph>,
169    mut islands: Option<ResMut<PhysicsIslands>>,
170) {
171    let entity = trigger.event_target();
172
173    let Some(joint) = joint_graph.get(entity) else {
174        return;
175    };
176
177    // Remove the joint from the island.
178    if let Some(islands) = &mut islands {
179        let island = islands.remove_joint(
180            joint.id,
181            &mut body_islands,
182            &contact_graph,
183            &mut joint_graph,
184        );
185
186        // Wake up the island if it was sleeping.
187        if island.is_sleeping {
188            commands.queue(WakeIslands(vec![island.id]));
189        }
190    }
191
192    // Remove the joint from the joint graph.
193    joint_graph.remove_joint(entity);
194}
195
196fn on_add_joint(mut world: DeferredWorld, ctx: HookContext) {
197    let entity = ctx.entity;
198    let component_id = ctx.component_id;
199
200    let mut joint = world.get_mut::<JointComponentId>(entity).unwrap();
201    let old_joint = joint.0;
202
203    // Update the joint component with the new component ID.
204    joint.0 = Some(component_id);
205
206    if let Some(old_joint) = old_joint {
207        // Joint already exists, remove the old one.
208        world.commands().entity(entity).remove_by_id(old_joint);
209
210        #[cfg(feature = "validate")]
211        {
212            use disqualified::ShortName;
213
214            // Log a warning about the joint replacement in case it was not intentional.
215            let components = world.components();
216            let old_joint_shortname = components.get_info(old_joint).unwrap().name();
217            let old_joint_name = ShortName(&old_joint_shortname);
218            let new_joint_shortname = components.get_info(component_id).unwrap().name();
219            let new_joint_name = ShortName(&new_joint_shortname);
220
221            warn!(
222                "{old_joint_name} was replaced with {new_joint_name} on entity {entity}. An entity can only hold one joint type at a time."
223            );
224        }
225    }
226}
227
228fn on_remove_joint(mut world: DeferredWorld, ctx: HookContext) {
229    let entity = ctx.entity;
230    let component_id = ctx.component_id;
231
232    // Remove the `JointComponentId` from the entity unless the component ID
233    // was changed, implying that the joint is being replaced by another one.
234    if let Some(mut joint) = world.get_mut::<JointComponentId>(entity)
235        && joint.0 == Some(component_id)
236    {
237        joint.0 = None;
238
239        // Remove the joint component.
240        world
241            .commands()
242            .entity(entity)
243            .try_remove::<JointComponentId>();
244    }
245}
246
247fn on_disable_joint_collision(
248    trigger: On<Add, JointCollisionDisabled>,
249    query: Query<&RigidBodyColliders>,
250    joint_graph: Res<JointGraph>,
251    mut contact_graph: ResMut<ContactGraph>,
252    mut constraint_graph: ResMut<ConstraintGraph>,
253) {
254    let entity = trigger.entity;
255
256    // Iterate through each collider of the body with fewer colliders,
257    // find contacts with the other body, and remove them.
258    let Some([body1, body2]) = joint_graph.bodies_of(entity) else {
259        return;
260    };
261    let Ok([colliders1, colliders2]) = query.get_many([body1, body2]) else {
262        return;
263    };
264
265    let (colliders, other_body) = if colliders1.len() < colliders2.len() {
266        (colliders1, body2)
267    } else {
268        (colliders2, body1)
269    };
270
271    let contacts_to_remove: Vec<(ContactId, usize)> = colliders
272        .iter()
273        .flat_map(|collider| {
274            contact_graph
275                .contact_edges_with(collider)
276                .filter_map(|edge| {
277                    if edge.body1 == Some(other_body) || edge.body2 == Some(other_body) {
278                        Some((edge.id, edge.constraint_handles.len()))
279                    } else {
280                        None
281                    }
282                })
283        })
284        .collect();
285
286    for (contact_id, num_constraints) in contacts_to_remove {
287        // Remove the contact from the constraint graph.
288        for _ in 0..num_constraints {
289            constraint_graph.pop_manifold(&mut contact_graph.edges, contact_id, body1, body2);
290        }
291
292        // Remove the contact from the contact graph.
293        let pair_key = PairKey::new(body1.index(), body2.index());
294        contact_graph.remove_edge_by_id(&pair_key, contact_id);
295    }
296}
297
298/// Update the joint graph when the entities of a joint change.
299fn on_change_joint_entities<T: Component + EntityConstraint<2>>(
300    query: Query<(Entity, &T), Changed<T>>,
301    mut commands: Commands,
302    mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
303    mut joint_graph: ResMut<JointGraph>,
304    mut contact_graph: ResMut<ContactGraph>,
305    mut islands: Option<ResMut<PhysicsIslands>>,
306) {
307    let mut islands_to_wake: Vec<IslandId> = Vec::new();
308
309    for (entity, joint) in &query {
310        let [body1, body2] = joint.entities();
311        let Some(old_edge) = joint_graph.get(entity) else {
312            continue;
313        };
314
315        if body1 != old_edge.body1 || body2 != old_edge.body2 {
316            // Remove the joint from the island.
317            if let Some(islands) = &mut islands {
318                let island = islands.remove_joint(
319                    old_edge.id,
320                    &mut body_islands,
321                    &contact_graph,
322                    &mut joint_graph,
323                );
324
325                // Wake up the island if it was sleeping.
326                if island.is_sleeping {
327                    islands_to_wake.push(island.id);
328                }
329            }
330
331            // Remove the old joint edge.
332            if let Some(mut edge) = joint_graph.remove_joint(entity) {
333                // Update the edge with the new bodies.
334                edge.body1 = body1;
335                edge.body2 = body2;
336
337                // Add the joint edge.
338                let joint_id = joint_graph.add_joint(body1, body2, edge);
339
340                // Link the joint to an island.
341                if let Some(islands) = &mut islands {
342                    islands.add_joint(
343                        joint_id,
344                        &mut body_islands,
345                        &mut contact_graph,
346                        &mut joint_graph,
347                    );
348                }
349            }
350        }
351    }
352
353    if !islands_to_wake.is_empty() {
354        islands_to_wake.sort_unstable();
355        islands_to_wake.dedup();
356
357        // Wake up the islands that were previously sleeping.
358        commands.queue(WakeIslands(islands_to_wake));
359    }
360}
361
362// TODO: Tests