1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
//! DynamicBody component allows simultaing phyiscs of a body.
use std::sync::Mutex;
use crate::prelude::*;
pub type SimulationCommand = dyn FnOnce(&mut rapier::RigidBody) + 'static + Send;
/// Bodies with `DynamicBody` will be simulated dynamically
/// if `is_dynamic` is True. Currently `DynamicBody` requires component
/// [`KinematicBody`], as it shares shape.
///
/// `if is_dynamic` is false, body is treated as kinematic.
#[derive(Default, Clone, HasSchema)]
#[repr(C)]
pub struct DynamicBody {
/// Is body simulating or kinematic mode?
pub is_dynamic: bool,
/// This transform is used to determine if position has moved in gameplay code outside of rapier sim.
/// Is only relevant when `is_dynamic` = true. It is updated whenever transform is synced to or from rapier.
///
/// If this does not match entity `Transform` at beginning of physics update, rapier simulation body
/// will be teleported to match gameplay.
///
/// See [`Self::simulation_transform_needs_update`] for context.
last_rapier_synced_transform: Transform,
/// Simulation command queue. See [`Self::push_simulation_command`] for details.
#[schema(opaque)]
command_queue: Arc<Mutex<Vec<Box<SimulationCommand>>>>,
}
impl DynamicBody {
/// Create new `DynamicBody`. is_dynamic = true simulates physics, false remains kinematic.
pub fn new(is_dynamic: bool) -> Self {
Self {
is_dynamic,
..Default::default()
}
}
/// Check if rapier simulation's body's transform is dirty (If it was modified outside of
/// physics simulation). Always returns true if object is not simulating.
///
/// Updates `last_rapier_synced_transform` if called, should only be called before updating rapier positions from gameplay.
pub fn simulation_transform_needs_update(&mut self, transform: &Transform) -> bool {
if !self.is_dynamic {
// Body not simulating, we should always update transform in sim
return true;
}
// Only use 2D translation + rotation, as this is what rapier sim uses.
let last_translation = self.last_rapier_synced_transform.translation.xy();
let current_translation = transform.translation.xy();
let last_rotation = self
.last_rapier_synced_transform
.rotation
.to_euler(EulerRot::XYZ)
.2;
let current_rotation = transform.rotation.to_euler(EulerRot::XYZ).2;
// transform was modified in gameplay outside of rapier sim
if last_translation != current_translation || last_rotation != current_rotation {
self.last_rapier_synced_transform.translation = transform.translation;
self.last_rapier_synced_transform.rotation = transform.rotation;
return true;
}
// Transform was not modified outside of rapier sim, is not dirty
false
}
/// Update `last_rapier_synced_transform` from simulation output so we can determine when gameplay has modified transform
/// outside of rapier for [`Self::simulation_transform_needs_update`].
///
/// This does NOT update position of entity.
pub fn update_last_rapier_synced_transform(&mut self, translation: Vec3, rotation: Quat) {
self.last_rapier_synced_transform.translation = translation;
self.last_rapier_synced_transform.rotation = rotation;
}
/// Push command that is executed before next physics step, after body is guranteed to be initialized in rapier
/// and set to [`rapier::RigidBodyType::Dynamic`] (if transitioning from Kinematic to Dynamic).
///
/// Commands are only executed if `is_dynamic` = true (body is simulating) next frame! Otherwise they are discarded.
/// This restriction is in place to prevent switching back to kinematic and modifying simulation state in an unintentional way.
///
/// Commands are executed in order they are pushed. If you need to immediately apply changes, use [`CollisionWorld::mutate_rigidbody`].
pub fn push_simulation_command(&mut self, command: Box<SimulationCommand>) {
self.command_queue.lock().unwrap().push(command);
}
/// Retrieve [`SimulationCommand`] queue for processing. Values are moved out of self, flushing queue.
pub fn simulation_commands(&mut self) -> Vec<Box<SimulationCommand>> {
let mut dummy: Vec<Box<SimulationCommand>> = vec![];
let mut queue = self.command_queue.lock().unwrap();
std::mem::swap(&mut *queue, &mut dummy);
dummy
}
}