use alloc::vec::Vec;
use bevy_math::{DVec2, Isometry2d, Vec2};
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use crate::RecipOrZero;
mod impls;
pub trait ComputeMassProperties2d {
fn mass(&self, density: f32) -> f32;
#[doc(alias = "unit_moment_of_inertia")]
fn unit_angular_inertia(&self) -> f32;
#[inline]
#[doc(alias = "moment_of_inertia")]
fn angular_inertia(&self, mass: f32) -> f32 {
mass * self.unit_angular_inertia()
}
fn center_of_mass(&self) -> Vec2;
#[inline]
fn mass_properties(&self, density: f32) -> MassProperties2d {
let mass = self.mass(density);
MassProperties2d::new(mass, self.angular_inertia(mass), self.center_of_mass())
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug, PartialEq))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "bevy_reflect", feature = "serialize"),
reflect(Serialize, Deserialize)
)]
pub struct MassProperties2d {
pub mass: f32,
pub angular_inertia: f32,
pub center_of_mass: Vec2,
}
impl Default for MassProperties2d {
fn default() -> Self {
Self::ZERO
}
}
impl MassProperties2d {
pub const ZERO: Self = Self {
mass: 0.0,
angular_inertia: 0.0,
center_of_mass: Vec2::ZERO,
};
#[inline]
pub fn new(mass: f32, angular_inertia: f32, center_of_mass: Vec2) -> Self {
Self {
mass,
angular_inertia,
center_of_mass,
}
}
#[inline]
pub fn from_point_cloud(points: &[Vec2], mass: f32) -> Self {
let points_recip = 1.0 / points.len() as f64;
let center_of_mass =
(points.iter().fold(DVec2::ZERO, |acc, p| acc + p.as_dvec2()) * points_recip).as_vec2();
let unit_angular_inertia = points.iter().fold(0.0, |acc, p| {
let r = p.distance_squared(center_of_mass) as f64;
acc + r * points_recip
}) as f32;
Self::new(mass, mass * unit_angular_inertia, center_of_mass)
}
#[inline]
pub fn global_center_of_mass(&self, isometry: impl Into<Isometry2d>) -> Vec2 {
let isometry: Isometry2d = isometry.into();
isometry.transform_point(self.center_of_mass)
}
#[inline]
pub fn unit_angular_inertia(&self) -> f32 {
self.mass.recip_or_zero() * self.angular_inertia
}
#[inline]
pub fn shifted_angular_inertia(&self, offset: Vec2) -> f32 {
self.angular_inertia + offset.length_squared() * self.mass
}
#[inline]
pub fn transformed_by(mut self, isometry: impl Into<Isometry2d>) -> Self {
self.transform_by(isometry);
self
}
#[inline]
pub fn transform_by(&mut self, isometry: impl Into<Isometry2d>) {
self.center_of_mass = self.global_center_of_mass(isometry);
}
#[inline]
pub fn inverse(&self) -> Self {
Self {
mass: self.mass.recip_or_zero(),
angular_inertia: self.angular_inertia.recip_or_zero(),
center_of_mass: self.center_of_mass,
}
}
#[inline]
pub fn set_mass(&mut self, new_mass: f32, update_angular_inertia: bool) {
if update_angular_inertia {
self.angular_inertia *= new_mass * self.mass.recip_or_zero();
}
self.mass = new_mass;
}
}
impl core::ops::Add for MassProperties2d {
type Output = Self;
#[inline]
fn add(self, other: Self) -> Self::Output {
if self == Self::ZERO {
return other;
} else if other == Self::ZERO {
return self;
}
let mass1 = self.mass;
let mass2 = other.mass;
let new_mass = mass1 + mass2;
let new_center_of_mass =
(self.center_of_mass * mass1 + other.center_of_mass * mass2) * new_mass.recip_or_zero();
let i1 = self.shifted_angular_inertia(new_center_of_mass - self.center_of_mass);
let i2 = other.shifted_angular_inertia(new_center_of_mass - other.center_of_mass);
let new_angular_inertia = i1 + i2;
Self {
mass: new_mass,
angular_inertia: new_angular_inertia,
center_of_mass: new_center_of_mass,
}
}
}
impl core::ops::AddAssign for MassProperties2d {
#[inline]
fn add_assign(&mut self, other: Self) {
*self = *self + other;
}
}
impl core::ops::Sub for MassProperties2d {
type Output = Self;
#[inline]
fn sub(self, other: Self) -> Self::Output {
if self == Self::ZERO || other == Self::ZERO {
return self;
}
let mass1 = self.mass;
let mass2 = other.mass;
if mass1 <= mass2 {
return Self {
center_of_mass: self.center_of_mass,
..Self::ZERO
};
}
let new_mass = mass1 - mass2;
let new_center_of_mass =
(self.center_of_mass * mass1 - other.center_of_mass * mass2) * new_mass.recip_or_zero();
let i1 = self.shifted_angular_inertia(new_center_of_mass - self.center_of_mass);
let i2 = other.shifted_angular_inertia(new_center_of_mass - other.center_of_mass);
let new_angular_inertia = (i1 - i2).max(0.0);
Self {
mass: new_mass,
angular_inertia: new_angular_inertia,
center_of_mass: new_center_of_mass,
}
}
}
impl core::ops::SubAssign for MassProperties2d {
#[inline]
fn sub_assign(&mut self, other: Self) {
*self = *self - other;
}
}
impl core::iter::Sum for MassProperties2d {
#[inline]
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
let mut total_mass = 0.0;
let mut total_angular_inertia = 0.0;
let mut total_center_of_mass = Vec2::ZERO;
let mut all_properties = Vec::with_capacity(iter.size_hint().1.unwrap_or_default());
for props in iter {
total_mass += props.mass;
total_center_of_mass += props.center_of_mass * props.mass;
all_properties.push(props);
}
if total_mass > 0.0 {
total_center_of_mass /= total_mass;
}
for props in all_properties {
total_angular_inertia +=
props.shifted_angular_inertia(total_center_of_mass - props.center_of_mass);
}
Self::new(total_mass, total_angular_inertia, total_center_of_mass)
}
}
#[cfg(any(feature = "approx", test))]
impl approx::AbsDiffEq for MassProperties2d {
type Epsilon = f32;
fn default_epsilon() -> f32 {
f32::EPSILON
}
fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool {
self.mass.abs_diff_eq(&other.mass, epsilon)
&& self
.angular_inertia
.abs_diff_eq(&other.angular_inertia, epsilon)
&& self
.center_of_mass
.abs_diff_eq(other.center_of_mass, epsilon)
}
}
#[cfg(any(feature = "approx", test))]
impl approx::RelativeEq for MassProperties2d {
fn default_max_relative() -> f32 {
f32::EPSILON
}
fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool {
self.mass.relative_eq(&other.mass, epsilon, max_relative)
&& self
.angular_inertia
.relative_eq(&other.angular_inertia, epsilon, max_relative)
&& self
.center_of_mass
.relative_eq(&other.center_of_mass, epsilon, max_relative)
}
}
#[cfg(any(feature = "approx", test))]
impl approx::UlpsEq for MassProperties2d {
fn default_max_ulps() -> u32 {
4
}
fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool {
self.mass.ulps_eq(&other.mass, epsilon, max_ulps)
&& self
.angular_inertia
.ulps_eq(&other.angular_inertia, epsilon, max_ulps)
&& self
.center_of_mass
.ulps_eq(&other.center_of_mass, epsilon, max_ulps)
}
}