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