use crate::prelude::*;
use bevy::{
ecs::{lifecycle::HookContext, world::DeferredWorld},
prelude::*,
};
#[cfg(feature = "3d")]
use bevy_heavy::AngularInertiaTensor;
use derive_more::From;
#[cfg(feature = "3d")]
use glam_matrix_extras::{MatConversionError, SymmetricMat3};
mod collider;
pub use collider::*;
mod computed;
pub use computed::*;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MassError {
Negative,
NaN,
}
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(
feature = "2d",
doc = ".with_child((Collider::circle(1.0), Mass(5.0)));"
)]
#[cfg_attr(
feature = "3d",
doc = ".with_child((Collider::sphere(1.0), Mass(5.0)));"
)]
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(
feature = "2d",
doc = ".with_child((Collider::circle(1.0), Mass(5.0)));"
)]
#[cfg_attr(
feature = "3d",
doc = ".with_child((Collider::sphere(1.0), Mass(5.0)));"
)]
#[derive(Reflect, Clone, Copy, Component, Debug, Default, Deref, DerefMut, PartialEq, From)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component, Default, PartialEq)]
pub struct Mass(pub f32);
impl Mass {
pub const ZERO: Self = Self(0.0);
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(
feature = "2d",
doc = "let mass = Mass::from_shape(&Collider::circle(1.0), 2.0);"
)]
#[cfg_attr(
feature = "3d",
doc = "let mass = Mass::from_shape(&Collider::sphere(1.0), 2.0);"
)]
#[cfg_attr(
feature = "2d",
doc = "let mass = Mass::from_shape(&Circle::new(1.0), 2.0);"
)]
#[cfg_attr(
feature = "3d",
doc = "let mass = Mass::from_shape(&Sphere::new(1.0), 2.0);"
)]
#[inline]
pub fn from_shape<T: ComputeMassProperties>(shape: &T, density: f32) -> Self {
Self(shape.mass(density))
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AngularInertiaError {
Negative,
NaN,
}
#[cfg(feature = "2d")]
#[derive(Reflect, Clone, Copy, Component, Debug, Default, Deref, DerefMut, PartialEq, From)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component, Default, PartialEq)]
#[doc(alias = "MomentOfInertia")]
pub struct AngularInertia(pub f32);
#[cfg(feature = "2d")]
impl AngularInertia {
pub const ZERO: Self = Self(0.0);
#[inline]
pub fn from_shape<T: ComputeMassProperties>(shape: &T, mass: f32) -> Self {
Self(shape.angular_inertia(mass))
}
#[inline]
pub fn shifted(&self, mass: f32, offset: Vec2) -> f32 {
if mass > 0.0 && mass.is_finite() && offset != Vec2::ZERO {
self.0 + offset.length_squared() * mass
} else {
self.0
}
}
}
#[cfg(feature = "3d")]
#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component, PartialEq)]
#[doc(alias = "MomentOfInertia")]
pub struct AngularInertia {
pub principal: Vec3,
pub local_frame: Quat,
}
#[cfg(feature = "3d")]
impl AngularInertia {
pub const ZERO: Self = Self {
principal: Vec3::ZERO,
local_frame: Quat::IDENTITY,
};
#[inline]
#[doc(alias = "from_principal_angular_inertia")]
pub fn new(principal_angular_inertia: Vec3) -> Self {
debug_assert!(
principal_angular_inertia.cmpge(Vec3::ZERO).all()
&& !principal_angular_inertia.is_nan(),
"principal angular inertia must be positive or zero for all axes"
);
Self {
principal: principal_angular_inertia,
local_frame: Quat::IDENTITY,
}
}
#[inline]
pub fn try_new(principal_angular_inertia: Vec3) -> Result<Self, AngularInertiaError> {
if principal_angular_inertia.is_nan() {
Err(AngularInertiaError::NaN)
} else if !principal_angular_inertia.cmpge(Vec3::ZERO).all() {
Err(AngularInertiaError::Negative)
} else {
Ok(Self {
principal: principal_angular_inertia,
local_frame: Quat::IDENTITY,
})
}
}
#[inline]
#[doc(alias = "from_principal_angular_inertia_with_local_frame")]
pub fn new_with_local_frame(principal_angular_inertia: Vec3, local_frame: Quat) -> Self {
debug_assert!(
principal_angular_inertia.cmpge(Vec3::ZERO).all()
&& !principal_angular_inertia.is_nan(),
"principal angular inertia must be positive or zero for all axes"
);
Self {
principal: principal_angular_inertia,
local_frame,
}
}
#[inline]
pub fn try_new_with_local_frame(
principal_angular_inertia: Vec3,
local_frame: Quat,
) -> Result<Self, AngularInertiaError> {
if principal_angular_inertia.is_nan() {
Err(AngularInertiaError::NaN)
} else if !principal_angular_inertia.cmpge(Vec3::ZERO).all() {
Err(AngularInertiaError::Negative)
} else {
Ok(Self {
principal: principal_angular_inertia,
local_frame,
})
}
}
#[inline]
#[doc(alias = "from_mat3")]
pub fn from_tensor(tensor: impl Into<AngularInertiaTensor>) -> Self {
let tensor = tensor.into();
let (principal, local_frame) = tensor.principal_angular_inertia_with_local_frame();
Self {
principal,
local_frame,
}
}
#[inline]
#[must_use]
#[doc(alias = "from_tensor")]
pub fn from_symmetric_mat3(mat: SymmetricMat3) -> Self {
Self::from_tensor(AngularInertiaTensor::from_symmetric_mat3(mat))
}
#[inline]
pub fn try_from_mat3(mat: Mat3) -> Result<Self, MatConversionError> {
SymmetricMat3::try_from_mat3(mat).map(Self::from_tensor)
}
#[inline]
#[must_use]
pub fn from_mat3_unchecked(mat: Mat3) -> Self {
Self::from_tensor(SymmetricMat3::from_mat3_unchecked(mat))
}
#[inline]
pub fn from_shape<T: ComputeMassProperties>(shape: &T, mass: f32) -> Self {
let principal = shape.principal_angular_inertia(mass);
let local_frame = shape.local_inertial_frame();
Self::new_with_local_frame(principal, local_frame)
}
#[inline]
pub fn tensor(self) -> AngularInertiaTensor {
AngularInertiaTensor::new_with_local_frame(self.principal, self.local_frame)
}
#[inline]
pub fn is_finite(self) -> bool {
self.principal.is_finite() && self.local_frame.is_finite()
}
#[inline]
pub fn is_nan(self) -> bool {
self.principal.is_nan() || self.local_frame.is_nan()
}
}
#[cfg(feature = "3d")]
impl TryFrom<Mat3> for AngularInertia {
type Error = MatConversionError;
fn try_from(mat: Mat3) -> Result<Self, Self::Error> {
Self::try_from_mat3(mat)
}
}
#[cfg(feature = "3d")]
impl From<SymmetricMat3> for AngularInertia {
fn from(tensor: SymmetricMat3) -> Self {
Self::from_tensor(tensor)
}
}
#[cfg(feature = "3d")]
impl From<AngularInertiaTensor> for AngularInertia {
fn from(tensor: AngularInertiaTensor) -> Self {
Self::from_tensor(tensor)
}
}
#[cfg(feature = "3d")]
impl From<AngularInertia> for AngularInertiaTensor {
fn from(inertia: AngularInertia) -> Self {
inertia.tensor()
}
}
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(feature = "2d", doc = " CenterOfMass::new(0.0, -0.5),")]
#[cfg_attr(feature = "3d", doc = " CenterOfMass::new(0.0, -0.5, 0.0),")]
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(
feature = "2d",
doc = "// Total center of mass: (10.0 * [0.0, -0.5] + 5.0 * [0.0, 4.0]) / (10.0 + 5.0) = [0.0, 1.0]"
)]
#[cfg_attr(
feature = "3d",
doc = "// Total center of mass: (10.0 * [0.0, -0.5, 0.0] + 5.0 * [0.0, 4.0, 0.0]) / (10.0 + 5.0) = [0.0, 1.0, 0.0]"
)]
#[cfg_attr(feature = "2d", doc = " CenterOfMass::new(0.0, -0.5),")]
#[cfg_attr(feature = "3d", doc = " CenterOfMass::new(0.0, -0.5, 0.0),")]
#[cfg_attr(feature = "2d", doc = " Collider::circle(1.0),")]
#[cfg_attr(feature = "3d", doc = " Collider::sphere(1.0),")]
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(feature = "2d", doc = "// Total center of mass: [0.0, -0.5]")]
#[cfg_attr(feature = "3d", doc = "// Total center of mass: [0.0, -0.5, 0.0]")]
#[cfg_attr(feature = "2d", doc = " CenterOfMass::new(0.0, -0.5),")]
#[cfg_attr(feature = "3d", doc = " CenterOfMass::new(0.0, -0.5, 0.0),")]
#[cfg_attr(feature = "2d", doc = " Collider::circle(1.0),")]
#[cfg_attr(feature = "3d", doc = " Collider::sphere(1.0),")]
#[derive(Reflect, Clone, Copy, Component, Debug, Default, Deref, DerefMut, PartialEq, From)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component, Default, PartialEq)]
pub struct CenterOfMass(pub VectorF32);
impl CenterOfMass {
pub const ZERO: Self = Self(VectorF32::ZERO);
#[inline]
#[cfg(feature = "2d")]
pub const fn new(x: f32, y: f32) -> Self {
Self(Vec2::new(x, y))
}
#[inline]
#[cfg(feature = "3d")]
pub const fn new(x: f32, y: f32, z: f32) -> Self {
Self(Vec3::new(x, y, z))
}
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(
feature = "2d",
doc = "let center_of_mass = CenterOfMass::from_shape(&Collider::circle(1.0));"
)]
#[cfg_attr(
feature = "3d",
doc = "let center_of_mass = CenterOfMass::from_shape(&Collider::sphere(1.0));"
)]
#[cfg_attr(
feature = "2d",
doc = "let center_of_mass = CenterOfMass::from_shape(&Circle::new(1.0));"
)]
#[cfg_attr(
feature = "3d",
doc = "let center_of_mass = CenterOfMass::from_shape(&Sphere::new(1.0));"
)]
#[inline]
pub fn from_shape<T: ComputeMassProperties>(shape: &T) -> Self {
Self(shape.center_of_mass())
}
}
#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component, Default, PartialEq)]
#[require(RecomputeMassProperties)]
#[component(on_remove = on_remove_no_auto_mass_property)]
pub struct NoAutoMass;
#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component, Default, PartialEq)]
#[require(RecomputeMassProperties)]
#[component(on_remove = on_remove_no_auto_mass_property)]
pub struct NoAutoAngularInertia;
#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component, Default, PartialEq)]
#[require(RecomputeMassProperties)]
#[component(on_remove = on_remove_no_auto_mass_property)]
pub struct NoAutoCenterOfMass;
fn on_remove_no_auto_mass_property(mut world: DeferredWorld, ctx: HookContext) {
if let Ok(mut entity_commands) = world.commands().get_entity(ctx.entity) {
entity_commands.try_insert(RecomputeMassProperties);
}
}
#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq)]
#[component(storage = "SparseSet")]
pub struct RecomputeMassProperties;
#[allow(missing_docs)]
#[derive(Bundle, Debug, Default, Clone, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct MassPropertiesBundle {
pub mass: Mass,
pub angular_inertia: AngularInertia,
pub center_of_mass: CenterOfMass,
}
impl MassPropertiesBundle {
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(
feature = "2d",
doc = " MassPropertiesBundle::from_shape(&Collider::circle(0.5), 2.0),"
)]
#[cfg_attr(
feature = "3d",
doc = " MassPropertiesBundle::from_shape(&Collider::sphere(0.5), 2.0),"
)]
#[cfg_attr(
feature = "2d",
doc = " MassPropertiesBundle::from_shape(&Circle::new(0.5), 2.0),"
)]
#[cfg_attr(
feature = "3d",
doc = " MassPropertiesBundle::from_shape(&Sphere::new(0.5), 2.0),"
)]
#[inline]
pub fn from_shape<T: ComputeMassProperties>(shape: &T, density: f32) -> Self {
shape.mass_properties(density).to_bundle()
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "3d")]
use super::*;
#[cfg(feature = "3d")]
use approx::assert_relative_eq;
#[test]
#[cfg(feature = "3d")]
fn angular_inertia_creation() {
use bevy_heavy::AngularInertiaTensor;
let angular_inertia = AngularInertia::new(Vec3::new(10.0, 20.0, 30.0));
assert_eq!(angular_inertia.principal, Vec3::new(10.0, 20.0, 30.0));
assert_eq!(angular_inertia.local_frame, Quat::IDENTITY);
assert_relative_eq!(
angular_inertia.tensor(),
AngularInertiaTensor::new(Vec3::new(10.0, 20.0, 30.0))
);
}
#[test]
#[should_panic]
#[cfg(feature = "3d")]
fn negative_angular_inertia_panics() {
AngularInertia::new(Vec3::new(-1.0, 2.0, 3.0));
}
#[test]
#[cfg(feature = "3d")]
fn negative_angular_inertia_error() {
assert_eq!(
AngularInertia::try_new(Vec3::new(-1.0, 2.0, 3.0)),
Err(AngularInertiaError::Negative),
"negative angular inertia should return an error"
);
}
#[test]
#[cfg(feature = "3d")]
fn nan_angular_inertia_error() {
assert_eq!(
AngularInertia::try_new(Vec3::new(f32::NAN, 2.0, 3.0)),
Err(AngularInertiaError::NaN),
"NaN angular inertia should return an error"
);
}
}