|
- 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, MeetingPoint, MeetingPointOwned, Obstacle, Position, Shape, Ship, ShipObstacle,
- },
- constants::{
- SHIP_ORBIT_AGILITY, SHIP_ORBIT_ANGLE_DELTA_MIN, SHIP_ORBIT_ANGLE_DELTA_RND,
- SHIP_ORBIT_DISTANCE_MAX, VECTOR_2F_POS_X,
- },
- misc::{ComponentEvent, StorageHelper, StorageHelperMut},
- resources::Global,
- return_if_none,
- };
-
- pub struct Ships {
- need_update: BitSet,
- fleet_owned_id: ReaderId<ComponentEvent<FleetOwned>>,
- }
-
- #[derive(SystemData)]
- pub struct ShipsData<'a> {
- global: Read<'a, Global>,
- entities: Entities<'a>,
- ships: WriteStorage<'a, Ship>,
- fleet_owned: ReadStorage<'a, FleetOwned>,
- meeting_point_owned: ReadStorage<'a, MeetingPointOwned>,
- positions: ReadStorage<'a, Position>,
- shapes: ReadStorage<'a, Shape>,
- obstacles: ReadStorage<'a, Obstacle>,
- meeting_points: ReadStorage<'a, MeetingPoint>,
- }
-
- struct Processor<'a> {
- need_update: &'a BitSet,
- entities: &'a Entities<'a>,
- positions: &'a ReadStorage<'a, Position>,
- shapes: &'a ReadStorage<'a, Shape>,
- obstacles: &'a ReadStorage<'a, Obstacle>,
- meeting_points: &'a ReadStorage<'a, MeetingPoint>,
- meeting_point_owned: &'a ReadStorage<'a, MeetingPointOwned>,
- delta: f32,
- }
-
- impl Ships {
- pub fn new(world: &mut World) -> Self {
- WriteStorage::<FleetOwned>::setup(world);
-
- let need_update = BitSet::new();
- let fleet_owned_id = world
- .system_data::<WriteStorage<FleetOwned>>()
- .register_event_reader();
-
- Self {
- need_update,
- fleet_owned_id,
- }
- }
-
- fn progress_events(&mut self, fleet_owned: &ReadStorage<'_, FleetOwned>) {
- self.need_update.clear();
- let events = fleet_owned.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,
- fleet_owned,
- meeting_point_owned,
- positions,
- shapes,
- obstacles,
- meeting_points,
- } = data;
-
- self.progress_events(&fleet_owned);
-
- /* update ships */
- let processor = Processor {
- need_update: &self.need_update,
- entities: &entities,
- positions: &positions,
- shapes: &shapes,
- obstacles: &obstacles,
- meeting_point_owned: &meeting_point_owned,
- meeting_points: &meeting_points,
- delta: global.delta * global.world_speed,
- };
-
- ships.set_event_emission(false);
-
- let data = (positions.mask(), &mut ships, &positions, &fleet_owned);
-
- data.par_join()
- .for_each(|(id, ship, position, fleet_owned)| {
- processor.progress_ship(id, ship, position, fleet_owned);
- });
-
- ships.set_event_emission(true);
- }
- }
-
- impl Processor<'_> {
- fn progress_ship(
- &self,
- id: Index,
- ship: &mut Ship,
- position: &Position,
- fleet_owned: &FleetOwned,
- ) {
- let fleet_id = fleet_owned.owner();
- let meeting_point_owned = return_if_none!(self.meeting_point_owned.get(fleet_id));
- let meeting_point_id = meeting_point_owned.owner();
- let meeting_point = return_if_none!(self.meeting_points.get(meeting_point_id));
- let meeting_point_pos = return_if_none!(self.positions.get(meeting_point_id)).get();
- let ship_pos = position.get();
- let target_pos = ship.target_pos();
- let target_dir = ship.target_dir();
-
- let meeting_point_to_target = target_pos - meeting_point_pos;
- let meeting_point_to_ship = ship_pos - meeting_point_pos;
- let mut ship_to_target = target_pos - ship_pos;
-
- let r_ship = meeting_point_to_ship.length_sqr();
- let r_target = meeting_point_to_target.length_sqr();
-
- let meeting_point_min = meeting_point.min();
- let meeting_point_max = meeting_point.max();
-
- let target_in_meeting_point =
- (r_target <= sqr(meeting_point_max)) && (r_target >= sqr(meeting_point_min));
- let ship_in_meeting_point = r_ship < sqr(SHIP_ORBIT_DISTANCE_MAX * meeting_point_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_meeting_point != target_in_meeting_point
- {
- let target_pos = if ship_in_meeting_point && meeting_point_max > 0.0 {
- let meeting_point_to_ship_vec3 =
- Vector3f::new(meeting_point_to_ship.x, meeting_point_to_ship.y, 0.0);
- let ship_dir_vec3 = ship.dir().into_vec3();
-
- let dir = if meeting_point_to_ship_vec3.cross(&ship_dir_vec3).z > 0.0 {
- 1.0
- } else {
- -1.0
- };
-
- let meeting_point_min = meeting_point.min();
- let meeting_point_max = meeting_point.max();
-
- let add = SHIP_ORBIT_ANGLE_DELTA_MIN + SHIP_ORBIT_ANGLE_DELTA_RND * random::<f32>();
- let angle = meeting_point_to_ship.angle2(&VECTOR_2F_POS_X)
- + (add * dir / meeting_point_max);
- let radius =
- meeting_point_min + (meeting_point_max - meeting_point_min) * random::<f32>();
-
- Vector2f::new(
- meeting_point_pos.x + radius * angle.cos(),
- meeting_point_pos.y + radius * angle.sin(),
- )
- } else {
- *ship.obstacle_mut() = ShipObstacle::Search;
-
- *meeting_point_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_meeting_point {
- *ship.obstacle_mut() = ShipObstacle::Done;
- } else if let ShipObstacle::Known(obstacle) = ship.obstacle() {
- if let Some(position) = self.positions.get(obstacle) {
- let obstacle_meeting_point = self.meeting_points.get(obstacle).unwrap();
- let obstacle_pos = position.get();
- let ship_to_obstacle = obstacle_pos - ship_pos;
-
- let obstacle_angle = ship_to_target
- .angle2(&ship_to_obstacle)
- .into_deg()
- .into_inner()
- .abs();
-
- let meeting_point_sqr = obstacle_meeting_point.max() * obstacle_meeting_point.max();
- if (obstacle_angle > 90.0 && ship_to_obstacle.length_sqr() > meeting_point_sqr)
- || obstacle_angle > 170.0
- {
- *ship.obstacle_mut() = ShipObstacle::Search;
- }
- } else {
- *ship.obstacle_mut() = ShipObstacle::Search;
- }
- }
-
- /* find obstacle */
- if !ship_in_meeting_point && 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.get();
- 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.obstacle_mut() = ShipObstacle::Known(e);
- }
- }
-
- if let ShipObstacle::Known(e) = ship.obstacle() {
- if e == fleet_owned.owner() {
- *ship.obstacle_mut() = 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_shape = self.shapes.get(obstacle).unwrap();
- let obstacle_meeting_point = self.meeting_points.get(obstacle).unwrap();
- let ship_to_obstacle = obstacle_pos.get() - ship_pos;
-
- let meeting_point_min = obstacle_meeting_point.min();
- let meeting_point_max = obstacle_meeting_point.max();
-
- let meeting_point = ship_to_obstacle.length();
- if meeting_point < meeting_point_max {
- let mut tangent = Vector2f::new(-ship_to_obstacle.y, ship_to_obstacle.x);
-
- let radius = obstacle_shape.radius();
- let mut adjust_low = linear_step(meeting_point_min, radius, meeting_point);
- let adjust_high =
- 1.0 - linear_step(meeting_point_max, meeting_point_min, meeting_point);
-
- 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(ship.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());
-
- *ship.dir_mut() *= Matrix3f::rotate(rot_speed * -dir * self.delta);
- }
- }
- }
|