avian2d/dynamics/solver/solver_body/
plugin.rs1use bevy::{
2 ecs::{entity_disabling::Disabled, query::QueryFilter, world::DeferredWorld},
3 prelude::*,
4};
5
6use super::{SolverBody, SolverBodyInertia};
7use crate::{
8 AngularVelocity, LinearVelocity, PhysicsSchedule, Position, RigidBody, RigidBodyActiveFilter,
9 RigidBodyDisabled, Rotation, Sleeping, SolverSystems, Vector,
10 dynamics::solver::{SolverDiagnostics, solver_body::SolverBodyFlags},
11 prelude::{ComputedAngularInertia, ComputedCenterOfMass, ComputedMass, Dominance, LockedAxes},
12};
13#[cfg(feature = "3d")]
14use crate::{
15 MatExt,
16 dynamics::integrator::{IntegrationSystems, integrate_positions},
17 prelude::SubstepSchedule,
18};
19
20pub struct SolverBodyPlugin;
36
37impl Plugin for SolverBodyPlugin {
38 fn build(&self, app: &mut App) {
39 app.add_observer(on_insert_rigid_body);
41
42 app.add_observer(
45 |trigger: On<Remove, RigidBodyDisabled>,
46 rb_query: Query<&RigidBody, Without<Sleeping>>,
47 commands: Commands| {
48 add_solver_body::<Without<Sleeping>>(In(trigger.entity), rb_query, commands);
49 },
50 );
51 app.add_observer(
52 |trigger: On<Remove, Disabled>,
53 rb_query: Query<
54 &RigidBody,
55 (
56 With<Disabled>,
59 Without<RigidBodyDisabled>,
60 Without<Sleeping>,
61 ),
62 >,
63 commands: Commands| {
64 add_solver_body::<(
65 With<Disabled>,
66 Without<RigidBodyDisabled>,
67 Without<Sleeping>,
68 )>(In(trigger.entity), rb_query, commands);
69 },
70 );
71 app.add_observer(
72 |trigger: On<Remove, Sleeping>,
73 rb_query: Query<&RigidBody, Without<RigidBodyDisabled>>,
74 commands: Commands| {
75 add_solver_body::<Without<RigidBodyDisabled>>(
76 In(trigger.entity),
77 rb_query,
78 commands,
79 );
80 },
81 );
82
83 app.add_observer(
85 |trigger: On<Remove, RigidBody>, deferred_world: DeferredWorld| {
86 remove_solver_body(In(trigger.entity), deferred_world);
87 },
88 );
89
90 app.add_observer(
92 |trigger: On<Add, (Disabled, RigidBodyDisabled, Sleeping)>,
93 deferred_world: DeferredWorld| {
94 remove_solver_body(In(trigger.entity), deferred_world);
95 },
96 );
97
98 app.add_systems(
100 PhysicsSchedule,
101 prepare_solver_bodies
102 .chain()
103 .in_set(SolverSystems::PrepareSolverBodies),
104 );
105
106 app.add_systems(
108 PhysicsSchedule,
109 writeback_solver_bodies.in_set(SolverSystems::Finalize),
110 );
111
112 #[cfg(feature = "3d")]
115 app.add_systems(
116 SubstepSchedule,
117 update_solver_body_angular_inertia
118 .in_set(IntegrationSystems::Position)
119 .after(integrate_positions),
120 );
121 }
122}
123
124fn on_insert_rigid_body(
125 trigger: On<Insert, RigidBody>,
126 bodies: Query<(&RigidBody, Has<SolverBody>), RigidBodyActiveFilter>,
127 mut commands: Commands,
128) {
129 let Ok((rb, has_solver_body)) = bodies.get(trigger.entity) else {
130 return;
131 };
132
133 if rb.is_static() && has_solver_body {
134 commands
136 .entity(trigger.entity)
137 .try_remove::<(SolverBody, SolverBodyInertia)>();
138 } else if !rb.is_static() && !has_solver_body {
139 commands
141 .entity(trigger.entity)
142 .try_insert((SolverBody::default(), SolverBodyInertia::default()));
143 }
144}
145
146fn add_solver_body<F: QueryFilter>(
147 In(entity): In<Entity>,
148 mut rb_query: Query<&RigidBody, F>,
149 mut commands: Commands,
150) {
151 if let Ok(rb) = rb_query.get_mut(entity) {
152 if rb.is_static() {
153 return;
154 }
155
156 commands
158 .entity(entity)
159 .try_insert((SolverBody::default(), SolverBodyInertia::default()));
160 }
161}
162
163fn remove_solver_body(In(entity): In<Entity>, mut deferred_world: DeferredWorld) {
164 let entity_ref = deferred_world.entity(entity);
165 if entity_ref.contains::<SolverBody>() {
166 deferred_world
167 .commands()
168 .entity(entity)
169 .try_remove::<(SolverBody, SolverBodyInertia)>();
170 }
171}
172
173fn prepare_solver_bodies(
174 mut query: Query<(
175 &RigidBody,
176 &mut SolverBody,
177 &mut SolverBodyInertia,
178 &LinearVelocity,
179 &AngularVelocity,
180 &Rotation,
181 &ComputedMass,
182 &ComputedAngularInertia,
183 Option<&LockedAxes>,
184 Option<&Dominance>,
185 )>,
186) {
187 #[allow(unused_variables)]
188 query.par_iter_mut().for_each(
189 |(
190 rb,
191 mut solver_body,
192 mut inertial_properties,
193 linear_velocity,
194 angular_velocity,
195 rotation,
196 mass,
197 angular_inertia,
198 locked_axes,
199 dominance,
200 )| {
201 solver_body.linear_velocity = linear_velocity.0;
202 solver_body.angular_velocity = angular_velocity.0;
203 solver_body.delta_position = Vector::ZERO;
204 solver_body.delta_rotation = Rotation::IDENTITY;
205
206 let locked_axes = locked_axes.copied().unwrap_or_default();
207 *inertial_properties = SolverBodyInertia::new(
208 mass.inverse(),
209 #[cfg(feature = "2d")]
210 angular_inertia.inverse(),
211 #[cfg(feature = "3d")]
212 angular_inertia.rotated(rotation.0).inverse(),
213 locked_axes,
214 dominance.map_or(0, |dominance| dominance.0),
215 rb.is_dynamic(),
216 );
217 solver_body.flags = SolverBodyFlags(locked_axes.to_bits() as u32);
218 solver_body
219 .flags
220 .set(SolverBodyFlags::IS_KINEMATIC, rb.is_kinematic());
221
222 #[cfg(feature = "3d")]
223 {
224 let epsilon = 1e-6;
242 let is_gyroscopic = !locked_axes.is_rotation_locked()
243 && !angular_inertia.inverse().is_isotropic(epsilon);
244
245 solver_body
246 .flags
247 .set(SolverBodyFlags::GYROSCOPIC_MOTION, is_gyroscopic);
248 }
249 },
250 );
251}
252
253#[allow(clippy::type_complexity)]
255fn writeback_solver_bodies(
256 mut query: Query<(
257 &SolverBody,
258 &mut Position,
259 &mut Rotation,
260 &ComputedCenterOfMass,
261 &mut LinearVelocity,
262 &mut AngularVelocity,
263 )>,
264 mut diagnostics: ResMut<SolverDiagnostics>,
265) {
266 let start = bevy::platform::time::Instant::now();
267
268 query.par_iter_mut().for_each(
269 |(solver_body, mut pos, mut rot, com, mut lin_vel, mut ang_vel)| {
270 let old_world_com = *rot * com.0;
273 *rot = (solver_body.delta_rotation * *rot).fast_renormalize();
274 let new_world_com = *rot * com.0;
275 pos.0 += solver_body.delta_position + old_world_com - new_world_com;
276
277 lin_vel.0 = solver_body.linear_velocity;
279 ang_vel.0 = solver_body.angular_velocity;
280 },
281 );
282
283 diagnostics.finalize += start.elapsed();
284}
285
286#[cfg(feature = "3d")]
287pub(crate) fn update_solver_body_angular_inertia(
288 mut query: Query<(&mut SolverBodyInertia, &ComputedAngularInertia, &Rotation)>,
289) {
290 query
291 .par_iter_mut()
292 .for_each(|(mut inertia, angular_inertia, rotation)| {
293 inertia.update_effective_inv_angular_inertia(angular_inertia, rotation.0);
294 });
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use crate::{PhysicsSchedulePlugin, SolverSchedulePlugin};
301
302 fn create_app() -> App {
303 let mut app = App::new();
304
305 app.add_plugins((
306 PhysicsSchedulePlugin::default(),
307 SolverSchedulePlugin,
308 SolverBodyPlugin,
309 ));
310
311 app
312 }
313
314 fn has_solver_body(app: &App, entity: Entity) -> bool {
315 app.world().get::<SolverBody>(entity).is_some()
316 }
317
318 #[test]
319 fn add_remove_solver_bodies() {
320 let mut app = create_app();
321
322 let entity1 = app.world_mut().spawn(RigidBody::Dynamic).id();
324 let entity2 = app.world_mut().spawn(RigidBody::Kinematic).id();
325 let entity3 = app.world_mut().spawn(RigidBody::Static).id();
326
327 app.update();
328
329 assert!(has_solver_body(&app, entity1));
331 assert!(has_solver_body(&app, entity2));
332 assert!(!has_solver_body(&app, entity3));
333
334 app.world_mut()
336 .entity_mut(entity1)
337 .insert(RigidBodyDisabled);
338
339 app.update();
340
341 assert!(!has_solver_body(&app, entity1));
343
344 app.world_mut()
346 .entity_mut(entity1)
347 .remove::<RigidBodyDisabled>();
348
349 app.update();
350
351 assert!(has_solver_body(&app, entity1));
353 }
354}