1use bevy::{
2 ecs::{entity_disabling::Disabled, query::QueryFilter},
3 prelude::*,
4};
5
6use super::{SolverBodies, SolverBody, SolverBodyIndex, SolverBodyInertia};
7use crate::{
8 AngularVelocity, LinearVelocity, PhysicsSchedule, Position, RigidBody, RigidBodyActiveFilter,
9 RigidBodyDisabled, Rot, Rotation, Sleeping, SolverSystems, Vector,
10 dynamics::{
11 integrator::CustomPositionIntegration,
12 solver::{SolverDiagnostics, solver_body::SolverBodyFlags},
13 },
14 math::ToRealPrecision,
15 prelude::{
16 AppDiagnosticsExt, ComputedAngularInertia, ComputedCenterOfMass, ComputedMass, Dominance,
17 LockedAxes,
18 },
19 utils::{MIN_PAR_ITER_ENTITIES, ParallelQueryForEach},
20};
21#[cfg(feature = "3d")]
22use crate::{
23 MatExt, QuatExt,
24 dynamics::integrator::{IntegrationSystems, integrate_positions},
25 prelude::SubstepSchedule,
26};
27
28pub struct SolverBodyPlugin;
47
48impl Plugin for SolverBodyPlugin {
49 fn build(&self, app: &mut App) {
50 app.init_resource::<SolverBodies>();
51
52 app.add_observer(on_insert_rigid_body);
54
55 app.add_observer(
58 |trigger: On<Remove<RigidBodyDisabled>>,
59 rb_query: Query<(&RigidBody, Has<SolverBodyIndex>), Without<Sleeping>>,
60 solver_bodies: ResMut<SolverBodies>,
61 commands: Commands| {
62 add_solver_body::<Without<Sleeping>>(
63 In(trigger.entity),
64 rb_query,
65 solver_bodies,
66 commands,
67 );
68 },
69 );
70 app.add_observer(
71 |trigger: On<Remove<Disabled>>,
72 rb_query: Query<
73 (&RigidBody, Has<SolverBodyIndex>),
74 (
75 With<Disabled>,
78 Without<RigidBodyDisabled>,
79 Without<Sleeping>,
80 ),
81 >,
82 solver_bodies: ResMut<SolverBodies>,
83 commands: Commands| {
84 add_solver_body::<(
85 With<Disabled>,
86 Without<RigidBodyDisabled>,
87 Without<Sleeping>,
88 )>(In(trigger.entity), rb_query, solver_bodies, commands);
89 },
90 );
91 app.add_observer(
92 |trigger: On<Remove<Sleeping>>,
93 rb_query: Query<(&RigidBody, Has<SolverBodyIndex>), Without<RigidBodyDisabled>>,
94 solver_bodies: ResMut<SolverBodies>,
95 commands: Commands| {
96 add_solver_body::<Without<RigidBodyDisabled>>(
97 In(trigger.entity),
98 rb_query,
99 solver_bodies,
100 commands,
101 );
102 },
103 );
104
105 app.add_observer(
107 |trigger: On<Remove<RigidBody>>,
108 index_query: Query<&mut SolverBodyIndex>,
109 solver_bodies: ResMut<SolverBodies>,
110 commands: Commands| {
111 remove_solver_body(In(trigger.entity), index_query, solver_bodies, commands);
112 },
113 );
114
115 app.add_observer(
117 |trigger: On<Add<(Disabled, RigidBodyDisabled, Sleeping)>>,
118 index_query: Query<&mut SolverBodyIndex>,
119 solver_bodies: ResMut<SolverBodies>,
120 commands: Commands| {
121 remove_solver_body(In(trigger.entity), index_query, solver_bodies, commands);
122 },
123 );
124
125 app.add_systems(
127 PhysicsSchedule,
128 prepare_solver_bodies
129 .chain()
130 .in_set(SolverSystems::PrepareSolverBodies),
131 );
132
133 app.add_systems(
135 PhysicsSchedule,
136 writeback_solver_bodies.in_set(SolverSystems::Finalize),
137 );
138
139 #[cfg(feature = "3d")]
142 app.add_systems(
143 SubstepSchedule,
144 update_solver_body_angular_inertia
145 .in_set(IntegrationSystems::Position)
146 .after(integrate_positions),
147 );
148 }
149
150 fn finish(&self, app: &mut App) {
151 app.register_physics_diagnostics::<SolverDiagnostics>();
153 }
154}
155
156fn on_insert_rigid_body(
157 trigger: On<Insert<RigidBody>>,
158 bodies: Query<(&RigidBody, Has<SolverBodyIndex>), RigidBodyActiveFilter>,
159 index_query: Query<&mut SolverBodyIndex>,
160 mut solver_bodies: ResMut<SolverBodies>,
161 mut commands: Commands,
162) {
163 let entity = trigger.entity;
164 let Ok((rb, has_solver_body)) = bodies.get(entity) else {
165 return;
166 };
167
168 if rb.is_static() && has_solver_body {
169 remove_solver_body_inner(entity, index_query, &mut solver_bodies, &mut commands);
171 } else if !rb.is_static() && !has_solver_body {
172 add_solver_body_inner(entity, &mut solver_bodies, &mut commands);
174 }
175}
176
177fn add_solver_body<F: QueryFilter>(
178 In(entity): In<Entity>,
179 rb_query: Query<(&RigidBody, Has<SolverBodyIndex>), F>,
180 mut solver_bodies: ResMut<SolverBodies>,
181 mut commands: Commands,
182) {
183 if let Ok((rb, has_solver_body)) = rb_query.get(entity) {
184 if rb.is_static() || has_solver_body {
185 return;
186 }
187
188 add_solver_body_inner(entity, &mut solver_bodies, &mut commands);
189 }
190}
191
192fn add_solver_body_inner(
194 entity: Entity,
195 solver_bodies: &mut SolverBodies,
196 commands: &mut Commands,
197) {
198 let index = solver_bodies.push(entity, SolverBody::default(), SolverBodyInertia::default());
199 commands.entity(entity).try_insert(index);
200}
201
202fn remove_solver_body(
203 In(entity): In<Entity>,
204 index_query: Query<&mut SolverBodyIndex>,
205 mut solver_bodies: ResMut<SolverBodies>,
206 mut commands: Commands,
207) {
208 remove_solver_body_inner(entity, index_query, &mut solver_bodies, &mut commands);
209}
210
211fn remove_solver_body_inner(
214 entity: Entity,
215 mut index_query: Query<&mut SolverBodyIndex>,
216 solver_bodies: &mut SolverBodies,
217 commands: &mut Commands,
218) {
219 let Ok(index) = index_query.get(entity).copied() else {
220 return;
221 };
222 if !index.is_valid() {
223 return;
224 }
225
226 if let Some(swapped_entity) = solver_bodies.swap_remove(index)
228 && let Ok(mut swapped_index) = index_query.get_mut(swapped_entity)
229 {
230 *swapped_index = index;
231 }
232
233 commands.entity(entity).try_remove::<SolverBodyIndex>();
234}
235
236fn prepare_solver_bodies(
237 mut solver_bodies: ResMut<SolverBodies>,
238 query: Query<(
239 &RigidBody,
240 &SolverBodyIndex,
241 &LinearVelocity,
242 &AngularVelocity,
243 &Rotation,
244 &ComputedMass,
245 &ComputedAngularInertia,
246 Option<&LockedAxes>,
247 Option<&Dominance>,
248 Has<CustomPositionIntegration>,
249 )>,
250) {
251 let access = solver_bodies.access();
252
253 #[allow(unused_variables)]
254 query.par_for_each(
255 MIN_PAR_ITER_ENTITIES,
256 |(
257 rb,
258 index,
259 linear_velocity,
260 angular_velocity,
261 rotation,
262 mass,
263 angular_inertia,
264 locked_axes,
265 dominance,
266 custom_position_integration,
267 )| {
268 let solver_body = unsafe { access.body_unchecked_mut(*index) };
271 let inertial_properties = unsafe { access.inertia_unchecked_mut(*index) };
272
273 solver_body.linear_velocity = linear_velocity.0;
274 solver_body.angular_velocity = angular_velocity.0;
275 solver_body.delta_position = Vector::ZERO;
276 solver_body.delta_rotation = Rot::IDENTITY;
277
278 let locked_axes = locked_axes.copied().unwrap_or_default();
279 *inertial_properties = SolverBodyInertia::new(
280 mass.inverse(),
281 #[cfg(feature = "2d")]
282 angular_inertia.inverse(),
283 #[cfg(feature = "3d")]
284 angular_inertia.rotated(rotation.0).inverse(),
285 locked_axes,
286 dominance.map_or(0, |dominance| dominance.0),
287 rb.is_dynamic(),
288 );
289 solver_body.flags = SolverBodyFlags(locked_axes.to_bits() as u32);
290 solver_body
291 .flags
292 .set(SolverBodyFlags::IS_KINEMATIC, rb.is_kinematic());
293 solver_body.flags.set(
294 SolverBodyFlags::CUSTOM_POSITION_INTEGRATION,
295 custom_position_integration,
296 );
297
298 #[cfg(feature = "3d")]
299 {
300 let epsilon = 1e-6;
318 let is_gyroscopic = !locked_axes.is_rotation_locked()
319 && !angular_inertia.inverse().is_isotropic(epsilon);
320
321 solver_body
322 .flags
323 .set(SolverBodyFlags::GYROSCOPIC_MOTION, is_gyroscopic);
324 }
325 },
326 );
327}
328
329#[allow(clippy::type_complexity)]
331fn writeback_solver_bodies(
332 solver_bodies: Res<SolverBodies>,
333 mut query: Query<(
334 &SolverBodyIndex,
335 &mut Position,
336 &mut Rotation,
337 &ComputedCenterOfMass,
338 &mut LinearVelocity,
339 &mut AngularVelocity,
340 )>,
341 mut diagnostics: ResMut<SolverDiagnostics>,
342) {
343 let start = bevy::platform::time::Instant::now();
344
345 query.par_for_each_mut(
346 MIN_PAR_ITER_ENTITIES,
347 |(index, mut pos, mut rot, com, mut lin_vel, mut ang_vel)| {
348 let Some(solver_body) = solver_bodies.get(*index) else {
349 return;
350 };
351
352 let old_world_com = *rot * com.0;
355 *rot = (solver_body.delta_rotation * Rot::from(*rot))
356 .fast_renormalize()
357 .into();
358 let new_world_com = *rot * com.0;
359 pos.0 += (solver_body.delta_position + (old_world_com - new_world_com)).real();
360
361 lin_vel.0 = solver_body.linear_velocity;
363 ang_vel.0 = solver_body.angular_velocity;
364 },
365 );
366
367 diagnostics.finalize += start.elapsed();
368}
369
370#[cfg(feature = "3d")]
371pub(crate) fn update_solver_body_angular_inertia(
372 mut solver_bodies: ResMut<SolverBodies>,
373 mut query: Query<(&SolverBodyIndex, &ComputedAngularInertia, &Rotation)>,
374) {
375 let access = solver_bodies.access();
376
377 query.par_for_each_mut(
378 MIN_PAR_ITER_ENTITIES,
379 |(index, angular_inertia, rotation)| {
380 let inertia = unsafe { access.inertia_unchecked_mut(*index) };
383 inertia.update_effective_inv_angular_inertia(angular_inertia, rotation.0);
384 },
385 );
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391 use crate::{PhysicsSchedulePlugin, SolverSchedulePlugin};
392
393 fn create_app() -> App {
394 let mut app = App::new();
395
396 app.add_plugins((
397 PhysicsSchedulePlugin::default(),
398 SolverSchedulePlugin,
399 SolverBodyPlugin,
400 ));
401
402 app
403 }
404
405 fn has_solver_body(app: &App, entity: Entity) -> bool {
406 app.world()
407 .resource::<SolverBodies>()
408 .contains_entity(entity)
409 && app.world().get::<SolverBodyIndex>(entity).is_some()
410 }
411
412 #[test]
413 fn add_remove_solver_bodies() {
414 let mut app = create_app();
415
416 let entity1 = app.world_mut().spawn(RigidBody::Dynamic).id();
418 let entity2 = app.world_mut().spawn(RigidBody::Kinematic).id();
419 let entity3 = app.world_mut().spawn(RigidBody::Static).id();
420
421 app.update();
422
423 assert!(has_solver_body(&app, entity1));
425 assert!(has_solver_body(&app, entity2));
426 assert!(!has_solver_body(&app, entity3));
427
428 app.world_mut()
430 .entity_mut(entity1)
431 .insert(RigidBodyDisabled);
432
433 app.update();
434
435 assert!(!has_solver_body(&app, entity1));
437
438 app.world_mut()
440 .entity_mut(entity1)
441 .remove::<RigidBodyDisabled>();
442
443 app.update();
444
445 assert!(has_solver_body(&app, entity1));
447 }
448}