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, ships: HashMap, collision_handler: ChannelEventCollector, collision_queue: Receiver, } // 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, position: Point2, 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 + '_ { 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 + '_ { 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 + '_ { self.projectiles .values() .map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.rigid_body])) } }