use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Point3, Rad, Vector2}; use crossbeam::channel::Receiver; use nalgebra::vector; use rand::Rng; use rapier2d::{ dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle}, geometry::{ColliderBuilder, ColliderHandle, CollisionEvent}, pipeline::{ActiveEvents, ChannelEventCollector}, }; use std::{collections::HashMap, f32::consts::PI}; use crate::{objects, objects::ProjectileWorldObject, util, wrapper::Wrapper, ShipPhysicsHandle}; use galactica_content as content; use galactica_gameobject as object; use galactica_render::{ObjectSprite, ParticleBuilder}; /// Keeps track of all objects in the world that we can interact with. /// Also wraps our physics engine pub struct World { wrapper: Wrapper, projectiles: HashMap, ships: HashMap, collision_handler: ChannelEventCollector, collision_queue: Receiver, } // Private methods impl<'a> World { 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: ShipPhysicsHandle) { 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); } /// Add a projectile fired from a ship fn add_projectiles( &mut self, s: &ShipPhysicsHandle, p: Vec<(object::Projectile, content::GunPoint)>, ) { let mut rng = rand::thread_rng(); for (projectile, point) in p { let r = self.get_ship_body(&s).unwrap().1; let ship_pos = util::rigidbody_position(r); let ship_rot = util::rigidbody_rotation(r); let ship_vel = util::rigidbody_velocity(r); let ship_ang = ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); let pos = ship_pos + (Matrix2::from_angle(-ship_ang) * point.pos.to_vec()); let spread: Rad = Deg(rng.gen_range( -(projectile.content.angle_rng.0 / 2.0)..=projectile.content.angle_rng.0 / 2.0, )) .into(); let vel = ship_vel + (Matrix2::from_angle(-ship_ang + spread) * Vector2 { x: 0.0, y: projectile.content.speed + rng.gen_range( -projectile.content.speed_rng..=projectile.content.speed_rng, ), }); let rigid_body = RigidBodyBuilder::kinematic_velocity_based() .translation(vector![pos.x, pos.y]) .rotation(-ship_ang.0) .linvel(vector![vel.x, vel.y]) .build(); let collider = ColliderBuilder::ball(1.0) .sensor(true) .active_events(ActiveEvents::COLLISION_EVENTS) .build(); let rigid_body = self.wrapper.rigid_body_set.insert(rigid_body); let collider = self.wrapper.collider_set.insert_with_parent( collider, rigid_body, &mut self.wrapper.rigid_body_set, ); self.projectiles.insert( collider.clone(), ProjectileWorldObject { projectile, rigid_body, collider, }, ); } } } // Public methods impl<'a> World { /// Create a new physics system 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, } } /// Add a ship to this physics system pub fn add_ship( &mut self, ct: &content::Content, ship: object::Ship, position: Point2, ) -> ShipPhysicsHandle { let ship_content = ct.get_ship(ship.handle); let cl = ColliderBuilder::convex_decomposition( &ship_content.collision.points[..], &ship_content.collision.indices[..], ) // Rotate collider to match sprite // (Collider starts pointing east, sprite starts pointing north.) .rotation(PI / -2.0) .mass(ship_content.mass); // TODO: only build colliders once let rb = RigidBodyBuilder::dynamic() .angular_damping(ship_content.angular_drag) .linear_damping(ship_content.linear_drag) .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 = ShipPhysicsHandle(r, c); self.ships.insert(c, objects::ShipWorldObject::new(ship, h)); return h; } /// Step this physics system by `t` seconds pub fn step(&mut self, t: f32, ct: &content::Content, particles: &mut Vec) { // Run ship updates // TODO: maybe reorganize projectile creation? let mut projectiles = Vec::new(); let mut to_remove = Vec::new(); for (_, s) in &mut self.ships { if s.ship.is_destroyed() { to_remove.push(s.physics_handle); continue; } let r = &mut self.wrapper.rigid_body_set[s.physics_handle.0]; s.step(r, t); if s.controls.guns { projectiles.push((s.physics_handle, s.ship.fire_guns())); } } for (s, p) in projectiles { self.add_projectiles(&s, p); } for s in to_remove { self.remove_ship(s); } // 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 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 hit = s.ship.handle_projectile_collision(ct, &p.projectile); if hit { let r = self.get_rigid_body(p.rigid_body); let pos = util::rigidbody_position(r); particles.push(ParticleBuilder { texture: ct.get_texture_handle("particle::blaster"), pos: Point3 { x: pos.x, y: pos.y, z: 1.0, // TODO:remove z coordinate }, lifetime: 0.1, size: 10.0, }); self.remove_projectile(*a); } } } } } // Delete projectiles let mut to_remove = Vec::new(); for (_, p) in &mut self.projectiles { p.projectile.tick(t); if p.projectile.is_expired() { to_remove.push(p.collider); } } for i in to_remove { self.remove_projectile(i); } } /// Get a rigid body from a handle pub fn get_rigid_body(&self, r: RigidBodyHandle) -> &RigidBody { &self.wrapper.rigid_body_set[r] } /// Get a ship from a handle pub fn get_ship_mut(&mut self, s: &ShipPhysicsHandle) -> Option<&mut objects::ShipWorldObject> { self.ships.get_mut(&s.1) } /// Get a ship and its rigidbody from a handle pub fn get_ship_body( &self, s: &ShipPhysicsHandle, ) -> Option<(&objects::ShipWorldObject, &RigidBody)> { // TODO: handle dead handles Some((self.ships.get(&s.1)?, self.wrapper.rigid_body_set.get(s.0)?)) } /// Iterate over all ships in this physics system 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(), ) }) } /// Iterate over all ship sprites in this physics system pub fn get_ship_sprites( &'a self, ct: &'a content::Content, ) -> impl Iterator + '_ { self.ships .values() .map(|x| x.get_sprite(ct, &self.wrapper.rigid_body_set[x.physics_handle.0])) } /// Iterate over all projectile sprites in this physics system pub fn get_projectile_sprites(&self) -> impl Iterator + '_ { self.projectiles .values() .map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.rigid_body])) } }