218 lines
5.5 KiB
Rust
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]))
|
|
}
|
|
}
|