use galactica_content::Relationship; use galactica_content::{Content, FactionHandle, ShipHandle, SystemHandle}; use galactica_playeragent::PlayerAgent; use nalgebra::{Isometry2, Point2, Rotation2, Vector2}; use rand::Rng; use rapier2d::{ dynamics::RigidBodyBuilder, geometry::{ColliderHandle, Group, InteractionGroups}, }; use std::collections::HashMap; use super::PhysEffectImage; use super::{ objects::{PhysEffect, PhysProjectile, PhysShip}, PhysImage, PhysProjectileImage, PhysShipImage, PhysStepResources, PhysWrapper, }; use crate::data::{ShipAutoPilot, ShipPersonality, ShipState}; // TODO: replace with a more generic handle /// A handle for a ship in this simulation /// This lets other crates reference ships /// without including `rapier2d`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct PhysSimShipHandle(pub ColliderHandle); pub(super) struct NewObjects { pub projectiles: Vec, pub effects: Vec, } /// A list of new objects to create this frame impl NewObjects { /// Create an empty NewObjects /// This should only be called once in each PhysSim pub fn new() -> Self { Self { projectiles: Vec::new(), effects: Vec::new(), } } /// Clear all lists in this struct. /// Call after saving all new objects. pub fn clear(&mut self) { self.projectiles.clear(); self.effects.clear(); } } /// Manages the physics state of one system pub struct PhysSim { /// The system this sim is attached to _system: SystemHandle, wrapper: PhysWrapper, new: NewObjects, effects: Vec, projectiles: HashMap, ships: HashMap, } // Private methods impl PhysSim { pub(super) fn start_unland_ship(&mut self, ct: &Content, collider: ColliderHandle) { let ship = self.ships.get_mut(&collider).unwrap(); let obj = ship.data.get_state().landed_on().unwrap(); let obj = ct.get_system_object(obj); let mut rng = rand::thread_rng(); let radius = rng.gen_range(500.0..=1500.0); let angle = rng.gen_range(0.0..std::f32::consts::TAU); let target_offset = Rotation2::new(angle) * Vector2::new(radius, 0.0); let target_trans = Vector2::new(obj.pos.x, obj.pos.y) + target_offset; let target_pos = Isometry2::new(target_trans, angle); ship.data.start_unland_to(ct, target_pos); let r = self.wrapper.get_rigid_body_mut(ship.rigid_body).unwrap(); r.set_enabled(true); r.set_position( Isometry2::new(Vector2::new(obj.pos.x, obj.pos.y), angle), true, ); } pub(super) fn collide_projectile_ship( &mut self, res: &mut PhysStepResources, projectile_h: ColliderHandle, ship_h: ColliderHandle, ) { let projectile = self.projectiles.get_mut(&projectile_h); let ship = self.ships.get_mut(&ship_h); if projectile.is_none() || ship.is_none() { return; } let projectile = projectile.unwrap(); let ship = ship.unwrap(); let f = res.ct.get_faction(projectile.faction); let r = f.relationships.get(&ship.data.get_faction()).unwrap(); let destory_projectile = match r { Relationship::Hostile => match ship.data.get_state() { ShipState::Flying { .. } => { ship.data.apply_damage(projectile.content.damage); true } ShipState::Collapsing { .. } => true, _ => false, }, _ => false, }; if destory_projectile { let pr = self.wrapper.get_rigid_body(projectile.rigid_body).unwrap(); let v = pr.velocity_at_point(pr.center_of_mass()).normalize() * projectile.content.force; let pos = *pr.translation(); let _ = pr; let r = self.wrapper.get_rigid_body_mut(ship.rigid_body).unwrap(); r.apply_impulse_at_point(Vector2::new(v.x, v.y), Point2::new(pos.x, pos.y), true); // Borrow again, we can only have one at a time let pr = self.wrapper.get_rigid_body(projectile.rigid_body).unwrap(); let pos = *pr.translation(); let angle = pr.rotation().angle(); match &projectile.content.impact_effect { None => {} Some(x) => { let r = ship.rigid_body; let sr = self.wrapper.get_rigid_body(r).unwrap(); let parent_velocity = pr.velocity_at_point(pr.center_of_mass()); let target_velocity = sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y)); self.effects.push(PhysEffect::new( res.ct, &mut self.wrapper, *x, pos, angle, parent_velocity, target_velocity, )); } }; projectile.destroy_silent(res, &mut self.new, &mut self.wrapper); } } } // Public methods impl PhysSim { /// Create a new physics system pub fn new(_ct: &Content, system: SystemHandle) -> Self { Self { _system: system, wrapper: PhysWrapper::new(), new: NewObjects::new(), effects: Vec::new(), projectiles: HashMap::new(), ships: HashMap::new(), } } /// Add a ship to this physics system pub fn add_ship( &mut self, ct: &Content, handle: ShipHandle, faction: FactionHandle, personality: ShipPersonality, position: Point2, ) -> PhysSimShipHandle { let ship_content = ct.get_ship(handle); let mut cl = ship_content.collider.0.clone(); // TODO: additonal ship mass from outfits and cargo let rb = RigidBodyBuilder::dynamic() .angular_damping(ship_content.angular_drag) .linear_damping(ship_content.linear_drag) .translation(Vector2::new(position.x, position.y)) .can_sleep(false); cl.set_collision_groups(InteractionGroups::new( Group::GROUP_1, Group::GROUP_1 | Group::GROUP_2, )); let ridid_body = self.wrapper.insert_rigid_body(rb.build()); let collider = self.wrapper.insert_collider(cl, ridid_body); self.ships.insert( collider, PhysShip::new(ct, handle, personality, faction, ridid_body, collider), ); return PhysSimShipHandle(collider); } /// Update a player ship's controls pub fn update_player_controls(&mut self, ct: &Content, player: &PlayerAgent) { if player.ship.is_none() { return; } let ship_object = self.ships.get_mut(&player.ship.unwrap()); if let Some(ship_object) = ship_object { match ship_object.data.get_state() { ShipState::Landing { .. } | ShipState::UnLanding { .. } | ShipState::Collapsing { .. } | ShipState::Dead => {} ShipState::Flying { autopilot: ShipAutoPilot::None, } => { ship_object.controls.guns = player.input.pressed_guns(); ship_object.controls.left = player.input.pressed_left(); ship_object.controls.right = player.input.pressed_right(); ship_object.controls.thrust = player.input.pressed_thrust(); if player.input.pressed_land() { ship_object.data.set_autopilot(ShipAutoPilot::Landing { target: player.selection.get_planet().unwrap(), }) } } ShipState::Flying { .. } => { // Any input automatically releases autopilot if player.input.pressed_left() || player.input.pressed_right() || player.input.pressed_thrust() || player.input.pressed_guns() { ship_object.data.set_autopilot(ShipAutoPilot::None); } } ShipState::Landed { .. } => { if player.input.pressed_land() { self.start_unland_ship(ct, player.ship.unwrap()); } } }; } } /// Step this simulation by `t` seconds pub fn step(&mut self, mut res: PhysStepResources, img: &PhysImage) { res.timing.start_physics_sim(); // Update physics self.wrapper.step(res.t); // Handle collision events while let Ok(event) = &self.wrapper.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) }; let p = self.projectiles.get(&a); let s = self.ships.get_mut(&b); if p.is_none() || s.is_none() { continue; } self.collide_projectile_ship(&mut res, a, b); } } // Step and garbage-collect projectiles self.projectiles.retain(|_, proj| { proj.step(&mut res, &mut self.new, &mut self.wrapper); !proj.should_remove() }); // Step and garbage-collect ships res.timing.start_physics_ships(); self.ships.retain(|_, ship| { ship.step(&mut res, img, &mut self.wrapper, &mut self.new); !ship.should_remove() }); res.timing.mark_physics_ships(); // Step and garbage-collect effects self.effects.retain_mut(|x| { x.step(&res, &mut self.wrapper); !x.is_destroyed() }); // Process new objects for p in self.new.projectiles.iter() { self.projectiles.insert(p.collider, p.clone()); } for e in self.new.effects.iter() { self.effects.push(e.clone()); } self.new.clear(); res.timing.mark_physics_sim(); } /// Update an image with the current simulation state pub fn update_image(&self, img: &mut PhysImage) { img.clear(); for s in self.ships.values() { img.ship_map .insert(PhysSimShipHandle(s.collider), img.ships.len()); img.ships.push(PhysShipImage { ship: s.clone(), rigidbody: self.wrapper.get_rigid_body(s.rigid_body).unwrap().clone(), }) } for p in self.projectiles.values() { img.projectiles.push(PhysProjectileImage { projectile: p.clone(), rigidbody: self.wrapper.get_rigid_body(p.rigid_body).unwrap().clone(), }) } for e in self.effects.iter() { img.effects.push(PhysEffectImage { effect: e.clone(), rigidbody: self.wrapper.get_rigid_body(e.rigid_body).unwrap().clone(), }) } } } // Public getters impl PhysSim { /// Get a ship physics object pub fn get_ship(&self, ship: &PhysSimShipHandle) -> Option<&PhysShip> { self.ships.get(&ship.0) } /// Get a ship physics object pub fn get_ship_mut(&mut self, ship: &PhysSimShipHandle) -> Option<&mut PhysShip> { self.ships.get_mut(&ship.0) } /// Iterate over all ships in this physics system pub fn iter_ships(&self) -> impl Iterator + '_ { self.ships.values() } /// Iterate over all ships in this physics system pub fn iter_projectiles(&self) -> impl Iterator + '_ { self.projectiles.values() } /// Iterate over all effects in this physics system pub fn iter_effects(&self) -> impl Iterator + '_ { self.effects.iter() } }