avian2d/dynamics/solver/joint_graph/
plugin.rs1use 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
27pub 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#[derive(Component, Clone, Debug, Default, PartialEq, Reflect)]
42pub struct JointComponentId(Option<ComponentId>);
43
44impl JointComponentId {
45 pub fn new() -> Self {
47 Self(None)
48 }
49
50 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 app.register_required_components::<T, JointComponentId>();
70
71 app.world_mut()
73 .register_component_hooks::<T>()
74 .on_add(on_add_joint)
75 .on_remove(on_remove_joint);
76
77 app.add_observer(
79 add_joint_to_graph::<T, Add, T, (With<JointComponentId>, Without<JointDisabled>)>,
80 );
81
82 app.add_observer(remove_joint_from_graph::<Remove, T>);
84
85 if !already_initialized {
86 app.add_observer(remove_joint_from_graph::<Add, (Disabled, JointDisabled)>);
88
89 app.add_observer(on_disable_joint_collision);
91 }
92
93 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 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 let joint_edge = JointGraphEdge::new(entity, body1, body2, collision_disabled);
143 let joint_id = joint_graph.add_joint(body1, body2, joint_edge);
144
145 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 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 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 if island.is_sleeping {
188 commands.queue(WakeIslands(vec![island.id]));
189 }
190 }
191
192 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 joint.0 = Some(component_id);
205
206 if let Some(old_joint) = old_joint {
207 world.commands().entity(entity).remove_by_id(old_joint);
209
210 #[cfg(feature = "validate")]
211 {
212 use disqualified::ShortName;
213
214 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 if let Some(mut joint) = world.get_mut::<JointComponentId>(entity)
235 && joint.0 == Some(component_id)
236 {
237 joint.0 = None;
238
239 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 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 for _ in 0..num_constraints {
289 constraint_graph.pop_manifold(&mut contact_graph.edges, contact_id, body1, body2);
290 }
291
292 let pair_key = PairKey::new(body1.index(), body2.index());
294 contact_graph.remove_edge_by_id(&pair_key, contact_id);
295 }
296}
297
298fn 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 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 if island.is_sleeping {
327 islands_to_wake.push(island.id);
328 }
329 }
330
331 if let Some(mut edge) = joint_graph.remove_joint(entity) {
333 edge.body1 = body1;
335 edge.body2 = body2;
336
337 let joint_id = joint_graph.add_joint(body1, body2, edge);
339
340 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 commands.queue(WakeIslands(islands_to_wake));
359 }
360}
361
362