use core::ops::Mul as _;
use math::{
collision::{cube_vs_cube, sphere_vs_cube, sphere_vs_sphere_dynamic},
cross,
cube::Cube,
dot, length, magnitude, magnitude_squared,
mat::{MatInverse as _, MatScale as _, MatTranspose as _},
normalize,
sphere::Sphere,
Base, Magnitude as _, Matrix3, Quaternion, Vector3,
};
use crate::{
core::factory::Handle,
physics::{
body::BodyTypes,
bounds::Bounds,
collider::Shapes,
dynabit::contact::{Contact, Side},
},
time::MediaTime,
};
#[derive(Clone)]
pub struct PhysicsBody {
pub(crate) body_type: BodyTypes,
pub(crate) collision_shape: Shapes,
pub(crate) position: Vector3,
pub(crate) orientation: Quaternion,
pub(crate) acceleration: Vector3,
pub(crate) linear_velocity: Vector3,
pub(crate) angular_velocity: Vector3,
pub(crate) inv_mass: f32,
pub(crate) center_of_mass: Vector3,
pub(crate) elasticity: f32,
pub(crate) friction: f32,
pub(crate) handle: Handle,
}
impl PhysicsBody {
pub fn apply_impulse(&mut self, point: Vector3, impulse: Vector3) {
if self.inv_mass == 0f32 {
return;
}
self.apply_linear_impulse(impulse);
let world_space_center_of_mass = self.world_space_center_of_mass();
let r = point - world_space_center_of_mass;
let dl = cross(r, impulse);
self.apply_angular_impulse(dl);
}
pub fn apply_linear_impulse(&mut self, impulse: Vector3) {
if self.inv_mass == 0f32 {
return;
}
self.linear_velocity += impulse * self.inv_mass;
}
pub fn apply_angular_impulse(&mut self, impulse: Vector3) {
if self.inv_mass == 0f32 {
return;
}
self.angular_velocity += self.inverse_world_space_inertia_tensor() * impulse;
let max_angular_speed = 30f32;
if magnitude_squared(self.angular_velocity) > max_angular_speed * max_angular_speed {
self.angular_velocity = normalize(self.angular_velocity) * max_angular_speed;
}
}
#[inline]
pub fn mass(&self) -> f32 {
1f32 / self.inv_mass
}
pub fn world_space_center_of_mass(&self) -> Vector3 {
self.position + self.orientation.get_matrix() * self.center_of_mass
}
pub fn inverse_body_space_inertia_tensor(&self) -> Matrix3 {
let inertia_tensor = self.collision_shape.inertia_tensor();
inertia_tensor.inverse() * Matrix3::from_scale(Vector3::from(self.inv_mass))
}
pub fn inverse_world_space_inertia_tensor(&self) -> Matrix3 {
let inertia_tensor = self.collision_shape.inertia_tensor();
let inv_mass = self.inv_mass;
let inverse =
inertia_tensor.inverse() * Matrix3::from((inv_mass, 0f32, 0f32, 0f32, inv_mass, 0f32, 0f32, 0f32, inv_mass));
let orientation = self.orientation.get_matrix();
orientation * inverse * orientation.transpose()
}
pub fn update(&mut self, dt: MediaTime) {
let dt = dt.as_seconds_f32();
self.position += self.linear_velocity * dt;
let world_space_center_of_mass = self.world_space_center_of_mass();
let delta = self.position - world_space_center_of_mass;
let orientation = self.orientation.get_matrix();
let inertia_tensor = orientation * self.collision_shape.inertia_tensor() * orientation.transpose();
let alpha = inertia_tensor.inverse() * (cross(self.angular_velocity, inertia_tensor * self.angular_velocity));
self.angular_velocity += alpha * dt;
let angular_step = self.angular_velocity * dt;
let angle = length(angular_step);
let dq = if angle > 0.0 {
Quaternion::from_axis_angle(angular_step / angle, angle)
} else {
Quaternion::identity()
};
self.orientation = Quaternion::normalize(dq * self.orientation);
self.position = world_space_center_of_mass + dq * delta;
}
pub fn bounds(&self) -> Bounds {
self.collision_shape.bounds() + self.position }
}
pub fn intersect((a, i): (&PhysicsBody, usize), (b, j): (&PhysicsBody, usize), dt: f32) -> Option<Contact> {
match (&a.collision_shape, &b.collision_shape) {
(Shapes::Sphere { radius: ra }, Shapes::Sphere { radius: rb }) => {
let intersection = sphere_vs_sphere_dynamic(
&Sphere {
center: a.position,
radius: *ra,
},
&Sphere {
center: b.position,
radius: *rb,
},
a.linear_velocity,
b.linear_velocity,
dt,
);
intersection.map(|e| Contact {
a: Side {
object: i,
point: e.point_on_a,
},
b: Side {
object: j,
point: e.point_on_b,
},
normal: e.normal,
depth: e.depth,
toi: e.toi,
})
}
(Shapes::Cube { size: sa }, Shapes::Cube { size: sb }) => {
cube_vs_cube(&Cube::new(a.position, *sa), &Cube::new(b.position, *sb)).map(|e| Contact {
a: Side {
object: i,
point: e.point_on_a,
},
b: Side {
object: j,
point: e.point_on_b,
},
normal: e.normal,
depth: e.depth,
toi: 0f32, })
}
(Shapes::Sphere { radius: ra }, Shapes::Cube { size: sb }) => {
sphere_vs_cube(&Sphere::new(a.position, *ra), &Cube::new(b.position, *sb)).map(|e| Contact {
a: Side {
object: i,
point: e.point_on_a,
},
b: Side {
object: j,
point: e.point_on_b,
},
normal: e.normal,
depth: e.depth,
toi: 0f32, })
}
(Shapes::Cube { size: sa }, Shapes::Sphere { radius: rb }) => {
sphere_vs_cube(&Sphere::new(b.position, *rb), &Cube::new(a.position, *sa))
.map(|e| e.swap())
.map(|e| Contact {
a: Side {
object: i,
point: e.point_on_a,
},
b: Side {
object: j,
point: e.point_on_b,
},
normal: e.normal,
depth: e.depth,
toi: 0f32, })
}
(Shapes::ConvexHull { .. }, _) | (_, Shapes::ConvexHull { .. }) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_handle() -> Handle {
crate::core::factory::Factory::<()>::new().create(())
}
#[test]
fn update_applies_full_angular_velocity_step_to_orientation() {
let mut body = PhysicsBody {
body_type: BodyTypes::Dynamic,
collision_shape: Shapes::Sphere { radius: 1.0 },
position: Vector3::zero(),
orientation: Quaternion::identity(),
acceleration: Vector3::zero(),
linear_velocity: Vector3::zero(),
angular_velocity: Vector3::new(0.0, 1.0, 0.0),
inv_mass: 1.0,
center_of_mass: Vector3::zero(),
elasticity: 0.0,
friction: 1.0,
handle: test_handle(),
};
body.update(MediaTime::from_seconds(1));
let expected = Quaternion::from_axis_angle(Vector3::new(0.0, 1.0, 0.0), 1.0);
let rotated = body.orientation * Vector3::new(1.0, 0.0, 0.0);
let expected_rotated = expected * Vector3::new(1.0, 0.0, 0.0);
assert!(magnitude(rotated - expected_rotated) < 1e-5);
}
}