#[cfg(doc)]
use super::IntegrationParameters;
use crate::control::PdErrors;
#[cfg(doc)]
use crate::control::PidController;
use crate::dynamics::MassProperties;
use crate::geometry::{
ColliderChanges, ColliderHandle, ColliderMassProps, ColliderParent, ColliderPosition,
ColliderSet, ColliderShape, ModifiedColliders,
};
use crate::math::{AngVector, AngularInertia, Pose, Real, Rotation, Vector};
use crate::utils::{
AngularInertiaOps, CrossProduct, DotProduct, PoseOps, ScalarType, SimdRealCopy,
};
use num::Zero;
#[cfg(feature = "dim2")]
use parry::math::Rot2;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[repr(transparent)]
pub struct RigidBodyHandle(pub crate::data::arena::Index);
impl RigidBodyHandle {
pub fn into_raw_parts(self) -> (u32, u32) {
self.0.into_raw_parts()
}
pub fn from_raw_parts(id: u32, generation: u32) -> Self {
Self(crate::data::arena::Index::from_raw_parts(id, generation))
}
pub fn invalid() -> Self {
Self(crate::data::arena::Index::from_raw_parts(
crate::INVALID_U32,
crate::INVALID_U32,
))
}
}
#[deprecated(note = "renamed as RigidBodyType")]
pub type BodyStatus = RigidBodyType;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub enum RigidBodyType {
Dynamic = 0,
Fixed = 1,
KinematicPositionBased = 2,
KinematicVelocityBased = 3,
}
impl RigidBodyType {
pub fn is_fixed(self) -> bool {
self == RigidBodyType::Fixed
}
pub fn is_dynamic(self) -> bool {
self == RigidBodyType::Dynamic
}
pub fn is_kinematic(self) -> bool {
self == RigidBodyType::KinematicPositionBased
|| self == RigidBodyType::KinematicVelocityBased
}
pub fn is_dynamic_or_kinematic(self) -> bool {
self != RigidBodyType::Fixed
}
}
bitflags::bitflags! {
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct RigidBodyChanges: u32 {
const IN_MODIFIED_SET = 1 << 0;
const POSITION = 1 << 1;
const SLEEP = 1 << 2;
const COLLIDERS = 1 << 3;
const TYPE = 1 << 4;
const DOMINANCE = 1 << 5;
const LOCAL_MASS_PROPERTIES = 1 << 6;
const ENABLED_OR_DISABLED = 1 << 7;
}
}
impl Default for RigidBodyChanges {
fn default() -> Self {
RigidBodyChanges::empty()
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Copy, PartialEq)]
pub struct RigidBodyPosition {
pub position: Pose,
pub next_position: Pose,
}
impl Default for RigidBodyPosition {
fn default() -> Self {
Self {
position: Pose::IDENTITY,
next_position: Pose::IDENTITY,
}
}
}
impl RigidBodyPosition {
#[must_use]
pub fn interpolate_velocity(&self, inv_dt: Real, local_com: Vector) -> RigidBodyVelocity<Real> {
let pose_err = self.pose_errors(local_com);
RigidBodyVelocity {
linvel: pose_err.linear * inv_dt,
angvel: pose_err.angular * inv_dt,
}
}
#[must_use]
pub fn integrate_forces_and_velocities(
&self,
dt: Real,
forces: &RigidBodyForces,
vels: &RigidBodyVelocity<Real>,
mprops: &RigidBodyMassProps,
) -> Pose {
let new_vels = forces.integrate(dt, vels, mprops);
let local_com = mprops.local_mprops.local_com;
new_vels.integrate(dt, &self.position, &local_com)
}
pub fn pose_errors(&self, local_com: Vector) -> PdErrors {
let com = self.position * local_com;
let shift = Pose::from_translation(com);
let dpos = shift.inverse() * self.next_position * self.position.inverse() * shift;
let angular;
#[cfg(feature = "dim2")]
{
angular = dpos.rotation.angle();
}
#[cfg(feature = "dim3")]
{
angular = dpos.rotation.to_scaled_axis();
}
let linear = dpos.translation;
PdErrors { linear, angular }
}
}
impl<T> From<T> for RigidBodyPosition
where
Pose: 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))]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct AxesMask: u8 {
const LIN_X = 1 << 0;
const LIN_Y = 1 << 1;
#[cfg(feature = "dim3")]
const LIN_Z = 1 << 2;
#[cfg(feature = "dim3")]
const ANG_X = 1 << 3;
#[cfg(feature = "dim3")]
const ANG_Y = 1 << 4;
const ANG_Z = 1 << 5;
}
}
impl Default for AxesMask {
fn default() -> Self {
AxesMask::empty()
}
}
bitflags::bitflags! {
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct LockedAxes: u8 {
const TRANSLATION_LOCKED_X = 1 << 0;
const TRANSLATION_LOCKED_Y = 1 << 1;
const TRANSLATION_LOCKED_Z = 1 << 2;
const TRANSLATION_LOCKED = Self::TRANSLATION_LOCKED_X.bits() | Self::TRANSLATION_LOCKED_Y.bits() | Self::TRANSLATION_LOCKED_Z.bits();
const ROTATION_LOCKED_X = 1 << 3;
const ROTATION_LOCKED_Y = 1 << 4;
const ROTATION_LOCKED_Z = 1 << 5;
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(Copy, Clone, Debug, PartialEq)]
pub enum RigidBodyAdditionalMassProps {
MassProps(MassProperties),
Mass(Real),
}
impl Default for RigidBodyAdditionalMassProps {
fn default() -> Self {
RigidBodyAdditionalMassProps::MassProps(MassProperties::default())
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct RigidBodyMassProps {
pub world_com: Vector,
pub effective_inv_mass: Vector,
pub effective_world_inv_inertia: AngularInertia,
pub local_mprops: MassProperties,
pub flags: LockedAxes,
pub additional_local_mprops: Option<Box<RigidBodyAdditionalMassProps>>,
}
impl Default for RigidBodyMassProps {
fn default() -> Self {
Self {
flags: LockedAxes::empty(),
local_mprops: MassProperties::zero(),
additional_local_mprops: None,
world_com: Vector::ZERO,
effective_inv_mass: Vector::ZERO,
effective_world_inv_inertia: AngularInertia::zero(),
}
}
}
impl From<LockedAxes> for RigidBodyMassProps {
fn from(flags: LockedAxes) -> Self {
Self {
flags,
..Self::default()
}
}
}
impl From<MassProperties> for RigidBodyMassProps {
fn from(local_mprops: MassProperties) -> Self {
Self {
local_mprops,
..Default::default()
}
}
}
impl RigidBodyMassProps {
#[must_use]
pub fn mass(&self) -> Real {
crate::utils::inv(self.local_mprops.inv_mass)
}
#[must_use]
pub fn effective_mass(&self) -> Vector {
self.effective_inv_mass.map(crate::utils::inv)
}
#[must_use]
pub fn effective_angular_inertia(&self) -> AngularInertia {
#[allow(unused_mut)] let mut ang_inertia = self.effective_world_inv_inertia;
#[cfg(feature = "dim3")]
{
if self.flags.contains(LockedAxes::ROTATION_LOCKED_X) {
ang_inertia.m11 = 1.0;
}
if self.flags.contains(LockedAxes::ROTATION_LOCKED_Y) {
ang_inertia.m22 = 1.0;
}
if self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
ang_inertia.m33 = 1.0;
}
}
#[allow(unused_mut)] let mut result = ang_inertia.inverse();
#[cfg(feature = "dim3")]
{
if self.flags.contains(LockedAxes::ROTATION_LOCKED_X) {
result.m11 = 0.0;
}
if self.flags.contains(LockedAxes::ROTATION_LOCKED_Y) {
result.m22 = 0.0;
}
if self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
result.m33 = 0.0;
}
}
result
}
pub fn recompute_mass_properties_from_colliders(
&mut self,
colliders: &ColliderSet,
attached_colliders: &RigidBodyColliders,
body_type: RigidBodyType,
position: &Pose,
) {
let added_mprops = self
.additional_local_mprops
.as_ref()
.map(|mprops| **mprops)
.unwrap_or_else(|| RigidBodyAdditionalMassProps::MassProps(MassProperties::default()));
self.local_mprops = MassProperties::default();
for handle in &attached_colliders.0 {
if let Some(co) = colliders.get(*handle) {
if co.is_enabled() {
if let Some(co_parent) = co.parent {
let to_add = co
.mprops
.mass_properties(&*co.shape)
.transform_by(&co_parent.pos_wrt_parent);
self.local_mprops += to_add;
}
}
}
}
match added_mprops {
RigidBodyAdditionalMassProps::MassProps(mprops) => {
self.local_mprops += mprops;
}
RigidBodyAdditionalMassProps::Mass(mass) => {
let new_mass = self.local_mprops.mass() + mass;
self.local_mprops.set_mass(new_mass, true);
}
}
self.update_world_mass_properties(body_type, position);
}
pub fn update_world_mass_properties(&mut self, body_type: RigidBodyType, position: &Pose) {
self.world_com = self.local_mprops.world_com(position);
self.effective_inv_mass = Vector::splat(self.local_mprops.inv_mass);
self.effective_world_inv_inertia = self.local_mprops.world_inv_inertia(&position.rotation);
if !body_type.is_dynamic() || self.flags.contains(LockedAxes::TRANSLATION_LOCKED_X) {
self.effective_inv_mass.x = 0.0;
}
if !body_type.is_dynamic() || self.flags.contains(LockedAxes::TRANSLATION_LOCKED_Y) {
self.effective_inv_mass.y = 0.0;
}
#[cfg(feature = "dim3")]
if !body_type.is_dynamic() || self.flags.contains(LockedAxes::TRANSLATION_LOCKED_Z) {
self.effective_inv_mass.z = 0.0;
}
#[cfg(feature = "dim2")]
{
if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
self.effective_world_inv_inertia = 0.0;
}
}
#[cfg(feature = "dim3")]
{
if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_X) {
self.effective_world_inv_inertia.m11 = 0.0;
self.effective_world_inv_inertia.m12 = 0.0;
self.effective_world_inv_inertia.m13 = 0.0;
}
if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_Y) {
self.effective_world_inv_inertia.m22 = 0.0;
self.effective_world_inv_inertia.m12 = 0.0;
self.effective_world_inv_inertia.m23 = 0.0;
}
if !body_type.is_dynamic() || self.flags.contains(LockedAxes::ROTATION_LOCKED_Z) {
self.effective_world_inv_inertia.m33 = 0.0;
self.effective_world_inv_inertia.m13 = 0.0;
self.effective_world_inv_inertia.m23 = 0.0;
}
}
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Copy, PartialEq)]
pub struct RigidBodyVelocity<T: ScalarType> {
pub linvel: T::Vector,
pub angvel: T::AngVector,
}
impl Default for RigidBodyVelocity<Real> {
fn default() -> Self {
Self::zero()
}
}
impl RigidBodyVelocity<Real> {
#[must_use]
#[cfg(feature = "dim2")]
pub fn new(linvel: Vector, angvel: AngVector) -> Self {
Self { linvel, angvel }
}
#[must_use]
#[cfg(feature = "dim3")]
pub fn new(linvel: Vector, angvel: AngVector) -> Self {
Self {
linvel: Vector::new(linvel.x, linvel.y, linvel.z),
angvel: AngVector::new(angvel.x, angvel.y, angvel.z),
}
}
#[must_use]
#[cfg(feature = "dim2")]
pub fn from_slice(slice: &[Real]) -> Self {
Self {
linvel: Vector::new(slice[0], slice[1]),
angvel: slice[2],
}
}
#[must_use]
#[cfg(feature = "dim3")]
pub fn from_slice(slice: &[Real]) -> Self {
Self {
linvel: Vector::new(slice[0], slice[1], slice[2]),
angvel: AngVector::new(slice[3], slice[4], slice[5]),
}
}
#[must_use]
pub fn zero() -> Self {
Self {
linvel: Default::default(),
angvel: Default::default(),
}
}
#[inline]
pub fn as_slice(&self) -> &[Real] {
self.as_vector().as_slice()
}
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [Real] {
self.as_vector_mut().as_mut_slice()
}
#[inline]
#[cfg(feature = "dim2")]
pub fn as_vector(&self) -> &na::Vector3<Real> {
unsafe { std::mem::transmute(self) }
}
#[inline]
#[cfg(feature = "dim2")]
pub fn as_vector_mut(&mut self) -> &mut na::Vector3<Real> {
unsafe { std::mem::transmute(self) }
}
#[inline]
#[cfg(feature = "dim3")]
pub fn as_vector(&self) -> &na::Vector6<Real> {
unsafe { std::mem::transmute(self) }
}
#[inline]
#[cfg(feature = "dim3")]
pub fn as_vector_mut(&mut self) -> &mut na::Vector6<Real> {
unsafe { std::mem::transmute(self) }
}
#[must_use]
#[cfg(feature = "dim2")]
pub fn transformed(self, rotation: &Rotation) -> Self {
Self {
linvel: *rotation * self.linvel,
angvel: self.angvel,
}
}
#[must_use]
#[cfg(feature = "dim3")]
pub fn transformed(self, rotation: &Rotation) -> Self {
Self {
linvel: *rotation * self.linvel,
angvel: *rotation * self.angvel,
}
}
#[must_use]
pub fn pseudo_kinetic_energy(&self) -> Real {
0.5 * (self.linvel.length_squared() + self.angvel.gdot(self.angvel))
}
#[must_use]
#[cfg(feature = "dim2")]
pub fn velocity_at_point(&self, point: Vector, world_com: Vector) -> Vector {
let dpt = point - world_com;
self.linvel + self.angvel.gcross(dpt)
}
#[must_use]
#[cfg(feature = "dim3")]
pub fn velocity_at_point(&self, point: Vector, world_com: Vector) -> Vector {
let dpt = point - world_com;
self.linvel + self.angvel.gcross(dpt)
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.linvel == Vector::ZERO && self.angvel == AngVector::default()
}
#[must_use]
#[profiling::function]
pub fn kinetic_energy(&self, rb_mprops: &RigidBodyMassProps) -> Real {
let mut energy = (rb_mprops.mass() * self.linvel.length_squared()) / 2.0;
#[cfg(feature = "dim2")]
if !num::Zero::is_zero(&rb_mprops.effective_world_inv_inertia) {
let inertia = 1.0 / rb_mprops.effective_world_inv_inertia;
energy += inertia * self.angvel * self.angvel / 2.0;
}
#[cfg(feature = "dim3")]
if !rb_mprops.effective_world_inv_inertia.is_zero() {
let inertia = rb_mprops.effective_world_inv_inertia.inverse_unchecked();
energy += self.angvel.gdot(inertia * self.angvel) / 2.0;
}
energy
}
pub fn apply_impulse(&mut self, rb_mprops: &RigidBodyMassProps, impulse: Vector) {
self.linvel += impulse * rb_mprops.effective_inv_mass;
}
#[cfg(feature = "dim2")]
pub fn apply_torque_impulse(&mut self, rb_mprops: &RigidBodyMassProps, torque_impulse: Real) {
self.angvel += rb_mprops.effective_world_inv_inertia * torque_impulse;
}
#[cfg(feature = "dim3")]
pub fn apply_torque_impulse(&mut self, rb_mprops: &RigidBodyMassProps, torque_impulse: Vector) {
self.angvel += rb_mprops.effective_world_inv_inertia * torque_impulse;
}
#[cfg(feature = "dim2")]
pub fn apply_impulse_at_point(
&mut self,
rb_mprops: &RigidBodyMassProps,
impulse: Vector,
point: Vector,
) {
let torque_impulse = (point - rb_mprops.world_com).perp_dot(impulse);
self.apply_impulse(rb_mprops, impulse);
self.apply_torque_impulse(rb_mprops, torque_impulse);
}
#[cfg(feature = "dim3")]
pub fn apply_impulse_at_point(
&mut self,
rb_mprops: &RigidBodyMassProps,
impulse: Vector,
point: Vector,
) {
let torque_impulse = (point - rb_mprops.world_com).cross(impulse);
self.apply_impulse(rb_mprops, impulse);
self.apply_torque_impulse(rb_mprops, torque_impulse);
}
}
impl<T: ScalarType> RigidBodyVelocity<T> {
#[must_use]
pub fn apply_damping(&self, dt: T, damping: &RigidBodyDamping<T>) -> Self {
let one = T::one();
RigidBodyVelocity {
linvel: self.linvel * (one / (one + dt * damping.linear_damping)),
angvel: self.angvel * (one / (one + dt * damping.angular_damping)),
}
}
#[must_use]
#[inline]
#[allow(clippy::let_and_return)] pub fn integrate(&self, dt: T, init_pos: &T::Pose, local_com: &T::Vector) -> T::Pose {
let com = *init_pos * *local_com;
let result = init_pos
.append_translation(-com)
.append_rotation(self.angvel * dt)
.append_translation(com + self.linvel * dt);
result
}
}
impl RigidBodyVelocity<Real> {
#[inline]
#[cfg(feature = "dim2")]
pub(crate) fn integrate_linearized(
&self,
dt: Real,
translation: &mut Vector,
rotation: &mut Rotation,
) {
let dang = self.angvel * dt;
let new_cos = rotation.re - dang * rotation.im;
let new_sin = rotation.im + dang * rotation.re;
*rotation = Rot2::from_cos_sin_unchecked(new_cos, new_sin);
rotation.normalize_mut();
*translation += self.linvel * dt;
}
#[inline]
#[cfg(feature = "dim3")]
pub(crate) fn integrate_linearized(
&self,
dt: Real,
translation: &mut Vector,
rotation: &mut Rotation,
) {
let hang = self.angvel * (dt * 0.5);
let id_plus_hang = Rotation::from_xyzw(hang.x, hang.y, hang.z, 1.0);
*rotation = id_plus_hang * *rotation;
*rotation = rotation.normalize();
*translation += self.linvel * dt;
}
}
impl std::ops::Mul<Real> for RigidBodyVelocity<Real> {
type Output = Self;
fn mul(self, rhs: Real) -> Self {
RigidBodyVelocity {
linvel: self.linvel * rhs,
angvel: self.angvel * rhs,
}
}
}
impl std::ops::Add<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
RigidBodyVelocity {
linvel: self.linvel + rhs.linvel,
angvel: self.angvel + rhs.angvel,
}
}
}
impl std::ops::AddAssign<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
fn add_assign(&mut self, rhs: Self) {
self.linvel += rhs.linvel;
self.angvel += rhs.angvel;
}
}
impl std::ops::Sub<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
RigidBodyVelocity {
linvel: self.linvel - rhs.linvel,
angvel: self.angvel - rhs.angvel,
}
}
}
impl std::ops::SubAssign<RigidBodyVelocity<Real>> for RigidBodyVelocity<Real> {
fn sub_assign(&mut self, rhs: Self) {
self.linvel -= rhs.linvel;
self.angvel -= rhs.angvel;
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Copy, PartialEq)]
pub struct RigidBodyDamping<T> {
pub linear_damping: T,
pub angular_damping: T,
}
impl<T: SimdRealCopy> Default for RigidBodyDamping<T> {
fn default() -> Self {
Self {
linear_damping: T::zero(),
angular_damping: T::zero(),
}
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Copy, PartialEq)]
pub struct RigidBodyForces {
pub force: Vector,
pub torque: AngVector,
pub gravity_scale: Real,
pub user_force: Vector,
pub user_torque: AngVector,
#[cfg(feature = "dim3")]
pub gyroscopic_forces_enabled: bool,
}
impl Default for RigidBodyForces {
fn default() -> Self {
#[cfg(feature = "dim2")]
return Self {
force: Vector::ZERO,
torque: 0.0,
gravity_scale: 1.0,
user_force: Vector::ZERO,
user_torque: 0.0,
};
#[cfg(feature = "dim3")]
return Self {
force: Vector::ZERO,
torque: AngVector::ZERO,
gravity_scale: 1.0,
user_force: Vector::ZERO,
user_torque: AngVector::ZERO,
gyroscopic_forces_enabled: false,
};
}
}
impl RigidBodyForces {
#[must_use]
pub fn integrate(
&self,
dt: Real,
init_vels: &RigidBodyVelocity<Real>,
mprops: &RigidBodyMassProps,
) -> RigidBodyVelocity<Real> {
let linear_acc = self.force * mprops.effective_inv_mass;
let angular_acc = mprops.effective_world_inv_inertia * self.torque;
RigidBodyVelocity {
linvel: init_vels.linvel + linear_acc * dt,
angvel: init_vels.angvel + angular_acc * dt,
}
}
pub fn compute_effective_force_and_torque(&mut self, gravity: Vector, mass: Vector) {
self.force = self.user_force + gravity * mass * self.gravity_scale;
self.torque = self.user_torque;
}
pub fn apply_force_at_point(
&mut self,
rb_mprops: &RigidBodyMassProps,
force: Vector,
point: Vector,
) {
self.user_force += force;
self.user_torque += (point - rb_mprops.world_com).gcross(force);
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Copy, PartialEq)]
pub struct RigidBodyCcd {
pub ccd_thickness: Real,
pub ccd_max_dist: Real,
pub ccd_active: bool,
pub ccd_enabled: bool,
pub soft_ccd_prediction: Real,
}
impl Default for RigidBodyCcd {
fn default() -> Self {
Self {
ccd_thickness: Real::MAX,
ccd_max_dist: 0.0,
ccd_active: false,
ccd_enabled: false,
soft_ccd_prediction: 0.0,
}
}
}
impl RigidBodyCcd {
pub fn max_point_velocity(&self, vels: &RigidBodyVelocity<Real>) -> Real {
#[cfg(feature = "dim2")]
return vels.linvel.length() + vels.angvel.abs() * self.ccd_max_dist;
#[cfg(feature = "dim3")]
return vels.linvel.length() + vels.angvel.length() * self.ccd_max_dist;
}
pub fn is_moving_fast(
&self,
dt: Real,
vels: &RigidBodyVelocity<Real>,
forces: Option<&RigidBodyForces>,
) -> bool {
let threshold = self.ccd_thickness / 10.0;
if let Some(forces) = forces {
let linear_part = (vels.linvel + forces.force * dt).length();
#[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).length() * 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, PartialEq, Eq, Hash)]
pub struct RigidBodyIds {
pub(crate) active_island_id: usize,
pub(crate) active_set_id: usize,
pub(crate) active_set_timestamp: u32,
}
impl Default for RigidBodyIds {
fn default() -> Self {
Self {
active_island_id: usize::MAX,
active_set_id: usize::MAX,
active_set_timestamp: 0,
}
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct RigidBodyColliders(pub Vec<ColliderHandle>);
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::COLLIDERS, true);
self.0.swap_remove(i);
}
}
pub fn attach_collider(
&mut self,
rb_type: RigidBodyType,
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: &ColliderMassProps,
) {
rb_changes.set(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.length() + 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.local_mprops += mass_properties;
rb_mprops.update_world_mass_properties(rb_type, &rb_pos.position);
}
pub(crate) fn update_positions(
&self,
colliders: &mut ColliderSet,
modified_colliders: &mut ModifiedColliders,
parent_pos: &Pose,
) {
for handle in &self.0 {
let co = colliders.index_mut_internal(*handle);
let new_pos = parent_pos * co.parent.as_ref().unwrap().pos_wrt_parent;
modified_colliders.push_once(*handle, co);
co.changes |= ColliderChanges::POSITION;
co.pos = ColliderPosition(new_pos);
}
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Default, Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RigidBodyDominance(pub i8);
impl RigidBodyDominance {
pub fn effective_group(&self, status: &RigidBodyType) -> i16 {
if status.is_dynamic_or_kinematic() {
self.0 as i16
} else {
i8::MAX as i16 + 1
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub(crate) enum SleepRootState {
Traversed,
TraversalPending,
#[default]
Unknown,
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub struct RigidBodyActivation {
pub normalized_linear_threshold: Real,
pub angular_threshold: Real,
pub time_until_sleep: Real,
pub time_since_can_sleep: Real,
pub sleeping: bool,
pub(crate) sleep_root_state: SleepRootState,
}
impl Default for RigidBodyActivation {
fn default() -> Self {
Self::active()
}
}
impl RigidBodyActivation {
pub fn default_normalized_linear_threshold() -> Real {
0.4
}
pub fn default_angular_threshold() -> Real {
0.5
}
pub fn default_time_until_sleep() -> Real {
2.0
}
pub fn active() -> Self {
RigidBodyActivation {
normalized_linear_threshold: Self::default_normalized_linear_threshold(),
angular_threshold: Self::default_angular_threshold(),
time_until_sleep: Self::default_time_until_sleep(),
time_since_can_sleep: 0.0,
sleeping: false,
sleep_root_state: SleepRootState::Unknown,
}
}
pub fn inactive() -> Self {
RigidBodyActivation {
normalized_linear_threshold: Self::default_normalized_linear_threshold(),
angular_threshold: Self::default_angular_threshold(),
time_until_sleep: Self::default_time_until_sleep(),
time_since_can_sleep: Self::default_time_until_sleep(),
sleeping: true,
sleep_root_state: SleepRootState::Unknown,
}
}
pub fn cannot_sleep() -> Self {
RigidBodyActivation {
normalized_linear_threshold: -1.0,
angular_threshold: -1.0,
..Self::active()
}
}
#[inline]
pub fn is_active(&self) -> bool {
!self.sleeping
}
#[inline]
pub fn wake_up(&mut self, strong: bool) {
self.sleeping = false;
if self.sleep_root_state != SleepRootState::TraversalPending {
self.sleep_root_state = SleepRootState::Unknown;
}
if strong {
self.time_since_can_sleep = 0.0;
}
}
#[inline]
pub fn sleep(&mut self) {
self.sleeping = true;
self.time_since_can_sleep = self.time_until_sleep;
}
pub fn is_eligible_for_sleep(&self) -> bool {
self.time_since_can_sleep >= self.time_until_sleep
}
pub(crate) fn update_energy(
&mut self,
body_type: RigidBodyType,
length_unit: Real,
sq_linvel: Real,
sq_angvel: Real,
dt: Real,
) {
let can_sleep = match body_type {
RigidBodyType::Dynamic => {
let linear_threshold = self.normalized_linear_threshold * length_unit;
sq_linvel < linear_threshold * linear_threshold.abs()
&& sq_angvel < self.angular_threshold * self.angular_threshold.abs()
}
RigidBodyType::KinematicPositionBased | RigidBodyType::KinematicVelocityBased => {
sq_linvel == 0.0 && sq_angvel == 0.0
}
RigidBodyType::Fixed => true,
};
if can_sleep {
self.time_since_can_sleep += dt;
} else {
self.time_since_can_sleep = 0.0;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::Real;
#[test]
fn test_interpolate_velocity() {
#[cfg(feature = "f32")]
let mut rng = oorandom::Rand32::new(0);
#[cfg(feature = "f64")]
let mut rng = oorandom::Rand64::new(0);
for i in -10..=10 {
let mult = i as Real;
let (local_com, curr_pos, next_pos);
#[cfg(feature = "dim2")]
{
local_com = Vector::new(rng.rand_float(), rng.rand_float());
curr_pos = Pose::new(
Vector::new(rng.rand_float(), rng.rand_float()) * mult,
rng.rand_float(),
);
next_pos = Pose::new(
Vector::new(rng.rand_float(), rng.rand_float()) * mult,
rng.rand_float(),
);
}
#[cfg(feature = "dim3")]
{
local_com = Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float());
curr_pos = Pose::new(
Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()) * mult,
Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()),
);
next_pos = Pose::new(
Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()) * mult,
Vector::new(rng.rand_float(), rng.rand_float(), rng.rand_float()),
);
}
let dt = 0.016;
let rb_pos = RigidBodyPosition {
position: curr_pos,
next_position: next_pos,
};
let vel = rb_pos.interpolate_velocity(1.0 / dt, local_com);
let interp_pos = vel.integrate(dt, &curr_pos, &local_com);
approx::assert_relative_eq!(interp_pos, next_pos, epsilon = 1.0e-5);
}
}
}