use super::*;
impl Default for BodyCollider {
fn default() -> BodyCollider {
BodyCollider::Aabb(AabbCollider::default())
}
}
impl Default for BodyCollider3D {
fn default() -> BodyCollider3D {
BodyCollider3D::Aabb(AabbCollider3D::default())
}
}
impl Default for PhysicsConfig {
fn default() -> PhysicsConfig {
PhysicsConfig::new(
Vector2D::new(0.0, DEFAULT_GRAVITY),
DEFAULT_LINEAR_DAMPING,
DEFAULT_ANGULAR_DAMPING,
)
}
}
impl RigidBody2D {
pub fn new_dynamic(id: u64, position: Vector2D) -> RigidBody2D {
let mass: f64 = PHYSICS_DEFAULT_MASS;
RigidBody2D::new(
id,
position,
mass,
1.0 / mass,
DEFAULT_RESTITUTION,
DEFAULT_FRICTION,
BodyType::Dynamic,
)
}
pub fn new_static(id: u64, position: Vector2D) -> RigidBody2D {
RigidBody2D::new(
id,
position,
PHYSICS_STATIC_MASS,
0.0,
DEFAULT_RESTITUTION,
DEFAULT_FRICTION,
BodyType::Static,
)
}
pub fn apply_force(&mut self, force: Vector2D) {
*self.get_mut_force_accumulator() += force;
}
pub fn apply_impulse(&mut self, impulse: Vector2D) {
let inverse_mass: f64 = self.get_inverse_mass();
if inverse_mass == 0.0 {
return;
}
*self.get_mut_velocity() += impulse.scaled(inverse_mass);
}
pub fn update_mass(&mut self, mass: f64) {
self.set_mass(mass);
self.set_inverse_mass(if mass > 0.0 { 1.0 / mass } else { 0.0 });
}
pub fn is_dynamic(&self) -> bool {
self.get_body_type() == BodyType::Dynamic
}
pub fn update_collider(&mut self, collider: BodyCollider) {
self.set_collider(Some(collider));
}
pub fn bounding_box(&self) -> Option<Rect> {
let collider: Option<BodyCollider> = self.get_collider();
match collider? {
BodyCollider::Aabb(aabb) => {
let aabb_rect: Rect = aabb.get_rect();
let mut offset_rect: Rect = aabb_rect;
offset_rect.set_x(
offset_rect.get_x() + self.get_position().get_x() - aabb_rect.get_width() * 0.5,
);
offset_rect.set_y(
offset_rect.get_y() + self.get_position().get_y()
- aabb_rect.get_height() * 0.5,
);
Some(offset_rect)
}
BodyCollider::Circle(circle) => {
let diameter: f64 = circle.get_circle().get_radius() * 2.0;
Some(Rect::from_center(self.get_position(), diameter, diameter))
}
}
}
}
impl PhysicsWorld2D {
pub fn with_config(config: PhysicsConfig) -> PhysicsWorld2D {
let mut world: PhysicsWorld2D = PhysicsWorld2D::new(config);
world.set_grid(SpatialHashGrid2D::with_default_size());
world
}
pub fn add_body(&mut self, body: RigidBody2D) {
self.get_mut_bodies().push(body);
}
pub fn remove_body(&mut self, id: u64) {
self.get_mut_bodies()
.retain(|body: &RigidBody2D| body.get_id() != id);
}
pub fn get_body(&self, id: u64) -> Option<&RigidBody2D> {
self.get_bodies()
.iter()
.find(|body: &&RigidBody2D| body.get_id() == id)
}
pub fn get_body_mut(&mut self, id: u64) -> Option<&mut RigidBody2D> {
self.get_mut_bodies()
.iter_mut()
.find(|body: &&mut RigidBody2D| body.get_id() == id)
}
}
impl Default for PhysicsWorld2D {
fn default() -> PhysicsWorld2D {
PhysicsWorld2D::with_config(PhysicsConfig::default())
}
}
impl RigidBody2D {
fn check_collision_with(&self, other: &RigidBody2D) -> Option<CollisionResult> {
let a_bbox: Rect = self.bounding_box()?;
let b_bbox: Rect = other.bounding_box()?;
if !Rect::broad_phase_alias(a_bbox, b_bbox) {
return None;
}
let self_collider: Option<BodyCollider> = self.get_collider();
let other_collider: Option<BodyCollider> = other.get_collider();
let position_delta: Vector2D = other.get_position() - self.get_position();
match (self_collider, other_collider) {
(Some(BodyCollider::Aabb(aabb_a)), Some(BodyCollider::Aabb(aabb_b))) => {
let aabb_b_rect: Rect = aabb_b.get_rect();
let offset_aabb_b: AabbCollider = AabbCollider::new(Rect::new(
aabb_b_rect.get_x() + position_delta.get_x(),
aabb_b_rect.get_y() + position_delta.get_y(),
aabb_b_rect.get_width(),
aabb_b_rect.get_height(),
));
aabb_a.collide_with_aabb(&offset_aabb_b)
}
(Some(BodyCollider::Circle(circle_a)), Some(BodyCollider::Circle(circle_b))) => {
let circle_b_inner: Circle = circle_b.get_circle();
let offset_circle_b: CircleCollider = CircleCollider::new(Circle::new(
circle_b_inner.get_center() + position_delta,
circle_b_inner.get_radius(),
));
circle_a.collide_with_circle(&offset_circle_b)
}
(Some(BodyCollider::Aabb(aabb)), Some(BodyCollider::Circle(circle))) => {
let circle_inner: Circle = circle.get_circle();
let offset_circle: CircleCollider = CircleCollider::new(Circle::new(
circle_inner.get_center() + position_delta,
circle_inner.get_radius(),
));
aabb.collide_with_circle(&offset_circle)
}
(Some(BodyCollider::Circle(circle)), Some(BodyCollider::Aabb(aabb))) => {
let aabb_rect: Rect = aabb.get_rect();
let offset_aabb: AabbCollider = AabbCollider::new(Rect::new(
aabb_rect.get_x() + position_delta.get_x(),
aabb_rect.get_y() + position_delta.get_y(),
aabb_rect.get_width(),
aabb_rect.get_height(),
));
offset_aabb
.collide_with_circle(&circle)
.map(|mut result: CollisionResult| {
result.set_normal(-result.get_normal());
result
})
}
_ => None,
}
}
fn resolve_collision_with(&mut self, other: &mut RigidBody2D, result: &CollisionResult) {
let self_inverse_mass: f64 = self.get_inverse_mass();
let other_inverse_mass: f64 = other.get_inverse_mass();
let relative_velocity: Vector2D = other.get_velocity() - self.get_velocity();
let velocity_along_normal: f64 = relative_velocity.dot(result.get_normal());
if velocity_along_normal > 0.0 {
return;
}
let restitution: f64 = self.get_restitution().min(other.get_restitution());
let inverse_mass_sum: f64 = self_inverse_mass + other_inverse_mass;
if inverse_mass_sum == 0.0 {
return;
}
let impulse_magnitude: f64 =
-(1.0 + restitution) * velocity_along_normal / inverse_mass_sum;
let impulse: Vector2D = result.get_normal().scaled(impulse_magnitude);
*self.get_mut_velocity() -= impulse.scaled(self_inverse_mass);
*other.get_mut_velocity() += impulse.scaled(other_inverse_mass);
let correction: Vector2D = result
.get_normal()
.scaled((result.get_depth() * PHYSICS_POSITION_PERCENT / inverse_mass_sum).max(0.0));
*self.get_mut_position() -= correction.scaled(self_inverse_mass);
*other.get_mut_position() += correction.scaled(other_inverse_mass);
}
}
impl PhysicsWorld2D {
pub fn step(&mut self, delta_time: f64) {
let config: PhysicsConfig = self.get_config();
let damping_factor: f64 = (1.0 - config.get_linear_damping() * delta_time).max(0.0);
let angular_damping: f64 = (1.0 - config.get_angular_damping() * delta_time).max(0.0);
let gravity: Vector2D = config.get_gravity();
for body in self.get_mut_bodies() {
if !body.is_dynamic() {
continue;
}
let body_mass: f64 = body.get_mass();
let body_inverse_mass: f64 = body.get_inverse_mass();
*body.get_mut_force_accumulator() += gravity.scaled(body_mass);
let force: Vector2D = body.get_force_accumulator();
*body.get_mut_velocity() += force.scaled(body_inverse_mass * delta_time);
*body.get_mut_velocity() *= damping_factor;
let current_velocity: Vector2D = body.get_velocity();
*body.get_mut_position() += current_velocity.scaled(delta_time);
body.set_force_accumulator(Vector2D::zero());
*body.get_mut_angular_velocity() *= angular_damping;
let current_angular_velocity: f64 = body.get_angular_velocity();
*body.get_mut_rotation() += current_angular_velocity * delta_time;
}
self.resolve_collisions();
}
fn resolve_collisions(&mut self) {
let body_count: usize = self.get_bodies().len();
if body_count < 2 {
return;
}
let mut pairs: Vec<(usize, usize)> = Vec::new();
{
let (bodies, grid, query_buffer, query_seen) = (
&self.bodies,
&mut self.grid,
&mut self.query_buffer,
&mut self.query_seen,
);
grid.clear();
for (index, body) in bodies.iter().enumerate() {
if let Some(bbox) = body.bounding_box() {
grid.insert(index, bbox.min(), bbox.max());
}
}
for (i, body) in bodies.iter().enumerate() {
let Some(bbox) = body.bounding_box() else {
continue;
};
grid.query_into(bbox.min(), bbox.max(), query_buffer, query_seen);
for &j in query_buffer.iter() {
if j > i {
pairs.push((i, j));
}
}
}
}
for iteration in 0..PHYSICS_MAX_ITERATIONS {
let mut any_collision: bool = false;
for &(i, j) in pairs.iter() {
let (left, right) = self.get_mut_bodies().split_at_mut(j);
let body_a: &mut RigidBody2D = &mut left[i];
let body_b: &mut RigidBody2D = &mut right[0];
if body_a.get_inverse_mass() == 0.0 && body_b.get_inverse_mass() == 0.0 {
continue;
}
if let Some(result) = body_a.check_collision_with(body_b) {
body_a.resolve_collision_with(body_b, &result);
any_collision = true;
}
}
if !any_collision {
break;
}
let _: u32 = iteration;
}
}
}
impl Updatable for PhysicsWorld2D {
fn update(&mut self, delta_time: f64) {
PhysicsWorld2D::step(self, delta_time);
}
}
impl Default for PhysicsConfig3D {
fn default() -> PhysicsConfig3D {
PhysicsConfig3D::new(
Vector3D::new(0.0, DEFAULT_GRAVITY_3D, 0.0),
DEFAULT_LINEAR_DAMPING,
DEFAULT_ANGULAR_DAMPING,
)
}
}
impl RigidBody3D {
pub fn new_dynamic(id: u64, position: Vector3D) -> RigidBody3D {
let mass: f64 = PHYSICS_DEFAULT_MASS;
RigidBody3D::new(
id,
position,
mass,
1.0 / mass,
DEFAULT_RESTITUTION,
DEFAULT_FRICTION,
BodyType::Dynamic,
)
}
pub fn new_static(id: u64, position: Vector3D) -> RigidBody3D {
RigidBody3D::new(
id,
position,
PHYSICS_STATIC_MASS,
0.0,
DEFAULT_RESTITUTION,
DEFAULT_FRICTION,
BodyType::Static,
)
}
pub fn apply_force(&mut self, force: Vector3D) {
*self.get_mut_force_accumulator() += force;
}
pub fn apply_torque(&mut self, torque: Vector3D) {
*self.get_mut_torque_accumulator() += torque;
}
pub fn apply_impulse(&mut self, impulse: Vector3D) {
let inverse_mass: f64 = self.get_inverse_mass();
if inverse_mass == 0.0 {
return;
}
*self.get_mut_velocity() += impulse.scaled(inverse_mass);
}
pub fn update_mass(&mut self, mass: f64) {
self.set_mass(mass);
self.set_inverse_mass(if mass > 0.0 { 1.0 / mass } else { 0.0 });
}
pub fn is_dynamic(&self) -> bool {
self.get_body_type() == BodyType::Dynamic
}
pub fn update_collider(&mut self, collider: BodyCollider3D) {
self.set_collider(Some(collider));
}
pub fn bounding_box(&self) -> Option<AABB3D> {
let collider: Option<BodyCollider3D> = self.get_collider();
let position: Vector3D = self.get_position();
match collider? {
BodyCollider3D::Aabb(aabb) => {
let center: Vector3D = aabb.get_aabb().center();
let size: Vector3D = aabb.get_aabb().size();
Some(AABB3D::from_center(
position + center,
size.get_x(),
size.get_y(),
size.get_z(),
))
}
BodyCollider3D::Sphere(sphere) => {
let sphere_inner: Sphere = sphere.get_sphere();
let diameter: f64 = sphere_inner.get_radius() * 2.0;
Some(AABB3D::from_center(
position + sphere_inner.get_center(),
diameter,
diameter,
diameter,
))
}
}
}
}
impl PhysicsWorld3D {
pub fn with_config(config: PhysicsConfig3D) -> PhysicsWorld3D {
let mut world: PhysicsWorld3D = PhysicsWorld3D::new(config);
world.set_grid(SpatialHashGrid3D::with_default_size());
world
}
pub fn add_body(&mut self, body: RigidBody3D) {
self.get_mut_bodies().push(body);
}
pub fn remove_body(&mut self, id: u64) {
self.get_mut_bodies()
.retain(|body: &RigidBody3D| body.get_id() != id);
}
pub fn get_body(&self, id: u64) -> Option<&RigidBody3D> {
self.get_bodies()
.iter()
.find(|body: &&RigidBody3D| body.get_id() == id)
}
pub fn get_body_mut(&mut self, id: u64) -> Option<&mut RigidBody3D> {
self.get_mut_bodies()
.iter_mut()
.find(|body: &&mut RigidBody3D| body.get_id() == id)
}
pub fn step(&mut self, delta_time: f64) {
let config: PhysicsConfig3D = self.get_config();
let damping_factor: f64 = (1.0 - config.get_linear_damping() * delta_time).max(0.0);
let angular_damping: f64 = (1.0 - config.get_angular_damping() * delta_time).max(0.0);
let gravity: Vector3D = config.get_gravity();
for body in self.get_mut_bodies() {
if !body.is_dynamic() {
continue;
}
let body_mass: f64 = body.get_mass();
let body_inverse_mass: f64 = body.get_inverse_mass();
*body.get_mut_force_accumulator() += gravity.scaled(body_mass);
let force: Vector3D = body.get_force_accumulator();
*body.get_mut_velocity() += force.scaled(body_inverse_mass * delta_time);
*body.get_mut_velocity() *= damping_factor;
let current_velocity: Vector3D = body.get_velocity();
*body.get_mut_position() += current_velocity.scaled(delta_time);
body.set_force_accumulator(Vector3D::zero());
*body.get_mut_angular_velocity() *= angular_damping;
let angular_velocity: Vector3D = body.get_angular_velocity();
let rotation_delta: Quaternion = Quaternion::new(
angular_velocity.get_x() * delta_time * 0.5,
angular_velocity.get_y() * delta_time * 0.5,
angular_velocity.get_z() * delta_time * 0.5,
1.0,
);
body.set_rotation((rotation_delta * body.get_rotation()).normalized());
body.set_torque_accumulator(Vector3D::zero());
}
self.resolve_collisions();
}
fn resolve_collisions(&mut self) {
let body_count: usize = self.get_bodies().len();
if body_count < 2 {
return;
}
let mut pairs: Vec<(usize, usize)> = Vec::new();
{
let (bodies, grid, query_buffer, query_seen) = (
&self.bodies,
&mut self.grid,
&mut self.query_buffer,
&mut self.query_seen,
);
grid.clear();
for (index, body) in bodies.iter().enumerate() {
if let Some(bbox) = body.bounding_box() {
grid.insert(index, bbox.get_min(), bbox.get_max());
}
}
for (i, body) in bodies.iter().enumerate() {
let Some(bbox) = body.bounding_box() else {
continue;
};
grid.query_into(bbox.get_min(), bbox.get_max(), query_buffer, query_seen);
for &j in query_buffer.iter() {
if j > i {
pairs.push((i, j));
}
}
}
}
for iteration in 0..PHYSICS_MAX_ITERATIONS {
let mut any_collision: bool = false;
for &(i, j) in pairs.iter() {
let (left, right) = self.get_mut_bodies().split_at_mut(j);
let body_a: &mut RigidBody3D = &mut left[i];
let body_b: &mut RigidBody3D = &mut right[0];
if body_a.get_inverse_mass() == 0.0 && body_b.get_inverse_mass() == 0.0 {
continue;
}
if let Some(result) = Self::check_collision_3d(body_a, body_b) {
Self::resolve_collision_3d(body_a, body_b, &result);
any_collision = true;
}
}
if !any_collision {
break;
}
let _: u32 = iteration;
}
}
fn check_collision_3d(a: &RigidBody3D, b: &RigidBody3D) -> Option<CollisionResult3D> {
let a_bbox: AABB3D = a.bounding_box()?;
let b_bbox: AABB3D = b.bounding_box()?;
if !AABB3D::broad_phase(a_bbox, b_bbox) {
return None;
}
let a_collider: Option<BodyCollider3D> = a.get_collider();
let b_collider: Option<BodyCollider3D> = b.get_collider();
let position_delta: Vector3D = b.get_position() - a.get_position();
match (a_collider, b_collider) {
(Some(BodyCollider3D::Aabb(aabb_a)), Some(BodyCollider3D::Aabb(aabb_b))) => {
let aabb_b_inner: AABB3D = aabb_b.get_aabb();
let offset_aabb: AabbCollider3D = AabbCollider3D::new(AABB3D::new(
aabb_b_inner.get_min() + position_delta,
aabb_b_inner.get_max() + position_delta,
));
aabb_a.collide_with_aabb(&offset_aabb)
}
(Some(BodyCollider3D::Sphere(sphere_a)), Some(BodyCollider3D::Sphere(sphere_b))) => {
let sphere_b_inner: Sphere = sphere_b.get_sphere();
let offset_sphere: SphereCollider3D = SphereCollider3D::new(Sphere::new(
sphere_b_inner.get_center() + position_delta,
sphere_b_inner.get_radius(),
));
sphere_a.collide_with_sphere(&offset_sphere)
}
(Some(BodyCollider3D::Aabb(aabb)), Some(BodyCollider3D::Sphere(sphere))) => {
let sphere_inner: Sphere = sphere.get_sphere();
let offset_sphere: SphereCollider3D = SphereCollider3D::new(Sphere::new(
sphere_inner.get_center() + position_delta,
sphere_inner.get_radius(),
));
aabb.collide_with_sphere(&offset_sphere)
}
(Some(BodyCollider3D::Sphere(sphere)), Some(BodyCollider3D::Aabb(aabb))) => {
let aabb_inner: AABB3D = aabb.get_aabb();
let offset_aabb: AabbCollider3D = AabbCollider3D::new(AABB3D::new(
aabb_inner.get_min() + position_delta,
aabb_inner.get_max() + position_delta,
));
offset_aabb
.collide_with_sphere(&sphere)
.map(|mut result: CollisionResult3D| {
result.set_normal(-result.get_normal());
result
})
}
_ => None,
}
}
fn resolve_collision_3d(a: &mut RigidBody3D, b: &mut RigidBody3D, result: &CollisionResult3D) {
let a_inverse_mass: f64 = a.get_inverse_mass();
let b_inverse_mass: f64 = b.get_inverse_mass();
let relative_velocity: Vector3D = b.get_velocity() - a.get_velocity();
let velocity_along_normal: f64 = relative_velocity.dot(result.get_normal());
if velocity_along_normal > 0.0 {
return;
}
let restitution: f64 = a.get_restitution().min(b.get_restitution());
let inverse_mass_sum: f64 = a_inverse_mass + b_inverse_mass;
if inverse_mass_sum == 0.0 {
return;
}
let impulse_magnitude: f64 =
-(1.0 + restitution) * velocity_along_normal / inverse_mass_sum;
let impulse: Vector3D = result.get_normal().scaled(impulse_magnitude);
*a.get_mut_velocity() -= impulse.scaled(a_inverse_mass);
*b.get_mut_velocity() += impulse.scaled(b_inverse_mass);
let correction: Vector3D = result
.get_normal()
.scaled((result.get_depth() * PHYSICS_POSITION_PERCENT / inverse_mass_sum).max(0.0));
*a.get_mut_position() -= correction.scaled(a_inverse_mass);
*b.get_mut_position() += correction.scaled(b_inverse_mass);
}
}
impl Updatable for PhysicsWorld3D {
fn update(&mut self, delta_time: f64) {
PhysicsWorld3D::step(self, delta_time);
}
}
impl Default for PhysicsWorld3D {
fn default() -> PhysicsWorld3D {
PhysicsWorld3D::with_config(PhysicsConfig3D::default())
}
}