1use crate::dynamics::RapierRigidBodyHandle;
2use crate::plugin::context::systemparams::RAPIER_CONTEXT_EXPECT_ERROR;
3use crate::plugin::context::{
4 DefaultRapierContext, RapierContextColliders, RapierContextEntityLink, RapierRigidBodySet,
5};
6use crate::plugin::{configuration::TimestepMode, RapierConfiguration};
7use crate::{dynamics::RigidBody, plugin::context::SimulationToRenderTime};
8use crate::{prelude::*, utils};
9use bevy::prelude::*;
10use rapier::dynamics::{RigidBodyBuilder, RigidBodyHandle, RigidBodyType};
11use std::collections::HashMap;
12
13pub type RigidBodyWritebackComponents<'a> = (
15 &'a RapierRigidBodyHandle,
16 &'a RapierContextEntityLink,
17 Option<&'a ChildOf>,
18 Option<&'a mut Transform>,
19 Option<&'a mut TransformInterpolation>,
20 Option<&'a mut Velocity>,
21 Option<&'a mut Sleeping>,
22);
23
24pub type RigidBodyComponents<'a> = (
26 (Entity, Option<&'a RapierContextEntityLink>),
27 &'a RigidBody,
28 Option<&'a GlobalTransform>,
29 Option<&'a Velocity>,
30 Option<&'a AdditionalMassProperties>,
31 Option<&'a ReadMassProperties>,
32 Option<&'a LockedAxes>,
33 Option<&'a ExternalForce>,
34 Option<&'a GravityScale>,
35 (Option<&'a Ccd>, Option<&'a SoftCcd>),
36 Option<&'a Dominance>,
37 Option<&'a Sleeping>,
38 Option<&'a Damping>,
39 Option<&'a RigidBodyDisabled>,
40 Option<&'a AdditionalSolverIterations>,
41);
42
43pub fn apply_rigid_body_user_changes(
45 mut rigid_body_sets: Query<&mut RapierRigidBodySet>,
46 config: Query<&RapierConfiguration>,
47 changed_rb_types: Query<
48 (&RapierRigidBodyHandle, &RapierContextEntityLink, &RigidBody),
49 Changed<RigidBody>,
50 >,
51 mut changed_transforms: Query<
52 (
53 &RapierRigidBodyHandle,
54 &RapierContextEntityLink,
55 &GlobalTransform,
56 Option<&mut TransformInterpolation>,
57 ),
58 Changed<GlobalTransform>,
59 >,
60 changed_velocities: Query<
61 (&RapierRigidBodyHandle, &RapierContextEntityLink, &Velocity),
62 Changed<Velocity>,
63 >,
64 changed_additional_mass_props: Query<
65 (
66 Entity,
67 &RapierContextEntityLink,
68 &RapierRigidBodyHandle,
69 &AdditionalMassProperties,
70 ),
71 Changed<AdditionalMassProperties>,
72 >,
73 changed_locked_axes: Query<
74 (
75 &RapierRigidBodyHandle,
76 &RapierContextEntityLink,
77 &LockedAxes,
78 ),
79 Changed<LockedAxes>,
80 >,
81 changed_forces: Query<
82 (
83 &RapierRigidBodyHandle,
84 &RapierContextEntityLink,
85 &ExternalForce,
86 ),
87 Changed<ExternalForce>,
88 >,
89 mut changed_impulses: Query<
90 (
91 &RapierRigidBodyHandle,
92 &RapierContextEntityLink,
93 &mut ExternalImpulse,
94 ),
95 Changed<ExternalImpulse>,
96 >,
97 changed_gravity_scale: Query<
98 (
99 &RapierRigidBodyHandle,
100 &RapierContextEntityLink,
101 &GravityScale,
102 ),
103 Changed<GravityScale>,
104 >,
105 (changed_ccd, changed_soft_ccd): (
106 Query<(&RapierRigidBodyHandle, &RapierContextEntityLink, &Ccd), Changed<Ccd>>,
107 Query<(&RapierRigidBodyHandle, &RapierContextEntityLink, &SoftCcd), Changed<SoftCcd>>,
108 ),
109 changed_dominance: Query<
110 (&RapierRigidBodyHandle, &RapierContextEntityLink, &Dominance),
111 Changed<Dominance>,
112 >,
113 changed_sleeping: Query<
114 (&RapierRigidBodyHandle, &RapierContextEntityLink, &Sleeping),
115 Changed<Sleeping>,
116 >,
117 changed_damping: Query<
118 (&RapierRigidBodyHandle, &RapierContextEntityLink, &Damping),
119 Changed<Damping>,
120 >,
121 (changed_disabled, changed_additional_solver_iterations): (
122 Query<
123 (
124 &RapierRigidBodyHandle,
125 &RapierContextEntityLink,
126 &RigidBodyDisabled,
127 ),
128 Changed<RigidBodyDisabled>,
129 >,
130 Query<
131 (
132 &RapierRigidBodyHandle,
133 &RapierContextEntityLink,
134 &AdditionalSolverIterations,
135 ),
136 Changed<AdditionalSolverIterations>,
137 >,
138 ),
139 mut mass_modified: MessageWriter<MassModifiedEvent>,
140) {
141 for (handle, link, sleeping) in changed_sleeping.iter() {
144 let rigidbody_set = rigid_body_sets
145 .get_mut(link.0)
146 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
147 .into_inner();
148
149 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
150 let activation = rb.activation_mut();
151 activation.normalized_linear_threshold = sleeping.normalized_linear_threshold;
152 activation.angular_threshold = sleeping.angular_threshold;
153
154 if !sleeping.sleeping && activation.sleeping {
155 rb.wake_up(true);
156 } else if sleeping.sleeping && !activation.sleeping {
157 rb.sleep();
158 }
159 }
160 }
161
162 for (handle, link, rb_type) in changed_rb_types.iter() {
169 let context = rigid_body_sets
170 .get_mut(link.0)
171 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
172 .into_inner();
173 if let Some(rb) = context.bodies.get_mut(handle.0) {
174 rb.set_body_type((*rb_type).into(), true);
175 }
176 }
177
178 let transform_changed_fn =
183 |handle: &RigidBodyHandle,
184 config: &RapierConfiguration,
185 transform: &GlobalTransform,
186 last_transform_set: &HashMap<RigidBodyHandle, GlobalTransform>| {
187 if config.force_update_from_transform_changes {
188 true
189 } else if let Some(prev) = last_transform_set.get(handle) {
190 *prev != *transform
191 } else {
192 true
193 }
194 };
195
196 for (handle, link, global_transform, mut interpolation) in changed_transforms.iter_mut() {
197 let rigidbody_set = rigid_body_sets
198 .get_mut(link.0)
199 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
200 .into_inner();
201 let config = config
202 .get(link.0)
203 .expect("Could not get `RapierConfiguration`");
204 let mut transform_changed = None;
206
207 if let Some(interpolation) = interpolation.as_deref_mut() {
208 transform_changed = transform_changed.or_else(|| {
209 Some(transform_changed_fn(
210 &handle.0,
211 config,
212 global_transform,
213 &rigidbody_set.last_body_transform_set,
214 ))
215 });
216
217 if transform_changed == Some(true) {
218 interpolation.start = None;
221 interpolation.end = None;
222 }
223 }
224 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
226 transform_changed = transform_changed.or_else(|| {
227 Some(transform_changed_fn(
228 &handle.0,
229 config,
230 global_transform,
231 &rigidbody_set.last_body_transform_set,
232 ))
233 });
234
235 match rb.body_type() {
236 RigidBodyType::KinematicPositionBased => {
237 if transform_changed == Some(true) {
238 rb.set_next_kinematic_position(utils::transform_to_iso(
239 &global_transform.compute_transform(),
240 ));
241 rigidbody_set
242 .last_body_transform_set
243 .insert(handle.0, *global_transform);
244 }
245 }
246 _ => {
247 if transform_changed == Some(true) {
248 rb.set_position(
249 utils::transform_to_iso(&global_transform.compute_transform()),
250 true,
251 );
252 rigidbody_set
253 .last_body_transform_set
254 .insert(handle.0, *global_transform);
255 }
256 }
257 }
258 }
259 }
260
261 for (handle, link, velocity) in changed_velocities.iter() {
262 let rigidbody_set = rigid_body_sets
263 .get_mut(link.0)
264 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
265 .into_inner();
266 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
267 rb.set_linvel(velocity.linvel.into(), true);
268 #[allow(clippy::useless_conversion)] rb.set_angvel(velocity.angvel.into(), true);
270 }
271 }
272
273 for (entity, link, handle, mprops) in changed_additional_mass_props.iter() {
274 let rigidbody_set = rigid_body_sets
275 .get_mut(link.0)
276 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
277 .into_inner();
278 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
279 match mprops {
280 AdditionalMassProperties::MassProperties(mprops) => {
281 rb.set_additional_mass_properties(mprops.into_rapier(), true);
282 }
283 AdditionalMassProperties::Mass(mass) => {
284 rb.set_additional_mass(*mass, true);
285 }
286 }
287
288 mass_modified.write(entity.into());
289 }
290 }
291
292 for (handle, link, additional_solver_iters) in changed_additional_solver_iterations.iter() {
293 let rigidbody_set = rigid_body_sets
294 .get_mut(link.0)
295 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
296 .into_inner();
297 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
298 rb.set_additional_solver_iterations(additional_solver_iters.0);
299 }
300 }
301
302 for (handle, link, locked_axes) in changed_locked_axes.iter() {
303 let rigidbody_set = rigid_body_sets
304 .get_mut(link.0)
305 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
306 .into_inner();
307 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
308 rb.set_locked_axes((*locked_axes).into(), true);
309 }
310 }
311
312 for (handle, link, forces) in changed_forces.iter() {
313 let rigidbody_set = rigid_body_sets
314 .get_mut(link.0)
315 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
316 .into_inner();
317 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
318 rb.reset_forces(true);
319 rb.reset_torques(true);
320 rb.add_force(forces.force.into(), true);
321 #[allow(clippy::useless_conversion)] rb.add_torque(forces.torque.into(), true);
323 }
324 }
325
326 for (handle, link, mut impulses) in changed_impulses.iter_mut() {
327 let rigidbody_set = rigid_body_sets
328 .get_mut(link.0)
329 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
330 .into_inner();
331 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
332 rb.apply_impulse(impulses.impulse.into(), true);
333 #[allow(clippy::useless_conversion)] rb.apply_torque_impulse(impulses.torque_impulse.into(), true);
335 impulses.reset();
336 }
337 }
338
339 for (handle, link, gravity_scale) in changed_gravity_scale.iter() {
340 let rigidbody_set = rigid_body_sets
341 .get_mut(link.0)
342 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
343 .into_inner();
344 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
345 rb.set_gravity_scale(gravity_scale.0, true);
346 }
347 }
348
349 for (handle, link, ccd) in changed_ccd.iter() {
350 let rigidbody_set = rigid_body_sets
351 .get_mut(link.0)
352 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
353 .into_inner();
354 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
355 rb.enable_ccd(ccd.enabled);
356 }
357 }
358
359 for (handle, link, soft_ccd) in changed_soft_ccd.iter() {
360 let rigidbody_set = rigid_body_sets
361 .get_mut(link.0)
362 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
363 .into_inner();
364 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
365 rb.set_soft_ccd_prediction(soft_ccd.prediction);
366 }
367 }
368
369 for (handle, link, dominance) in changed_dominance.iter() {
370 let rigidbody_set = rigid_body_sets
371 .get_mut(link.0)
372 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
373 .into_inner();
374 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
375 rb.set_dominance_group(dominance.groups);
376 }
377 }
378
379 for (handle, link, damping) in changed_damping.iter() {
380 let rigidbody_set = rigid_body_sets
381 .get_mut(link.0)
382 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
383 .into_inner();
384 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
385 rb.set_linear_damping(damping.linear_damping);
386 rb.set_angular_damping(damping.angular_damping);
387 }
388 }
389
390 for (handle, link, _) in changed_disabled.iter() {
391 let rigidbody_set = rigid_body_sets
392 .get_mut(link.0)
393 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
394 .into_inner();
395 if let Some(co) = rigidbody_set.bodies.get_mut(handle.0) {
396 co.set_enabled(false);
397 }
398 }
399}
400
401pub fn writeback_rigid_bodies(
404 mut rigid_body_sets: Query<&mut RapierRigidBodySet>,
405 timestep_mode: Res<TimestepMode>,
406 config: Query<&RapierConfiguration>,
407 sim_to_render_time: Query<&SimulationToRenderTime>,
408 global_transforms: Query<&GlobalTransform>,
409 mut writeback: Query<
410 RigidBodyWritebackComponents,
411 (With<RigidBody>, Without<RigidBodyDisabled>),
412 >,
413) {
414 for (handle, link, child_of, transform, mut interpolation, mut velocity, mut sleeping) in
415 writeback.iter_mut()
416 {
417 let config = config
418 .get(link.0)
419 .expect("Could not get `RapierConfiguration`");
420 if !config.physics_pipeline_active {
421 continue;
422 }
423 let handle = handle.0;
424
425 let rigid_body_set = rigid_body_sets
426 .get_mut(link.0)
427 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
428 .into_inner();
429 let sim_to_render_time = sim_to_render_time
430 .get(link.0)
431 .expect("Could not get `SimulationToRenderTime`");
432 if let Some(rb) = rigid_body_set.bodies.get(handle) {
436 let mut interpolated_pos = utils::iso_to_transform(rb.position());
437
438 if let TimestepMode::Interpolated { dt, .. } = *timestep_mode {
439 if let Some(interpolation) = interpolation.as_deref_mut() {
440 if interpolation.end.is_none() {
441 interpolation.end = Some(*rb.position());
442 }
443
444 if let Some(interpolated) =
445 interpolation.lerp_slerp((dt + sim_to_render_time.diff) / dt)
446 {
447 interpolated_pos = utils::iso_to_transform(&interpolated);
448 }
449 }
450 }
451
452 if let Some(mut transform) = transform {
453 interpolated_pos = interpolated_pos.with_scale(transform.scale);
457
458 if let Some(parent_global_transform) =
468 child_of.and_then(|c| global_transforms.get(c.parent()).ok())
469 {
470 let (inverse_parent_scale, inverse_parent_rotation, inverse_parent_translation) =
474 parent_global_transform
475 .affine()
476 .inverse()
477 .to_scale_rotation_translation();
478 let new_rotation = inverse_parent_rotation * interpolated_pos.rotation;
479
480 #[allow(unused_mut)] let mut new_translation = inverse_parent_rotation
482 * inverse_parent_scale
483 * interpolated_pos.translation
484 + inverse_parent_translation;
485
486 #[cfg(feature = "dim2")]
488 {
489 new_translation.z = transform.translation.z;
490 }
491
492 if transform.rotation != new_rotation
493 || transform.translation != new_translation
494 {
495 transform.rotation = new_rotation;
499 transform.translation = new_translation;
500 }
501
502 let new_global_transform = parent_global_transform.mul_transform(*transform);
506
507 rigid_body_set
508 .last_body_transform_set
509 .insert(handle, new_global_transform);
510 } else {
511 #[cfg(feature = "dim2")]
513 {
514 interpolated_pos.translation.z = transform.translation.z;
515 }
516
517 if transform.rotation != interpolated_pos.rotation
518 || transform.translation != interpolated_pos.translation
519 {
520 transform.rotation = interpolated_pos.rotation;
524 transform.translation = interpolated_pos.translation;
525 }
526
527 rigid_body_set
528 .last_body_transform_set
529 .insert(handle, GlobalTransform::from(interpolated_pos));
530 }
531 }
532
533 if let Some(velocity) = &mut velocity {
534 let new_vel = Velocity {
535 linvel: (*rb.linvel()).into(),
536 #[cfg(feature = "dim3")]
537 angvel: (*rb.angvel()).into(),
538 #[cfg(feature = "dim2")]
539 angvel: rb.angvel(),
540 };
541
542 if **velocity != new_vel {
546 **velocity = new_vel;
547 }
548 }
549
550 if let Some(sleeping) = &mut sleeping {
551 if sleeping.sleeping != rb.is_sleeping() {
555 sleeping.sleeping = rb.is_sleeping();
556 }
557 }
558 }
559 }
560}
561
562pub fn init_rigid_bodies(
564 mut commands: Commands,
565 default_context_access: Query<Entity, With<DefaultRapierContext>>,
566 mut rigidbody_sets: Query<(Entity, &mut RapierRigidBodySet)>,
567 rigid_bodies: Query<RigidBodyComponents, Without<RapierRigidBodyHandle>>,
568) {
569 for (
570 (entity, entity_context_link),
571 rb,
572 transform,
573 vel,
574 additional_mass_props,
575 _mass_props,
576 locked_axes,
577 force,
578 gravity_scale,
579 (ccd, soft_ccd),
580 dominance,
581 sleep,
582 damping,
583 disabled,
584 additional_solver_iters,
585 ) in rigid_bodies.iter()
586 {
587 let mut builder = RigidBodyBuilder::new((*rb).into());
588 builder = builder.enabled(disabled.is_none());
589
590 if let Some(transform) = transform {
591 builder = builder.pose(utils::transform_to_iso(&transform.compute_transform()));
592 }
593
594 #[allow(clippy::useless_conversion)] if let Some(vel) = vel {
596 builder = builder.linvel(vel.linvel.into()).angvel(vel.angvel.into());
597 }
598
599 if let Some(locked_axes) = locked_axes {
600 builder = builder.locked_axes((*locked_axes).into())
601 }
602
603 if let Some(gravity_scale) = gravity_scale {
604 builder = builder.gravity_scale(gravity_scale.0);
605 }
606
607 if let Some(ccd) = ccd {
608 builder = builder.ccd_enabled(ccd.enabled)
609 }
610
611 if let Some(soft_ccd) = soft_ccd {
612 builder = builder.soft_ccd_prediction(soft_ccd.prediction)
613 }
614
615 if let Some(dominance) = dominance {
616 builder = builder.dominance_group(dominance.groups)
617 }
618
619 if let Some(sleep) = sleep {
620 builder = builder.sleeping(sleep.sleeping);
621 }
622
623 if let Some(damping) = damping {
624 builder = builder
625 .linear_damping(damping.linear_damping)
626 .angular_damping(damping.angular_damping);
627 }
628
629 if let Some(mprops) = additional_mass_props {
630 builder = match mprops {
631 AdditionalMassProperties::MassProperties(mprops) => {
632 builder.additional_mass_properties(mprops.into_rapier())
633 }
634 AdditionalMassProperties::Mass(mass) => builder.additional_mass(*mass),
635 };
636 }
637
638 if let Some(added_iters) = additional_solver_iters {
639 builder = builder.additional_solver_iterations(added_iters.0);
640 }
641
642 builder = builder.user_data(entity.to_bits() as u128);
643
644 let mut rb = builder.build();
645
646 #[allow(clippy::useless_conversion)] if let Some(force) = force {
648 rb.add_force(force.force.into(), false);
649 rb.add_torque(force.torque.into(), false);
650 }
651
652 if let Some(sleep) = sleep {
657 let activation = rb.activation_mut();
658 activation.normalized_linear_threshold = sleep.normalized_linear_threshold;
659 activation.angular_threshold = sleep.angular_threshold;
660 }
661 let context_entity = entity_context_link.map_or_else(
663 || {
664 let context_entity = default_context_access.single().ok()?;
665 commands
666 .entity(entity)
667 .insert(RapierContextEntityLink(context_entity));
668 Some(context_entity)
669 },
670 |link| Some(link.0),
671 );
672 let Some(context_entity) = context_entity else {
673 continue;
674 };
675
676 let Ok((_, mut rigidbody_set)) = rigidbody_sets.get_mut(context_entity) else {
677 log::error!("Could not find entity {context_entity} with rapier context while initializing {entity}");
678 continue;
679 };
680 let handle = rigidbody_set.bodies.insert(rb);
681 commands
682 .entity(entity)
683 .insert(RapierRigidBodyHandle(handle));
684 rigidbody_set.entity2body.insert(entity, handle);
685
686 if let Some(transform) = transform {
687 rigidbody_set
688 .last_body_transform_set
689 .insert(handle, *transform);
690 }
691 }
692}
693
694pub fn apply_initial_rigid_body_impulses(
700 mut context: Query<(&mut RapierRigidBodySet, &RapierContextColliders)>,
701 mut init_impulses: Query<
704 (Entity, &RapierContextEntityLink, &mut ExternalImpulse),
705 Without<RapierRigidBodyHandle>,
706 >,
707) {
708 for (entity, link, mut impulse) in init_impulses.iter_mut() {
709 let (mut rigidbody_set, context_colliders) =
710 context.get_mut(link.0).expect(RAPIER_CONTEXT_EXPECT_ERROR);
711 let rigidbody_set = &mut *rigidbody_set;
712
713 let bodies = &mut rigidbody_set.bodies;
714 if let Some(rb) = rigidbody_set
715 .entity2body
716 .get(&entity)
717 .and_then(|h| bodies.get_mut(*h))
718 {
719 rb.recompute_mass_properties_from_colliders(&context_colliders.colliders);
721 rb.apply_impulse(impulse.impulse.into(), false);
723
724 #[allow(clippy::useless_conversion)] rb.apply_torque_impulse(impulse.torque_impulse.into(), false);
726
727 impulse.reset();
728 }
729 }
730}