diff options
| author | Crozet Sébastien <developer@crozet.re> | 2021-04-26 17:59:25 +0200 |
|---|---|---|
| committer | Crozet Sébastien <developer@crozet.re> | 2021-04-26 18:00:50 +0200 |
| commit | c32da78f2a6014c491aa3e975fb83ddb7c80610e (patch) | |
| tree | edd20f23270baee1577c486f78d825eb93ea0de0 /src/dynamics/rigid_body_components.rs | |
| parent | aaf80bfa872c6f29b248cab8eb5658ab0d73cb4a (diff) | |
| download | rapier-c32da78f2a6014c491aa3e975fb83ddb7c80610e.tar.gz rapier-c32da78f2a6014c491aa3e975fb83ddb7c80610e.tar.bz2 rapier-c32da78f2a6014c491aa3e975fb83ddb7c80610e.zip | |
Split rigid-bodies and colliders into multiple components
Diffstat (limited to 'src/dynamics/rigid_body_components.rs')
| -rw-r--r-- | src/dynamics/rigid_body_components.rs | 659 |
1 files changed, 659 insertions, 0 deletions
diff --git a/src/dynamics/rigid_body_components.rs b/src/dynamics/rigid_body_components.rs new file mode 100644 index 0000000..855a0e8 --- /dev/null +++ b/src/dynamics/rigid_body_components.rs @@ -0,0 +1,659 @@ +use crate::data::{ComponentSetMut, ComponentSetOption}; +use crate::dynamics::MassProperties; +use crate::geometry::{ + ColliderChanges, ColliderHandle, ColliderMassProperties, ColliderParent, ColliderPosition, + ColliderShape, InteractionGraph, RigidBodyGraphIndex, +}; +use crate::math::{AngVector, AngularInertia, Isometry, Point, Real, Translation, Vector}; +use crate::parry::partitioning::IndexedData; +use crate::utils::WDot; +use num::Zero; + +/// The unique handle of a rigid body added to a `RigidBodySet`. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[repr(transparent)] +pub struct RigidBodyHandle(pub crate::data::arena::Index); + +impl RigidBodyHandle { + /// Converts this handle into its (index, generation) components. + pub fn into_raw_parts(self) -> (u32, u32) { + self.0.into_raw_parts() + } + + /// Reconstructs an handle from its (index, generation) components. + pub fn from_raw_parts(id: u32, generation: u32) -> Self { + Self(crate::data::arena::Index::from_raw_parts(id, generation)) + } + + /// An always-invalid rigid-body handle. + pub fn invalid() -> Self { + Self(crate::data::arena::Index::from_raw_parts( + crate::INVALID_U32, + crate::INVALID_U32, + )) + } +} + +impl IndexedData for RigidBodyHandle { + fn default() -> Self { + Self(IndexedData::default()) + } + + fn index(&self) -> usize { + self.0.index() + } +} + +/// The type of a body, governing the way it is affected by external forces. +#[deprecated(note = "renamed as RigidBodyType")] +pub type BodyStatus = RigidBodyType; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +/// The status of a body, governing the way it is affected by external forces. +pub enum RigidBodyType { + /// A `RigidBodyType::Dynamic` body can be affected by all external forces. + Dynamic, + /// A `RigidBodyType::Static` body cannot be affected by external forces. + Static, + /// A `RigidBodyType::Kinematic` body cannot be affected by any external forces but can be controlled + /// by the user at the position level while keeping realistic one-way interaction with dynamic bodies. + /// + /// One-way interaction means that a kinematic body can push a dynamic body, but a kinematic body + /// cannot be pushed by anything. In other words, the trajectory of a kinematic body can only be + /// modified by the user and is independent from any contact or joint it is involved in. + Kinematic, + // Semikinematic, // A kinematic that performs automatic CCD with the static environment to avoid traversing it? + // Disabled, +} + +impl RigidBodyType { + pub fn is_static(self) -> bool { + self == RigidBodyType::Static + } + + pub fn is_dynamic(self) -> bool { + self == RigidBodyType::Dynamic + } + + pub fn is_kinematic(self) -> bool { + self == RigidBodyType::Kinematic + } +} + +bitflags::bitflags! { + #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] + /// Flags describing how the rigid-body has been modified by the user. + pub struct RigidBodyChanges: u32 { + const MODIFIED = 1 << 0; + const POSITION = 1 << 1; + const SLEEP = 1 << 2; + const COLLIDERS = 1 << 3; + const TYPE = 1 << 4; + } +} + +impl Default for RigidBodyChanges { + fn default() -> Self { + RigidBodyChanges::empty() + } +} + +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Copy)] +pub struct RigidBodyPosition { + /// The world-space position of the rigid-body. + pub position: Isometry<Real>, + /// The next position of the rigid-body. + /// + /// At the beginning of the timestep, and when the + /// timestep is complete we must have position == next_position + /// except for kinematic bodies. + /// + /// The next_position is updated after the velocity and position + /// resolution. Then it is either validated (ie. we set position := set_position) + /// or clamped by CCD. + pub next_position: Isometry<Real>, +} + +impl Default for RigidBodyPosition { + fn default() -> Self { + Self { + position: Isometry::identity(), + next_position: Isometry::identity(), + } + } +} + +impl RigidBodyPosition { + #[must_use] + pub fn interpolate_velocity(&self, inv_dt: Real) -> RigidBodyVelocity { + let dpos = self.next_position * self.position.inverse(); + let angvel; + #[cfg(feature = "dim2")] + { + angvel = dpos.rotation.angle() * inv_dt; + } + #[cfg(feature = "dim3")] + { + angvel = dpos.rotation.scaled_axis() * inv_dt; + } + let linvel = dpos.translation.vector * inv_dt; + RigidBodyVelocity { linvel, angvel } + } + + #[must_use] + pub fn integrate_force_and_velocity( + &self, + dt: Real, + forces: &RigidBodyForces, + vels: &RigidBodyVelocity, + mprops: &RigidBodyMassProps, + ) -> Isometry<Real> { + let new_vels = forces.integrate(dt, vels, mprops); + new_vels.integrate(dt, &self.position, &mprops.mass_properties.local_com) + } +} + +impl<T> From<T> for RigidBodyPosition +where + Isometry<Real>: From<T>, +{ + fn from(position: T) -> Self { + let position = position.into(); + Self { + position, + next_position: position, + } + } +} + +bitflags::bitflags! { + #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] + /// Flags affecting the behavior of the constraints solver for a given contact manifold. + pub struct RigidBodyMassPropsFlags: u8 { + const TRANSLATION_LOCKED = 1 << 0; + const ROTATION_LOCKED_X = 1 << 1; + const ROTATION_LOCKED_Y = 1 << 2; + const ROTATION_LOCKED_Z = 1 << 3; + const ROTATION_LOCKED = Self::ROTATION_LOCKED_X.bits | Self::ROTATION_LOCKED_Y.bits | Self::ROTATION_LOCKED_Z.bits; + } +} + +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Copy)] +pub struct RigidBodyMassProps { + /// Flags for locking rotation and translation. + pub flags: RigidBodyMassPropsFlags, + /// The local mass properties of the rigid-body. + pub mass_properties: MassProperties, + /// The world-space center of mass of the rigid-body. + pub world_com: Point<Real>, + /// The inverse mass taking into account translation locking. + pub effective_inv_mass: Real, + /// The square-root of the world-space inverse angular inertia tensor of the rigid-body, + /// taking into account rotation locking. + pub effective_world_inv_inertia_sqrt: AngularInertia<Real>, +} + +impl Default for RigidBodyMassProps { + fn default() -> Self { + Self { + flags: RigidBodyMassPropsFlags::empty(), + mass_properties: MassProperties::zero(), + world_com: Point::origin(), + effective_inv_mass: 0.0, + effective_world_inv_inertia_sqrt: AngularInertia::zero(), + } + } +} + +impl From<RigidBodyMassPropsFlags> for RigidBodyMassProps { + fn from(flags: RigidBodyMassPropsFlags) -> Self { + Self { + flags, + ..Self::default() + } + } +} + +impl RigidBodyMassProps { + #[must_use] + pub fn with_translations_locked(mut self) -> Self { + self.flags |= RigidBodyMassPropsFlags::TRANSLATION_LOCKED; + self + } + + pub fn effective_mass(&self) -> Real { + crate::utils::inv(self.effective_inv_mass) + } + + pub fn update_world_mass_properties(&mut self, position: &Isometry<Real>) { + self.world_com = self.mass_properties.world_com(&position); + self.effective_inv_mass = self.mass_properties.inv_mass; + self.effective_world_inv_inertia_sqrt = self + .mass_properties + .world_inv_inertia_sqrt(&position.rotation); + + // Take into account translation/rotation locking. + if self + .flags + .contains(RigidBodyMassPropsFlags::TRANSLATION_LOCKED) + { + self.effective_inv_mass = 0.0; + } + + #[cfg(feature = "dim2")] + { + if self + .flags + .contains(RigidBodyMassPropsFlags::ROTATION_LOCKED_Z) + { + self.effective_world_inv_inertia_sqrt = 0.0; + } + } + #[cfg(feature = "dim3")] + { + if self + .flags + .contains(RigidBodyMassPropsFlags::ROTATION_LOCKED_X) + { + self.effective_world_inv_inertia_sqrt.m11 = 0.0; + self.effective_world_inv_inertia_sqrt.m12 = 0.0; + self.effective_world_inv_inertia_sqrt.m13 = 0.0; + } + + if self + .flags + .contains(RigidBodyMassPropsFlags::ROTATION_LOCKED_Y) + { + self.effective_world_inv_inertia_sqrt.m22 = 0.0; + self.effective_world_inv_inertia_sqrt.m12 = 0.0; + self.effective_world_inv_inertia_sqrt.m23 = 0.0; + } + if self + .flags + .contains(RigidBodyMassPropsFlags::ROTATION_LOCKED_Z) + { + self.effective_world_inv_inertia_sqrt.m33 = 0.0; + self.effective_world_inv_inertia_sqrt.m13 = 0.0; + self.effective_world_inv_inertia_sqrt.m23 = 0.0; + } + } + } +} + +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Copy)] +pub struct RigidBodyVelocity { + /// The linear velocity of the rigid-body. + pub linvel: Vector<Real>, + /// The angular velocity of the rigid-body. + pub angvel: AngVector<Real>, +} + +impl Default for RigidBodyVelocity { + fn default() -> Self { + Self::zero() + } +} + +impl RigidBodyVelocity { + #[must_use] + pub fn zero() -> Self { + Self { + linvel: na::zero(), + angvel: na::zero(), + } + } + + #[must_use] + pub fn pseudo_kinetic_energy(&self) -> Real { + self.linvel.norm_squared() + self.angvel.gdot(self.angvel) + } + + #[must_use] + pub fn apply_damping(&self, dt: Real, damping: &RigidBodyDamping) -> Self { + RigidBodyVelocity { + linvel: self.linvel * (1.0 / (1.0 + dt * damping.linear_damping)), + angvel: self.angvel * (1.0 / (1.0 + dt * damping.angular_damping)), + } + } + + #[must_use] + pub fn integrate( + &self, + dt: Real, + init_pos: &Isometry<Real>, + local_com: &Point<Real>, + ) -> Isometry<Real> { + let com = init_pos * local_com; + let shift = Translation::from(com.coords); + let mut result = + shift * Isometry::new(self.linvel * dt, self.angvel * dt) * shift.inverse() * init_pos; + result.rotation.renormalize_fast(); + result + } + + #[must_use] + pub fn is_zero(&self) -> bool { + self.linvel.is_zero() && self.angvel.is_zero() + } +} + +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Copy)] +pub struct RigidBodyDamping { + /// Damping factor for gradually slowing down the translational motion of the rigid-body. + pub linear_damping: Real, + /// Damping factor for gradually slowing down the angular motion of the rigid-body. + pub angular_damping: Real, +} + +impl Default for RigidBodyDamping { + fn default() -> Self { + Self { + linear_damping: 0.0, + angular_damping: 0.0, + } + } +} + +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Copy)] +pub struct RigidBodyForces { + /// Accumulation of external forces (only for dynamic bodies). + pub force: Vector<Real>, + /// Accumulation of external torques (only for dynamic bodies). + pub torque: AngVector<Real>, + pub gravity_scale: Real, +} + +impl Default for RigidBodyForces { + fn default() -> Self { + Self { + force: na::zero(), + torque: na::zero(), + gravity_scale: 1.0, + } + } +} + +impl RigidBodyForces { + #[must_use] + pub fn integrate( + &self, + dt: Real, + init_vels: &RigidBodyVelocity, + mprops: &RigidBodyMassProps, + ) -> RigidBodyVelocity { + let linear_acc = self.force * mprops.effective_inv_mass; + let angular_acc = mprops.effective_world_inv_inertia_sqrt + * (mprops.effective_world_inv_inertia_sqrt * self.torque); + + RigidBodyVelocity { + linvel: init_vels.linvel + linear_acc * dt, + angvel: init_vels.angvel + angular_acc * dt, + } + } + + pub fn add_linear_acceleration(&mut self, gravity: &Vector<Real>, mass: Real) { + self.force += gravity * self.gravity_scale * mass; + } +} + +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Copy)] +pub struct RigidBodyCcd { + pub ccd_thickness: Real, + pub ccd_max_dist: Real, + pub ccd_active: bool, + pub ccd_enabled: bool, +} + +impl Default for RigidBodyCcd { + fn default() -> Self { + Self { + ccd_thickness: 0.0, + ccd_max_dist: 0.0, + ccd_active: false, + ccd_enabled: false, + } + } +} + +impl RigidBodyCcd { + pub fn max_point_velocity(&self, vels: &RigidBodyVelocity) -> Real { + #[cfg(feature = "dim2")] + return vels.linvel.norm() + vels.angvel.abs() * self.ccd_max_dist; + #[cfg(feature = "dim3")] + return vels.linvel.norm() + vels.angvel.norm() * self.ccd_max_dist; + } + + pub fn is_moving_fast( + &self, + dt: Real, + vels: &RigidBodyVelocity, + forces: Option<&RigidBodyForces>, + ) -> bool { + // NOTE: for the threshold we don't use the exact CCD thickness. Theoretically, we + // should use `self.rb_ccd.ccd_thickness - smallest_contact_dist` where `smallest_contact_dist` + // is the deepest contact (the contact with the largest penetration depth, i.e., the + // negative `dist` with the largest absolute value. + // However, getting this penetration depth assumes querying the contact graph from + // the narrow-phase, which can be pretty expensive. So we use the CCD thickness + // divided by 10 right now. We will see in practice if this value is OK or if we + // should use a smaller (to be less conservative) or larger divisor (to be more conservative). + let threshold = self.ccd_thickness / 10.0; + + if let Some(forces) = forces { + let linear_part = (vels.linvel + forces.force * dt).norm(); + #[cfg(feature = "dim2")] + let angular_part = (vels.angvel + forces.torque * dt).abs() * self.ccd_max_dist; + #[cfg(feature = "dim3")] + let angular_part = (vels.angvel + forces.torque * dt).norm() * self.ccd_max_dist; + let vel_with_forces = linear_part + angular_part; + vel_with_forces > threshold + } else { + self.max_point_velocity(vels) * dt > threshold + } + } +} + +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Copy)] +pub struct RigidBodyIds { + pub joint_graph_index: RigidBodyGraphIndex, + pub active_island_id: usize, + pub active_set_id: usize, + pub active_set_offset: usize, + pub active_set_timestamp: u32, +} + +impl Default for RigidBodyIds { + fn default() -> Self { + Self { + joint_graph_index: InteractionGraph::<(), ()>::invalid_graph_index(), + active_island_id: 0, + active_set_id: 0, + active_set_offset: 0, + active_set_timestamp: 0, + } + } +} + +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[derive(Clone, Debug)] +pub struct RigidBodyColliders(pub Vec<ColliderHandle>); + +impl Default for RigidBodyColliders { + fn default() -> Self { + Self(vec![]) + } +} + +impl RigidBodyColliders { + pub fn detach_collider( + &mut self, + rb_changes: &mut RigidBodyChanges, + co_handle: ColliderHandle, + ) { + if let Some(i) = self.0.iter().position(|e| *e == co_handle) { + rb_changes.set( + RigidBodyChanges::MODIFIED | RigidBodyChanges::COLLIDERS, + true, + ); + self.0.swap_remove(i); + } + } + + pub fn attach_collider( + &mut self, + rb_changes: &mut RigidBodyChanges, + rb_ccd: &mut RigidBodyCcd, + rb_mprops: &mut RigidBodyMassProps, + rb_pos: &RigidBodyPosition, + co_handle: ColliderHandle, + co_pos: &mut ColliderPosition, + co_parent: &ColliderParent, + co_shape: &ColliderShape, + co_mprops: &ColliderMassProperties, + ) { + rb_changes.set( + RigidBodyChanges::MODIFIED | RigidBodyChanges::COLLIDERS, + true, + ); + + co_pos.0 = rb_pos.position * co_parent.pos_wrt_parent; + rb_ccd.ccd_thickness = rb_ccd.ccd_thickness.min(co_shape.ccd_thickness()); + + let shape_bsphere = co_shape.compute_bounding_sphere(&co_parent.pos_wrt_parent); + rb_ccd.ccd_max_dist = rb_ccd + .ccd_max_dist + .max(shape_bsphere.center.coords.norm() + shape_bsphere.radius); + + let mass_properties = co_mprops + .mass_properties(&**co_shape) + .transform_by(&co_parent.pos_wrt_parent); + self.0.push(co_handle); + rb_mprops.mass_properties += mass_properties; + rb_mprops.update_world_mass_properties(&rb_pos.position); + } + + pub fn update_positions<Colliders>( + &self, + colliders: &mut Colliders, + modified_colliders: &mut Vec<ColliderHandle>, + parent_pos: &Isometry<Real>, + ) where + Colliders: ComponentSetMut<ColliderPosition> + + ComponentSetMut<ColliderChanges> + + ComponentSetOption<ColliderParent>, + { + for handle in &self.0 { + // NOTE: the ColliderParent component must exist if we enter this method. + let co_parent: &ColliderParent = colliders + .get(handle.0) + .expect("Could not find the ColliderParent component."); + let new_pos = parent_pos * co_parent.pos_wrt_parent; + + // Set the modification flag so we can benefit from the modification-tracking + // when updating the narrow-phase/broad-phase afterwards. + colliders.map_mut_internal(handle.0, |co_changes: &mut ColliderChanges| { + if !co_changes.contains(ColliderChanges::MODIFIED) { + modified_colliders.push(*handle); + } + + *co_changes |= ColliderChanges::POSITION; + }); + colliders.set_internal(handle.0, ColliderPosition(new_pos)); + } + } +} + +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Copy)] +pub struct RigidBodyDominance(pub i8); + +impl Default for RigidBodyDominance { + fn default() -> Self { + RigidBodyDominance(0) + } +} + +impl RigidBodyDominance { + pub fn effective_group(&self, status: &RigidBodyType) -> i16 { + if status.is_dynamic() { + self.0 as i16 + } else { + i8::MAX as i16 + 1 + } + } +} + +/// The rb_activation status of a body. +/// +/// This controls whether a body is sleeping or not. +/// If the threshold is negative, the body never sleeps. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +pub struct RigidBodyActivation { + /// The threshold pseudo-kinetic energy bellow which the body can fall asleep. + pub threshold: Real, + /// The current pseudo-kinetic energy of the body. + pub energy: Real, + /// Is this body already sleeping? + pub sleeping: bool, +} + +impl Default for RigidBodyActivation { + fn default() -> Self { + Self::new_active() + } +} + +impl RigidBodyActivation { + /// The default amount of energy bellow which a body can be put to sleep by nphysics. + pub fn default_threshold() -> Real { + 0.01 + } + + /// Create a new rb_activation status initialised with the default rb_activation threshold and is active. + pub fn new_active() -> Self { + RigidBodyActivation { + threshold: Self::default_threshold(), + energy: Self::default_threshold() * 4.0, + sleeping: false, + } + } + + /// Create a new rb_activation status initialised with the default rb_activation threshold and is inactive. + pub fn new_inactive() -> Self { + RigidBodyActivation { + threshold: Self::default_threshold(), + energy: 0.0, + sleeping: true, + } + } + + /// Returns `true` if the body is not asleep. + #[inline] + pub fn is_active(&self) -> bool { + self.energy != 0.0 + } + + #[inline] + pub fn wake_up(&mut self, strong: bool) { + self.sleeping = false; + if strong || self.energy == 0.0 { + self.energy = self.threshold.abs() * 2.0; + } + } + + #[inline] + pub fn sleep(&mut self) { + self.energy = 0.0; + self.sleeping = true; + } +} |
