use glc::{ math::{linear_step, sqr}, matrix::Matrix3f, vector::{Angle, Vector2f, Vector3f}, }; use rand::random; use shrev::ReaderId; use specs::{ hibitset::BitSet, prelude::*, world::Index, Entities, ParJoin, Read, ReadStorage, System, WriteStorage, }; use crate::{ components::{FleetOwned, Obstacle, Orbit, OrbitOwned, Position, Ship, ShipObstacle, 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>, entities: Entities<'a>, ships: WriteStorage<'a, Ship>, velocities: WriteStorage<'a, Velocity>, fleet_owned: ReadStorage<'a, FleetOwned>, orbit_owned: ReadStorage<'a, OrbitOwned>, positions: ReadStorage<'a, Position>, obstacles: ReadStorage<'a, Obstacle>, orbits: ReadStorage<'a, Orbit>, } struct Processor<'a> { need_update: &'a BitSet, entities: &'a Entities<'a>, positions: &'a ReadStorage<'a, Position>, obstacles: &'a ReadStorage<'a, Obstacle>, orbits: &'a ReadStorage<'a, Orbit>, orbit_owned: &'a ReadStorage<'a, OrbitOwned>, 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, entities, mut ships, mut velocities, fleet_owned, orbit_owned, positions, obstacles, orbits, } = data; self.progress_events(&fleet_owned); /* update ships */ let processor = Processor { need_update: &self.need_update, entities: &entities, positions: &positions, obstacles: &obstacles, orbit_owned: &orbit_owned, orbits: &orbits, delta: global.delta * global.world_speed, }; let data = ( positions.mask(), &mut ships, &mut velocities, &positions, &fleet_owned, ); data.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 orbit_owned = return_if_none!(self.orbit_owned.get(fleet_id)); let orbit_id = orbit_owned.owner(); let orbit = return_if_none!(self.orbits.get(orbit_id)); let orbit_pos = return_if_none!(self.positions.get(orbit_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 = ship_pos - orbit_pos; let mut ship_to_target = target_pos - ship_pos; let r_ship = orbit_to_ship.length_sqr(); let r_target = orbit_to_target.length_sqr(); let orbit_min = orbit.min(); let orbit_max = orbit.max(); let target_in_orbit = (r_target <= sqr(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 && 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 = velocity.dir().into_vec3(); let dir = if orbit_to_ship_vec3.cross(&ship_dir_vec3).z > 0.0 { 1.0 } else { -1.0 }; let orbit_min = orbit.min(); let orbit_max = orbit.max(); let add = SHIP_ORBIT_ANGLE_DELTA_MIN + SHIP_ORBIT_ANGLE_DELTA_RND * random::(); let angle = orbit_to_ship.angle2(&VECTOR_2F_POS_X) + (add * dir / orbit_max); let radius = orbit_min + (orbit_max - orbit_min) * random::(); Vector2f::new( orbit_pos.x + radius * angle.cos(), orbit_pos.y + radius * angle.sin(), ) } else { ship.set_obstacle(ShipObstacle::Search); *orbit_pos }; ship.set_target(target_pos, (target_pos - ship_pos).normalize()); ship_to_target = target_pos - ship_pos; } /* check if obstacle is still valid */ if ship_in_orbit { ship.set_obstacle(ShipObstacle::Done); } else if let ShipObstacle::Known(obstacle) = ship.obstacle() { if let Some(position) = self.positions.get(obstacle) { let obstacle_orbit = self.orbits.get(obstacle).unwrap(); let obstacle_pos = position.pos(); let ship_to_obstacle = obstacle_pos - ship_pos; let obstacle_angle = ship_to_target .angle2(&ship_to_obstacle) .into_deg() .into_inner() .abs(); let orbit_sqr = obstacle_orbit.max() * obstacle_orbit.max(); if (obstacle_angle > 90.0 && ship_to_obstacle.length_sqr() > orbit_sqr) || obstacle_angle > 170.0 { ship.set_obstacle(ShipObstacle::Search); } } else { ship.set_obstacle(ShipObstacle::Search); } } /* find obstacle */ if !ship_in_orbit && ship.obstacle() == ShipObstacle::Search { let mut dist_sqr = f32::MAX; for (e, position, _) in (self.entities, self.positions, self.obstacles).join() { let obstacle_pos = position.pos(); let ship_to_obstacle = obstacle_pos - ship_pos; if ship_to_target * ship_to_obstacle < 0.0 { continue; // obstacle is behind the ship } let len_sqr = ship_to_obstacle.length_sqr(); if len_sqr < dist_sqr { dist_sqr = len_sqr; ship.set_obstacle(ShipObstacle::Known(e)); } } if let ShipObstacle::Known(e) = ship.obstacle() { if e == fleet_owned.owner() { ship.set_obstacle(ShipObstacle::Done); } } } /* check the obstacle */ let mut expected_dir = ship_to_target; if let ShipObstacle::Known(obstacle) = ship.obstacle() { let obstacle_pos = self.positions.get(obstacle).unwrap(); let obstacle_orbit = self.orbits.get(obstacle).unwrap(); let ship_to_obstacle = obstacle_pos.pos() - ship_pos; let orbit_min = obstacle_orbit.min(); let orbit_max = obstacle_orbit.max(); let orbit = ship_to_obstacle.length(); if orbit < orbit_max { let mut tangent = Vector2f::new(-ship_to_obstacle.y, ship_to_obstacle.x); let radius = obstacle_pos.shape().radius(); let mut adjust_low = linear_step(orbit_min, radius, orbit); let adjust_high = 1.0 - linear_step(orbit_max, orbit_min, orbit); if ship_to_target * tangent < 0.0 { tangent = -tangent; } else { adjust_low = -adjust_low; } let a_low = Angle::Deg(45.0); let a_high = tangent.angle2(&ship_to_target); let mat = Matrix3f::rotate(a_low * adjust_low + a_high * adjust_high); expected_dir = mat.transform(tangent); } } /* update ship direction */ let angle = expected_dir.angle2(&velocity.dir()); if angle.into_inner().abs() > 0.0001 { let dir = angle.into_inner() / angle.abs().into_inner(); let agility = SHIP_ORBIT_AGILITY; let rot_speed = agility * linear_step(0.0, 45.0, angle.abs().into_deg().into_inner()); *velocity.dir_mut() *= Matrix3f::rotate(rot_speed * -dir * self.delta); } } }