use nalgebra::SVector;
use symtropy_math::{Bivector, Point, Shape, Sphere, Transform};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BodyHandle(pub usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NetId(pub u64);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BodyType {
Dynamic,
Static,
Kinematic,
}
pub struct RigidBody<const D: usize> {
pub handle: BodyHandle,
pub net_id: Option<NetId>,
pub body_type: BodyType,
pub transform: Transform<D>,
pub linear_velocity: SVector<f64, D>,
pub angular_velocity: Bivector<D>,
pub mass: f64,
pub inv_mass: f64,
pub inertia: SVector<f64, D>,
pub inv_inertia: SVector<f64, D>,
pub restitution: f64,
pub friction: f64,
pub collider: Box<dyn Shape<D>>,
pub force_accumulator: SVector<f64, D>,
pub torque_accumulator: Bivector<D>,
pub linear_damping: f64,
pub angular_damping: f64,
pub sleeping: bool,
pub sleep_counter: u32,
pub collision_group: u32,
pub collision_mask: u32,
pub is_sensor: bool,
}
impl<const D: usize> RigidBody<D> {
pub fn dynamic_sphere(handle: BodyHandle, position: Point<D>, radius: f64, mass: f64) -> Self {
Self {
handle,
net_id: None,
body_type: BodyType::Dynamic,
transform: Transform::from_translation(position),
linear_velocity: SVector::zeros(),
angular_velocity: Bivector::zero(),
mass,
inv_mass: 1.0 / mass,
inertia: SVector::from_element(0.4 * mass * radius * radius), inv_inertia: SVector::from_element(1.0 / (0.4 * mass * radius * radius)),
restitution: 0.5,
friction: 0.3,
collider: Box::new(Sphere::new(Point::origin(), radius)),
force_accumulator: SVector::zeros(),
torque_accumulator: Bivector::zero(),
linear_damping: 0.01,
angular_damping: 0.05,
sleeping: false,
sleep_counter: 0,
collision_group: 0xFFFF_FFFF,
collision_mask: 0xFFFF_FFFF,
is_sensor: false,
}
}
pub fn static_body(handle: BodyHandle, position: Point<D>, collider: Box<dyn Shape<D>>) -> Self {
Self {
handle,
net_id: None,
body_type: BodyType::Static,
transform: Transform::from_translation(position),
linear_velocity: SVector::zeros(),
angular_velocity: Bivector::zero(),
mass: f64::INFINITY,
inv_mass: 0.0,
inertia: SVector::from_element(f64::INFINITY),
inv_inertia: SVector::zeros(),
restitution: 0.5,
friction: 0.5,
collider,
force_accumulator: SVector::zeros(),
torque_accumulator: Bivector::zero(),
linear_damping: 0.0,
angular_damping: 0.0,
sleeping: false,
sleep_counter: 0,
collision_group: 0xFFFF_FFFF,
collision_mask: 0xFFFF_FFFF,
is_sensor: false,
}
}
pub fn dynamic(
handle: BodyHandle,
position: Point<D>,
mass: f64,
inertia: f64,
collider: Box<dyn Shape<D>>,
) -> Self {
Self {
handle,
net_id: None,
body_type: BodyType::Dynamic,
transform: Transform::from_translation(position),
linear_velocity: SVector::zeros(),
angular_velocity: Bivector::zero(),
mass,
inv_mass: 1.0 / mass,
inertia: SVector::from_element(inertia),
inv_inertia: SVector::from_element(1.0 / inertia),
restitution: 0.5,
friction: 0.3,
collider,
force_accumulator: SVector::zeros(),
torque_accumulator: Bivector::zero(),
linear_damping: 0.01,
angular_damping: 0.05,
sleeping: false,
sleep_counter: 0,
collision_group: 0xFFFF_FFFF,
collision_mask: 0xFFFF_FFFF,
is_sensor: false,
}
}
#[inline]
pub fn apply_force(&mut self, force: SVector<f64, D>) {
self.force_accumulator += force;
}
#[inline]
pub fn apply_torque(&mut self, torque: Bivector<D>) {
self.torque_accumulator = self.torque_accumulator.add(&torque);
}
#[inline]
pub fn clear_accumulators(&mut self) {
self.force_accumulator = SVector::zeros();
self.torque_accumulator = Bivector::zero();
}
#[inline]
pub fn position(&self) -> &Point<D> {
&self.transform.translation
}
#[inline]
pub fn is_dynamic(&self) -> bool {
self.body_type == BodyType::Dynamic
}
#[inline]
pub fn wake(&mut self) {
self.sleeping = false;
self.sleep_counter = 0;
}
pub fn try_sleep(&mut self, threshold: f64, ticks_required: u32) -> bool {
if !self.is_dynamic() || self.sleeping {
return false;
}
let speed = self.linear_velocity.norm() + self.angular_velocity.norm();
if speed < threshold {
self.sleep_counter += 1;
if self.sleep_counter >= ticks_required {
self.sleeping = true;
self.linear_velocity = SVector::zeros();
self.angular_velocity = Bivector::zero();
return true;
}
} else {
self.sleep_counter = 0;
}
false
}
pub fn kinetic_energy(&self) -> f64 {
let linear = 0.5 * self.mass * self.linear_velocity.norm_squared();
let i_avg = self.inertia.sum() / D as f64;
let angular = 0.5 * i_avg * self.angular_velocity.norm_squared();
linear + angular
}
pub fn world_support(&self, direction: &SVector<f64, D>) -> SVector<f64, D> {
let local_dir = self.transform.rotation.reverse().rotate_vector(direction);
let local_support = self.collider.support(&local_dir);
self.transform.transform_point(&Point(local_support)).0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dynamic_sphere_creation() {
let body = RigidBody::<3>::dynamic_sphere(
BodyHandle(0),
Point::new([1.0, 2.0, 3.0]),
1.0,
10.0,
);
assert!(body.is_dynamic());
assert!((body.mass - 10.0).abs() < 1e-12);
assert!((body.inv_mass - 0.1).abs() < 1e-12);
}
#[test]
fn static_body_infinite_mass() {
let body = RigidBody::<3>::static_body(
BodyHandle(0),
Point::origin(),
Box::new(Sphere::<3>::unit()),
);
assert!(!body.is_dynamic());
assert_eq!(body.inv_mass, 0.0);
}
#[test]
fn force_accumulation() {
let mut body = RigidBody::<3>::dynamic_sphere(
BodyHandle(0),
Point::origin(),
1.0,
1.0,
);
body.apply_force(SVector::from([1.0, 0.0, 0.0]));
body.apply_force(SVector::from([0.0, 2.0, 0.0]));
assert!((body.force_accumulator[0] - 1.0).abs() < 1e-12);
assert!((body.force_accumulator[1] - 2.0).abs() < 1e-12);
body.clear_accumulators();
assert!(body.force_accumulator.norm() < 1e-12);
}
#[test]
fn kinetic_energy_at_rest_is_zero() {
let body = RigidBody::<4>::dynamic_sphere(
BodyHandle(0),
Point::origin(),
1.0,
1.0,
);
assert!(body.kinetic_energy() < 1e-12);
}
#[test]
fn world_support_translated() {
let body = RigidBody::<3>::dynamic_sphere(
BodyHandle(0),
Point::new([10.0, 0.0, 0.0]),
1.0,
1.0,
);
let dir = SVector::from([1.0, 0.0, 0.0]);
let sp = body.world_support(&dir);
assert!((sp[0] - 11.0).abs() < 1e-10);
}
}