use galactica_system::data::ShipState; use galactica_util::{constants::UI_SPRITE_INSTANCE_LIMIT, to_radians}; use nalgebra::{Point2, Vector2}; use crate::{ datastructs::RenderState, vertexbuffer::{types::UiInstance, BufferObject}, PositionAnchor, RenderInput, }; pub(super) struct Radar { last_player_position: Point2, } impl Radar { pub fn new() -> Self { Self { last_player_position: Point2::new(0.0, 0.0), } } } impl Radar { pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) { let radar_range = 4000.0; let radar_size = 300.0; let hide_range = 0.85; let shrink_distance = 20.0; let system_object_scale = 1.0 / 600.0; let ship_scale = 1.0 / 10.0; // TODO: maybe a cleaner solution for last posititon? // This is necessary because the player may be dead or landed let player_ship = input .systemsim .get_ship(&galactica_system::phys::PhysSimShipHandle( input.player.ship.unwrap(), )) .unwrap(); match player_ship.data.get_state() { ShipState::Dead => {} ShipState::UnLanding { .. } => { let pos = player_ship .data .get_state() .unlanding_position(&input.ct) .unwrap(); self.last_player_position = Point2::new(pos.x, pos.y) } ShipState::Landed { target } => { let landed_body = input.ct.get_system_object(*target); self.last_player_position = Point2::new(landed_body.pos.x, landed_body.pos.y); } ShipState::Landing { .. } | ShipState::Flying { .. } | ShipState::Collapsing { .. } => { let player_body = input .systemsim .get_rigid_body(player_ship.rigid_body) .unwrap(); self.last_player_position = (*player_body.translation()).into(); } }; // TODO: don't hard-code these, add config options let planet_sprite = input.ct.get_sprite_handle("ui::planetblip"); let ship_sprite = input.ct.get_sprite_handle("ui::shipblip"); let arrow_sprite = input.ct.get_sprite_handle("ui::centerarrow"); // Enforce buffer limit if state.vertex_buffers.ui_counter as u64 > UI_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. panic!("UI limit exceeded!") } // Push this object's instance state.queue.write_buffer( &state.vertex_buffers.ui.instances, UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwNw.to_int(), position: [10.0, -10.0], angle: 0.0, size: radar_size, color: [1.0, 1.0, 1.0, 1.0], sprite_index: input.ct.get_sprite_handle("ui::radar").get_index(), }]), ); state.vertex_buffers.ui_counter += 1; // Draw system objects let system = input.ct.get_system(input.current_system); for o in &system.objects { let size = (o.size / o.pos.z) / (radar_range * system_object_scale); let p = Point2::new(o.pos.x, o.pos.y); let d = (p - self.last_player_position) / radar_range; // Add half the blip sprite's height to distance let m = d.magnitude() + (size / (2.0 * radar_size)); if m < hide_range { // Shrink blips as they get closeto the edge let size = size.min((hide_range - m) * size * shrink_distance); if size <= 2.0 { // Don't draw super tiny sprites, they flicker continue; } // Enforce buffer limit if state.vertex_buffers.ui_counter as u64 > UI_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. panic!("UI limit exceeded!") } // Push this object's instance state.queue.write_buffer( &state.vertex_buffers.ui.instances, UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwC.to_int(), position: (Point2::new(radar_size / 2.0 + 10.0, radar_size / -2.0 - 10.0) + (d * (radar_size / 2.0))) .into(), angle: o.angle, size, color: [0.5, 0.5, 0.5, 1.0], sprite_index: planet_sprite.get_index(), }]), ); state.vertex_buffers.ui_counter += 1; } } // Draw ships for (s, r) in input.systemsim.iter_ship_body() { let ship = input.ct.get_ship(s.data.get_content()); let (color, z_scale) = match s.data.get_state() { ShipState::Dead | ShipState::Landed { .. } => { continue; } // TODO: different color for landing? // TODO: scale blip for ship z-position ShipState::Landing { .. } => ([0.2, 0.2, 0.2, 1.0], 1.0), ShipState::UnLanding { .. } => ([0.2, 0.2, 0.2, 1.0], 1.0), ShipState::Collapsing { .. } => { // TODO: configurable ([0.2, 0.2, 0.2, 1.0], 1.0) } ShipState::Flying { .. } => { let c = input.ct.get_faction(s.data.get_faction()).color; ([c[0], c[1], c[2], 1.0], 1.0) } }; let size = (ship.size * ship.sprite.aspect) * ship_scale * z_scale; let p: Point2 = { if s.collider == input.player.ship.unwrap() { self.last_player_position } else { (*r.translation()).into() } }; let d = (p - self.last_player_position) / radar_range; let m = d.magnitude() + (size / (2.0 * radar_size)); if m < hide_range { let size = size.min((hide_range - m) * size * shrink_distance); if size < 2.0 { continue; } let angle = r.rotation().angle(); let position = Point2::new(radar_size / 2.0 + 10.0, radar_size / -2.0 - 10.0) + (d * (radar_size / 2.0)); // Enforce buffer limit // TODO: cleaner solution. don't do this everywhere. if state.vertex_buffers.ui_counter as u64 > UI_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. panic!("UI limit exceeded!") } // Push this object's instance state.queue.write_buffer( &state.vertex_buffers.ui.instances, UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwC.to_int(), position: position.into(), angle: -angle, // TODO: consistent angles size, color, sprite_index: ship_sprite.get_index(), }]), ); state.vertex_buffers.ui_counter += 1; } } // Draw viewport frame let d = Vector2::new( (input.camera_zoom / 2.0) * state.window_aspect, input.camera_zoom / 2.0, ) / radar_range; let m = d.magnitude(); let d = d * (radar_size / 2.0); let color = [0.3, 0.3, 0.3, 1.0]; if m < 0.8 { let sprite = input.ct.get_sprite_handle("ui::radarframe"); let size = 7.0f32.min((0.8 - m) * 70.0); // Enforce buffer limit (this section adds four items) if state.vertex_buffers.ui_counter as u64 + 4 > UI_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. panic!("UI limit exceeded!") } state.queue.write_buffer( &state.vertex_buffers.ui.instances, UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwNw.to_int(), position: Point2::new( (radar_size / 2.0 + 10.0) - d.x, (radar_size / -2.0 - 10.0) + d.y, ) .into(), angle: to_radians(90.0), size, color, sprite_index: sprite.get_index(), }]), ); state.vertex_buffers.ui_counter += 1; state.queue.write_buffer( &state.vertex_buffers.ui.instances, UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwSw.to_int(), position: Point2::new( (radar_size / 2.0 + 10.0) - d.x, (radar_size / -2.0 - 10.0) - d.y, ) .into(), angle: to_radians(180.0), size, color, sprite_index: sprite.get_index(), }]), ); state.vertex_buffers.ui_counter += 1; state.queue.write_buffer( &state.vertex_buffers.ui.instances, UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwSe.to_int(), position: Point2::new( (radar_size / 2.0 + 10.0) + d.x, (radar_size / -2.0 - 10.0) - d.y, ) .into(), angle: to_radians(270.0), size, color, sprite_index: sprite.get_index(), }]), ); state.vertex_buffers.ui_counter += 1; state.queue.write_buffer( &state.vertex_buffers.ui.instances, UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwNe.to_int(), position: Point2::new( (radar_size / 2.0 + 10.0) + d.x, (radar_size / -2.0 - 10.0) + d.y, ) .into(), angle: to_radians(0.0), size, color, sprite_index: sprite.get_index(), }]), ); state.vertex_buffers.ui_counter += 1; } // Arrow to center of system let q = Point2::new(0.0, 0.0) - self.last_player_position; let m = q.magnitude(); if m > 200.0 { let angle = clockwise_angle(&Vector2::new(1.0, 0.0), &q); let position = Point2::new(10.0 + (radar_size / 2.0), -10.0 - (radar_size / 2.0)) + Rotation2::new(angle) * Vector2::new(0.915 * (radar_size / 2.0), 0.0); if state.vertex_buffers.ui_counter as u64 > UI_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. panic!("UI limit exceeded!") } state.queue.write_buffer( &state.vertex_buffers.ui.instances, UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwC.to_int(), position: position.into(), angle, size: 10.0, color: [1.0, 1.0, 1.0, 1f32.min((m - 200.0) / 400.0)], sprite_index: arrow_sprite.get_index(), }]), ); state.vertex_buffers.ui_counter += 1; } } }