Galactica/src/physics/physics.rs
2023-12-30 17:39:19 -08:00

218 lines
5.5 KiB
Rust

use cgmath::Point2;
use content::{Content, FactionHandle};
use crossbeam::channel::Receiver;
use nalgebra::vector;
use rapier2d::{
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle},
geometry::{ColliderBuilder, ColliderHandle, CollisionEvent},
pipeline::ChannelEventCollector,
};
use std::{collections::HashMap, f32::consts::PI};
use super::{wrapper::Wrapper, ShipHandle};
use crate::{content, game::outfits, objects, render::Sprite};
/// Keeps track of all objects in the world that we can interact with.
/// Also wraps our physics engine
pub struct Physics {
wrapper: Wrapper,
projectiles: HashMap<ColliderHandle, objects::Projectile>,
ships: HashMap<ColliderHandle, objects::Ship>,
collision_handler: ChannelEventCollector,
collision_queue: Receiver<CollisionEvent>,
}
// Private methods
impl Physics {
fn remove_projectile(&mut self, c: ColliderHandle) {
let p = match self.projectiles.remove(&c) {
Some(p) => p,
None => return,
};
self.wrapper.rigid_body_set.remove(
p.rigid_body,
&mut self.wrapper.im,
&mut self.wrapper.collider_set,
&mut self.wrapper.ij,
&mut self.wrapper.mj,
true,
);
}
fn remove_ship(&mut self, h: ShipHandle) {
self.wrapper.rigid_body_set.remove(
h.0,
&mut self.wrapper.im,
&mut self.wrapper.collider_set,
&mut self.wrapper.ij,
&mut self.wrapper.mj,
true,
);
self.ships.remove(&h.1);
}
fn add_projectile(&mut self, pb: objects::ProjectileBuilder) -> ColliderHandle {
let r = self.wrapper.rigid_body_set.insert(pb.rigid_body.build());
let c = self.wrapper.collider_set.insert_with_parent(
pb.collider.build(),
r,
&mut self.wrapper.rigid_body_set,
);
self.projectiles.insert(c, pb.build(r, c));
return c;
}
}
// Public methods
impl Physics {
pub fn new() -> Self {
let (collision_send, collision_queue) = crossbeam::channel::unbounded();
let (contact_force_send, _) = crossbeam::channel::unbounded();
Self {
wrapper: Wrapper::new(),
projectiles: HashMap::new(),
ships: HashMap::new(),
collision_handler: ChannelEventCollector::new(collision_send, contact_force_send),
collision_queue,
}
}
pub fn add_ship(
&mut self,
ct: &content::Ship,
outfits: Vec<outfits::ShipOutfit>,
position: Point2<f32>,
faction: FactionHandle,
) -> ShipHandle {
let cl = ColliderBuilder::convex_decomposition(
&ct.collision.points[..],
&ct.collision.indices[..],
)
// Rotate collider to match sprite
// (Collider starts pointing east, sprite starts pointing north.)
.rotation(PI / -2.0)
.mass(ct.mass);
let rb = RigidBodyBuilder::dynamic()
.translation(vector![position.x, position.y])
.can_sleep(false);
let r = self.wrapper.rigid_body_set.insert(rb.build());
let c = self.wrapper.collider_set.insert_with_parent(
cl.build(),
r,
&mut self.wrapper.rigid_body_set,
);
let h = ShipHandle(r, c);
self.ships
.insert(c, objects::Ship::new(ct, outfits, h, faction));
return h;
}
pub fn step(&mut self, t: f32, ct: &Content) {
// Run ship updates
let mut res = Vec::new();
let mut to_remove = Vec::new();
for (_, s) in &mut self.ships {
if s.hull <= 0.0 {
to_remove.push(s.physics_handle);
continue;
}
let r = &mut self.wrapper.rigid_body_set[s.physics_handle.0];
res.push(s.apply_controls(r, t));
}
for r in to_remove {
self.remove_ship(r);
}
for r in res {
for p in r.projectiles {
self.add_projectile(p);
}
}
// Update physics
self.wrapper.step(t, &self.collision_handler);
// Handle collision events
while let Ok(event) = &self.collision_queue.try_recv() {
if event.started() {
let a = &event.collider1();
let b = &event.collider2();
// If projectiles are a part of this collision, make sure
// `a` is one of them.
let (a, b) = if self.projectiles.contains_key(b) {
(b, a)
} else {
(a, b)
};
if let Some(p) = self.projectiles.get(a) {
if let Some(s) = self.ships.get_mut(b) {
let p_faction = ct.get_faction(p.faction);
let r = p_faction.relationships[&s.faction];
match r {
content::Relationship::Hostile => {
// TODO: implement death and spawning, and enable damage
//s.hull -= p.damage;
self.remove_projectile(*a);
self.remove_projectile(*b);
}
_ => {}
}
}
}
}
}
// Delete projectiles
let mut to_remove = Vec::new();
for (_, p) in &mut self.projectiles {
p.tick(t);
if p.is_expired() {
to_remove.push(p.collider);
}
}
for i in to_remove {
self.remove_projectile(i);
}
}
pub fn get_rigid_body(&self, r: RigidBodyHandle) -> &RigidBody {
&self.wrapper.rigid_body_set[r]
}
pub fn get_ship_mut(&mut self, s: &ShipHandle) -> Option<&mut objects::Ship> {
self.ships.get_mut(&s.1)
}
pub fn get_ship_body(&self, s: &ShipHandle) -> Option<(&objects::Ship, &RigidBody)> {
// TODO: handle dead handles
Some((self.ships.get(&s.1)?, self.wrapper.rigid_body_set.get(s.0)?))
}
pub fn iter_ship_body(&self) -> impl Iterator<Item = (&objects::Ship, &RigidBody)> + '_ {
self.ships.values().map(|x| {
(
x,
self.wrapper.rigid_body_set.get(x.physics_handle.0).unwrap(),
)
})
}
pub fn get_ship_sprites(&self) -> impl Iterator<Item = Sprite> + '_ {
self.ships
.values()
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.physics_handle.0]))
}
pub fn get_projectile_sprites(&self) -> impl Iterator<Item = Sprite> + '_ {
self.projectiles
.values()
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.rigid_body]))
}
}