use glc::{ math::{linear_step, sqr}, matrix::Matrix3f, vector::{Vector2f, Vector3f}, }; use rand::random; use shrev::ReaderId; use specs::{ hibitset::BitSet, prelude::*, world::Index, ParJoin, Read, ReadStorage, System, WriteStorage, }; use crate::{ components::{Fleet, FleetOwned, Position, Ship, Velocity}, constants::{ SHIP_ORBIT_AGILITY, SHIP_ORBIT_ANGLE_DELTA_MIN, SHIP_ORBIT_ANGLE_DELTA_RND, SHIP_ORBIT_DISTANCE_MAX, VECTOR_2F_POS_X, }, misc::ComponentEvent, resources::Global, return_if_none, }; pub struct Ships { need_update: BitSet, fleet_owned_id: ReaderId>, } #[derive(SystemData)] pub struct ShipsData<'a> { global: Read<'a, Global>, ships: WriteStorage<'a, Ship>, velocities: WriteStorage<'a, Velocity>, fleet_owned: ReadStorage<'a, FleetOwned>, positions: ReadStorage<'a, Position>, fleets: ReadStorage<'a, Fleet>, } struct Processor<'a> { need_update: &'a BitSet, positions: &'a ReadStorage<'a, Position>, fleets: &'a ReadStorage<'a, Fleet>, delta: f32, } impl Ships { pub fn new(world: &mut World) -> Self { let need_update = BitSet::new(); let fleet_owned_id = unsafe { WriteStorage::::setup(world); let mut fleet_owned = world.system_data::>(); fleet_owned .unprotected_storage_mut() .channel_mut() .register_reader() }; Self { need_update, fleet_owned_id, } } fn progress_events(&mut self, fleet_owned: &ReadStorage<'_, FleetOwned>) { self.need_update.clear(); let events = fleet_owned .unprotected_storage() .channel() .read(&mut self.fleet_owned_id); for event in events { let id = match event { ComponentEvent::Inserted(id) => id, ComponentEvent::Modified(id, _) => id, ComponentEvent::Removed(id, _) => id, }; self.need_update.add(*id); } } } impl<'a> System<'a> for Ships { type SystemData = ShipsData<'a>; fn run(&mut self, data: Self::SystemData) { let ShipsData { global, mut ships, mut velocities, fleet_owned, positions, fleets, } = data; self.progress_events(&fleet_owned); /* update ships */ let processor = Processor { need_update: &self.need_update, positions: &positions, fleets: &fleets, delta: global.delta, }; ( positions.mask(), &mut ships, &mut velocities, &positions, &fleet_owned, ) .par_join() .for_each(|(id, ship, velocity, position, fleet_owned)| { processor.progress_ship(id, ship, velocity, position, fleet_owned); }); } } impl Processor<'_> { fn progress_ship( &self, id: Index, ship: &mut Ship, velocity: &mut Velocity, position: &Position, fleet_owned: &FleetOwned, ) { let fleet_id = fleet_owned.owner; let fleet = return_if_none!(self.fleets.get(fleet_id)); let orbit_pos = return_if_none!(self.positions.get(fleet_id)).pos; let ship_pos = position.pos; let target_pos = ship.target_pos; let target_dir = ship.target_dir; let orbit_to_target = target_pos - orbit_pos; let orbit_to_ship = position.pos - orbit_pos; let ship_to_target = target_pos - position.pos; let r_ship = orbit_to_ship.length_sqr(); let r_target = orbit_to_target.length_sqr(); let orbit_min = fleet.orbit_min; let orbit_max = fleet.orbit_max; let target_in_orbit = (r_target <= sqr(fleet.orbit_max)) && (r_target >= sqr(orbit_min)); let ship_in_orbit = r_ship < sqr(SHIP_ORBIT_DISTANCE_MAX * orbit_max); let need_update = self.need_update.contains(id); let has_target = target_dir.length_sqr() != 0.0; let passed_target = target_dir * ship_to_target <= 0.0; /* check and update target posistion */ if need_update || !has_target || passed_target || ship_in_orbit != target_in_orbit { let target_pos = if ship_in_orbit && fleet.orbit_max > 0.0 { let orbit_to_ship_vec3 = Vector3f::new(orbit_to_ship.x, orbit_to_ship.y, 0.0); let ship_dir_vec3 = Vector3f::new(velocity.dir.x, velocity.dir.y, 0.0); let dir = if orbit_to_ship_vec3.cross(&ship_dir_vec3).z > 0.0 { 1.0 } else { -1.0 }; let angle = orbit_to_ship.angle2(&VECTOR_2F_POS_X); let angle = angle + (SHIP_ORBIT_ANGLE_DELTA_MIN + SHIP_ORBIT_ANGLE_DELTA_RND * random()) * dir / fleet.orbit_max; let radius = fleet.orbit_min + (fleet.orbit_max - fleet.orbit_min) * random::(); Vector2f::new( orbit_pos.x + radius * angle.cos(), orbit_pos.y + radius * angle.sin(), ) } else { orbit_pos }; ship.target_pos = target_pos; ship.target_dir = (target_pos - ship_pos).normalize(); } /* update ship direction */ let angle = ship_to_target.angle2(&velocity.dir); if angle.into_inner().abs() > 0.0001 { let dir = angle.into_inner() / angle.abs().into_inner(); let agility = if ship_in_orbit { SHIP_ORBIT_AGILITY } else { ship.agility }; let rot_speed = agility * linear_step(0.0, 45.0, angle.abs().into_deg().into_inner()); let m = Matrix3f::new( Vector3f::new(velocity.dir.y, -velocity.dir.x, 0.0), Vector3f::new(velocity.dir.x, velocity.dir.y, 0.0), Vector3f::new(0.0, 0.0, 1.0), ); let m = m * Matrix3f::rotate(rot_speed * -dir * self.delta); velocity.dir = Vector2f::new(m.axis_y.x, m.axis_y.y); } } }