use core::marker::PhantomData;
use crate::sim::body::Body;
use crate::sim::math::{Mat3, Quat, Vec3};
#[derive(Debug, Clone, Copy)]
pub(crate) struct SolverBody {
pub(crate) linear_velocity: Vec3,
pub(crate) angular_velocity: Vec3,
pub(crate) position: Vec3,
pub(crate) rotation: Quat,
pub(crate) delta_position: Vec3,
pub(crate) delta_rotation: Quat,
pub(super) start_rotation_conjugate: Quat,
pub(super) start_position: Vec3,
pub(crate) inv_mass: f32,
pub(super) inv_inertia_local: Vec3,
pub(crate) inv_inertia: Mat3,
pub(crate) gravity_scale: f32,
pub(crate) damping: f32,
pub(crate) simulated: bool,
}
impl SolverBody {
pub(crate) const IMMOVABLE: SolverBody = SolverBody {
linear_velocity: Vec3::ZERO,
angular_velocity: Vec3::ZERO,
position: Vec3::ZERO,
rotation: Quat::IDENTITY,
delta_position: Vec3::ZERO,
delta_rotation: Quat::IDENTITY,
start_rotation_conjugate: Quat::IDENTITY,
start_position: Vec3::ZERO,
inv_mass: 0.0,
inv_inertia_local: Vec3::ZERO,
inv_inertia: Mat3::ZERO,
gravity_scale: 0.0,
damping: 0.0,
simulated: false,
};
pub(crate) fn from_body(body: &Body) -> Self {
let simulated = body.is_simulated();
SolverBody {
linear_velocity: body.linear_velocity,
angular_velocity: body.angular_velocity,
position: body.position,
rotation: body.orientation,
delta_position: Vec3::ZERO,
delta_rotation: Quat::IDENTITY,
start_rotation_conjugate: body.orientation.conjugate(),
start_position: body.position,
inv_mass: if simulated { body.inv_mass } else { 0.0 },
inv_inertia_local: if simulated {
body.inv_inertia_local
} else {
Vec3::ZERO
},
inv_inertia: if simulated {
body.inv_inertia_world()
} else {
Mat3::ZERO
},
gravity_scale: body.gravity_scale,
damping: body.damping,
simulated,
}
}
pub(crate) fn velocity_at(&self, r: Vec3) -> Vec3 {
self.linear_velocity + self.angular_velocity.cross(r)
}
pub(crate) fn apply_impulse(&mut self, impulse: Vec3, r: Vec3) {
if !self.simulated {
return;
}
self.linear_velocity += impulse * self.inv_mass;
self.angular_velocity += self.inv_inertia.mul_vec3(r.cross(impulse));
}
pub(crate) fn apply_angular_impulse(&mut self, impulse: Vec3) {
if !self.simulated {
return;
}
self.angular_velocity += self.inv_inertia.mul_vec3(impulse);
}
pub(crate) fn integrate_position(&mut self, h: f32) {
self.position += self.linear_velocity * h;
self.rotation = self.rotation.integrate(self.angular_velocity, h);
self.delta_position = self.position - self.start_position;
self.delta_rotation = self.rotation.mul(self.start_rotation_conjugate);
if self.inv_inertia_local != Vec3::ZERO {
self.inv_inertia = Mat3::diagonal_conjugated(self.rotation, self.inv_inertia_local);
}
}
}
pub(crate) struct Bodies<'a> {
at: *mut SolverBody,
len: usize,
owner: PhantomData<&'a mut [SolverBody]>,
}
unsafe impl Send for Bodies<'_> {}
impl Default for Bodies<'_> {
fn default() -> Self {
Bodies {
at: core::ptr::null_mut(),
len: 0,
owner: PhantomData,
}
}
}
impl<'a> Bodies<'a> {
pub(crate) fn new(bodies: &'a mut [SolverBody]) -> Self {
Bodies {
at: bodies.as_mut_ptr(),
len: bodies.len(),
owner: PhantomData,
}
}
pub(crate) unsafe fn share(&self) -> Bodies<'a> {
Bodies {
at: self.at,
len: self.len,
owner: PhantomData,
}
}
pub(crate) fn get(&self, slot: u32) -> &SolverBody {
assert!(
(slot as usize) < self.len,
"body slot {slot} is out of range"
);
unsafe { &*self.at.add(slot as usize) }
}
pub(crate) fn get_mut(&mut self, slot: u32) -> &mut SolverBody {
assert!(
(slot as usize) < self.len,
"body slot {slot} is out of range"
);
unsafe { &mut *self.at.add(slot as usize) }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sim::math::vec3;
fn movable() -> SolverBody {
SolverBody {
inv_mass: 2.0,
inv_inertia: Mat3::IDENTITY,
simulated: true,
..SolverBody::IMMOVABLE
}
}
#[test]
fn an_immovable_body_declines_every_impulse() {
let mut body = SolverBody::IMMOVABLE;
body.apply_impulse(vec3(5.0, 5.0, 5.0), Vec3::X);
body.apply_angular_impulse(vec3(5.0, 5.0, 5.0));
assert_eq!(body.linear_velocity, Vec3::ZERO);
assert_eq!(body.angular_velocity, Vec3::ZERO);
}
#[test]
fn a_movable_body_takes_it() {
let mut body = movable();
body.apply_impulse(vec3(1.0, 0.0, 0.0), Vec3::ZERO);
assert_eq!(body.linear_velocity, vec3(2.0, 0.0, 0.0));
body.apply_angular_impulse(vec3(0.0, 3.0, 0.0));
assert_eq!(body.angular_velocity, vec3(0.0, 3.0, 0.0));
}
#[test]
fn two_handles_write_disjoint_slots() {
let mut storage = [movable(), movable(), movable()];
let mut first = Bodies::new(&mut storage);
let mut second = unsafe { first.share() };
first.get_mut(0).linear_velocity = Vec3::X;
second.get_mut(2).linear_velocity = Vec3::Y;
assert_eq!(first.get(0).linear_velocity, Vec3::X);
assert_eq!(first.get(1).linear_velocity, Vec3::ZERO);
assert_eq!(second.get(2).linear_velocity, Vec3::Y);
}
#[test]
#[should_panic(expected = "out of range")]
fn a_slot_past_the_end_is_refused() {
let mut storage = [movable()];
let bodies = Bodies::new(&mut storage);
bodies.get(4);
}
#[test]
fn an_empty_handle_holds_nothing() {
let bodies = Bodies::default();
assert_eq!(bodies.len, 0);
}
}