|
- use std::mem::take;
- use std::time::Instant;
-
- use glc::{
- math::{clamp, linear_step},
- matrix::Matrix4f,
- misc::BindGuard,
- shader::{Program, Type, Uniform},
- vector::{Vector2f, Vector4f},
- };
- use shrev::{EventChannel, ReaderId};
- use space_crush_common::{
- components::{Fleet, Orbit, OrbitOwned, Position, ShipCount},
- constants::VECTOR_2F_POS_X,
- continue_if_none,
- misc::{LogResult, WorldHelper as _},
- resources::Global,
- return_if_none,
- };
- use specs::{prelude::*, ReadExpect, ReadStorage, System, World};
-
- use crate::{
- constants::{
- FLEET_SELECT_ANIMATION_TIME, FLEET_SELECT_DETAIL_TIMEOUT, FLEET_SELECT_OFFSET,
- FLEET_SELECT_TEXT_OFFSET, FLEET_SELECT_TEXT_SIZE, FLEET_SELECT_WIDTH,
- UNIFORM_BUFFER_INDEX_CAMERA,
- },
- misc::{
- HorizontalAlign, MouseEvent, Text, TextCache, TextManager, VerticalAlign, WorldHelper as _,
- },
- resources::{Camera, Config, GameState, Geometry, InputState, Selection},
- Error,
- };
-
- pub struct FleetSelect {
- program_simple: Program,
- program_detail: Program,
-
- cache: TextCache,
- text_total: Text,
- text_fighter: Text,
- text_bomber: Text,
- text_transporter: Text,
-
- mouse_event_id: ReaderId<MouseEvent>,
- select_mode: SelectMode,
- camera_counter: usize,
- need_value_update: bool,
- values_changed_once: bool,
- is_new_selection: bool,
-
- mouse_pos: Vector2f,
- count: ShipCount,
- values: Vector4f,
- marker: Vector2f,
- shape_size: f32,
- ring0: f32,
- ring1: f32,
- zoom: f32,
- }
-
- #[derive(Copy, Clone, Debug)]
- enum SelectMode {
- None,
- Init(Instant),
- Simple(f32),
- Detail(f32),
- SimpleClose(f32),
- DetailClose(f32),
- }
-
- #[derive(SystemData)]
- pub struct FleetSelectData<'a> {
- game_state: WriteExpect<'a, GameState>,
- camera: ReadExpect<'a, Camera>,
-
- mouse_events: ReadExpect<'a, EventChannel<MouseEvent>>,
- input_state: ReadExpect<'a, InputState>,
- geometry: ReadExpect<'a, Geometry>,
- global: ReadExpect<'a, Global>,
- config: ReadExpect<'a, Config>,
-
- orbit_owned: ReadStorage<'a, OrbitOwned>,
- positions: ReadStorage<'a, Position>,
- orbits: ReadStorage<'a, Orbit>,
- fleets: ReadStorage<'a, Fleet>,
- }
-
- macro_rules! selection {
- (&$data:expr) => {
- return_if_none!($data.game_state.selection())
- };
- (&mut $data:expr) => {
- return_if_none!($data.game_state.selection_mut())
- };
- }
-
- macro_rules! fleet {
- (&$data:expr, $id:expr) => {
- return_if_none!($data.fleets.get($id))
- };
- }
-
- macro_rules! position {
- (&$data:expr, $id:expr) => {
- return_if_none!($data.positions.get($id))
- };
- }
-
- macro_rules! orbit_owned {
- (&$data:expr, $id:expr) => {
- return_if_none!($data.orbit_owned.get($id))
- };
- }
-
- impl FleetSelect {
- pub fn new(world: &World, text_manager: &TextManager) -> Result<Self, Error> {
- let program_simple = world.load_program(vec![
- (Type::Vertex, "resources/shader/fleet_select/vert.glsl"),
- (
- Type::Fragment,
- "resources/shader/fleet_select/simple_frag.glsl",
- ),
- ])?;
- program_simple.uniform_block_binding("Camera", UNIFORM_BUFFER_INDEX_CAMERA)?;
-
- let program_detail = world.load_program(vec![
- (Type::Vertex, "resources/shader/fleet_select/vert.glsl"),
- (
- Type::Fragment,
- "resources/shader/fleet_select/detail_frag.glsl",
- ),
- ])?;
- program_detail.uniform_block_binding("Camera", UNIFORM_BUFFER_INDEX_CAMERA)?;
-
- let cache = text_manager.create_cache()?;
- let text_total = new_text(&cache)?;
- let text_fighter = new_text(&cache)?;
- let text_bomber = new_text(&cache)?;
- let text_transporter = new_text(&cache)?;
-
- let select_mode = SelectMode::None;
- let mouse_event_id = world.register_event_reader::<MouseEvent>()?;
-
- Ok(Self {
- program_simple,
- program_detail,
-
- cache,
- text_total,
- text_fighter,
- text_bomber,
- text_transporter,
-
- mouse_event_id,
- select_mode,
- camera_counter: 0,
- need_value_update: true,
- values_changed_once: false,
- is_new_selection: true,
-
- mouse_pos: Default::default(),
- count: Default::default(),
- values: Default::default(),
- marker: Default::default(),
- shape_size: Default::default(),
- ring0: Default::default(),
- ring1: Default::default(),
- zoom: Default::default(),
- })
- }
-
- fn update_camera(&mut self, d: &FleetSelectData<'_>) {
- let camera_counter = d.camera.update_counter();
-
- if self.camera_counter != camera_counter {
- self.camera_counter = camera_counter;
- self.need_value_update = true;
- }
- }
-
- fn handle_events(&mut self, d: &mut FleetSelectData<'_>) {
- let events = d.mouse_events.read(&mut self.mouse_event_id);
- for event in events {
- match event {
- MouseEvent::ButtonDown(button) if button == &d.config.input.fleet_select_button => {
- let pos = d.camera.view_to_world(d.input_state.mouse_pos);
- let selection = d.game_state.selection_mut().take();
- for (position, orbit) in (&d.positions, &d.orbits).join() {
- let r = orbit.max() * orbit.max();
- if (position.pos() - pos).length_sqr() <= r {
- let player_index = d.game_state.player_index();
- let fleet_id = continue_if_none!(orbit.fleets().get(player_index));
- let fleet_id = *continue_if_none!(fleet_id);
-
- *d.game_state.selection_mut() = match selection {
- Some(s) if s.fleet == fleet_id => {
- self.is_new_selection = false;
-
- Some(s)
- }
- _ => {
- self.is_new_selection = true;
-
- Some(Selection {
- fleet: fleet_id,
- count: ShipCount::none(),
- })
- }
- };
-
- let selection = selection!(&d);
- let fleet = fleet!(&d, selection.fleet);
- let timeout = Instant::now() + FLEET_SELECT_DETAIL_TIMEOUT;
-
- self.mouse_pos = d.input_state.mouse_pos;
- self.select_mode = SelectMode::Init(timeout);
- self.values_changed_once = false;
- self.set_count(selection.count, &fleet.count());
-
- break;
- }
- }
- }
- MouseEvent::ButtonUp(button) if button == &d.config.input.fleet_select_button => {
- self.select_mode = match self.select_mode {
- SelectMode::Simple(progress) => {
- selection!(&mut d).count = self.count;
-
- self.mouse_pos = d.input_state.mouse_pos;
-
- SelectMode::SimpleClose(progress)
- }
- SelectMode::Detail(progress) => {
- selection!(&mut d).count = self.count;
-
- self.mouse_pos = d.input_state.mouse_pos;
-
- SelectMode::DetailClose(progress)
- }
- SelectMode::Init(_) if self.is_new_selection => {
- selection!(&mut d).count = ShipCount::all();
-
- SelectMode::None
- }
- _ => SelectMode::None,
- }
- }
- MouseEvent::Move(_, _) if self.select_mode.is_init() => {
- self.need_value_update = true;
- self.values_changed_once = false;
- self.mouse_pos = d.input_state.mouse_pos;
- self.select_mode = SelectMode::Simple(0.0);
- }
- MouseEvent::Move(_, _) if self.select_mode.is_active() => {
- self.need_value_update = true;
- self.mouse_pos = d.input_state.mouse_pos;
- }
- _ => (),
- }
- }
- }
-
- fn update(&mut self, d: &FleetSelectData<'_>) {
- if !self.select_mode.is_active() {
- return;
- }
-
- if !take(&mut self.need_value_update) {
- return;
- }
-
- /* calculate values */
- let selection = selection!(&d);
- let orbit_owned = orbit_owned!(&d, selection.fleet);
- let position = position!(&d, orbit_owned.owner());
- let fleet = fleet!(&d, selection.fleet);
-
- self.marker = d.camera.view_to_world(self.mouse_pos) - position.pos();
- self.zoom = d.camera.view().axis_x.as_vec3().length();
- self.shape_size = position.shape().radius();
- self.ring0 = self.shape_size + FLEET_SELECT_OFFSET / self.zoom;
- self.ring1 = self.ring0 + FLEET_SELECT_WIDTH / self.zoom;
-
- let is_simple = self.select_mode.is_simple();
- let angle = self
- .marker
- .angle2(&VECTOR_2F_POS_X)
- .normalize()
- .into_deg()
- .into_inner();
- let sector = match angle {
- x if (135.0 >= x && x > 45.0) || is_simple => 3,
- x if 45.0 >= x && x > -45.0 => 0,
- x if -45.0 >= x && x > -135.0 => 1,
- _ => 2,
- };
-
- /* calulate ship value */
- let value = self.marker.length();
- if value > self.ring1 {
- self.values_changed_once = true;
-
- match sector {
- x @ 0..=2 => {
- let mut count = selection.count;
- count[x] = usize::MAX;
- self.set_count(count, fleet.count());
-
- self.values[x] = 1.0;
- self.update_values(fleet.count());
- }
- _ => {
- self.count = ShipCount::all();
- self.values = Vector4f::new(1.0, 1.0, 1.0, 1.0);
- }
- }
- } else if value > self.shape_size {
- let value = linear_step(self.ring0, self.ring1, value);
-
- self.values_changed_once = true;
-
- match sector {
- x @ 0..=2 => {
- let mut count = selection.count;
- count[x] = (fleet.count()[x] as f32 * value).round() as usize;
- self.set_count(count, fleet.count());
-
- self.values[x] = value;
- self.update_values(fleet.count());
- }
- _ => {
- self.count = *fleet.count() * value;
- self.values = value.into();
- }
- }
- } else if self.values_changed_once {
- match sector {
- x @ 0..=2 => {
- let mut count = selection.count;
- count[x] = 0;
- self.set_count(count, fleet.count());
-
- self.values[x] = 0.0;
- self.update_values(fleet.count());
- }
- _ => {
- self.count = Default::default();
- self.values = Default::default();
- }
- }
- }
-
- /* update texts */
- let c = self.count.merge(fleet.count());
- let _guard = self.cache.begin_update();
- self.text_total
- .update(0, c.total().to_string())
- .error("Unable to update text for ship count (total)");
- self.text_fighter
- .update(0, c.fighter.to_string())
- .error("Unable to update text for ship count (fighter)");
- self.text_bomber
- .update(0, c.bomber.to_string())
- .error("Unable to update text for ship count (bomber)");
- self.text_transporter
- .update(0, c.transporter.to_string())
- .error("Unable to update text for ship count (transporter)");
- }
-
- fn progress(&mut self, delta: f32) -> Option<f32> {
- match &mut self.select_mode {
- SelectMode::Init(detail_timeout) if *detail_timeout < Instant::now() => {
- self.need_value_update = true;
- self.values_changed_once = false;
- self.select_mode = SelectMode::Detail(0.0);
-
- Some(0.0)
- }
- SelectMode::Simple(progress) => {
- *progress += delta / FLEET_SELECT_ANIMATION_TIME;
- *progress = clamp(0.0, 1.0, *progress);
-
- Some(*progress)
- }
- SelectMode::SimpleClose(progress) => {
- *progress += delta / FLEET_SELECT_ANIMATION_TIME;
-
- if *progress > 2.0 {
- self.select_mode = SelectMode::None;
-
- Some(2.0)
- } else {
- Some(*progress)
- }
- }
- SelectMode::Detail(progress) => {
- *progress += delta / FLEET_SELECT_ANIMATION_TIME;
- *progress = clamp(0.0, 1.0, *progress);
-
- Some(*progress)
- }
- SelectMode::DetailClose(progress) => {
- *progress += delta / FLEET_SELECT_ANIMATION_TIME;
-
- if *progress > 2.0 {
- self.select_mode = SelectMode::None;
-
- Some(2.0)
- } else {
- Some(*progress)
- }
- }
- _ => None,
- }
- }
-
- fn render(&mut self, progress: f32, d: &FleetSelectData<'_>) {
- /* select program */
- let is_simple = self.select_mode.is_simple();
- let program = if is_simple {
- &self.program_simple
- } else {
- &self.program_detail
- };
-
- /* extract system data */
- let selection = selection!(&d);
- let orbit_owned = orbit_owned!(&d, selection.fleet);
- let position = position!(&d, orbit_owned.owner());
-
- /* calculate shared values */
- let size = self.ring1 + 50.0;
- let rings = Vector2f::new(self.ring0 / size, self.ring1 / size);
- let px = position.pos().x;
- let py = position.pos().y;
- let m = Matrix4f::new(
- Vector4f::new(size, 0.0, 0.0, 0.0),
- Vector4f::new(0.0, size, 0.0, 0.0),
- Vector4f::new(0.0, 0.0, size, 0.0),
- Vector4f::new(px, py, 0.0, 1.0),
- );
-
- /* update uniforms */
- let guard = BindGuard::new(&program);
- program
- .uniform(0, Uniform::Matrix4f(&m))
- .error("Error while updating model matrix");
- program
- .uniform(1, Uniform::Vector2f(&rings))
- .error("Error while updating rings");
- program
- .uniform(2, Uniform::Vector2f(&self.marker))
- .error("Error while updating marker");
- program
- .uniform(3, Uniform::Vector4f(&self.values))
- .error("Error while updating value");
- program
- .uniform(4, Uniform::Float(progress))
- .error("Error while updating progress");
- program
- .uniform(5, Uniform::Float(self.shape_size / size))
- .error("Error while updating size");
-
- /* render selection menu */
- d.geometry.render_quad();
- drop(guard);
-
- /* render text */
- let alpha = if progress <= 1.0 {
- progress
- } else {
- 2.0 - progress
- };
-
- if is_simple {
- self.text_total
- .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
- .render_offset(&self.text_pos(self.marker, *position.pos(), d));
- } else {
- self.text_total
- .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
- .render_offset(&self.text_pos((0.0, 1.0), *position.pos(), d));
- self.text_fighter
- .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
- .render_offset(&self.text_pos((1.0, 0.0), *position.pos(), d));
- self.text_bomber
- .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
- .render_offset(&self.text_pos((0.0, -1.0), *position.pos(), d));
- self.text_transporter
- .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
- .render_offset(&self.text_pos((-1.0, 0.0), *position.pos(), d));
- }
- }
-
- fn set_count(&mut self, count: ShipCount, fleet_count: &ShipCount) {
- let c = count.merge(fleet_count);
- let f = &fleet_count;
-
- self.count = count;
- self.values = Vector4f::new(
- clamp(0.0, 1.0, c.fighter as f32 / f.fighter as f32),
- clamp(0.0, 1.0, c.bomber as f32 / f.bomber as f32),
- clamp(0.0, 1.0, c.transporter as f32 / f.transporter as f32),
- clamp(0.0, 1.0, c.total() as f32 / f.total() as f32),
- );
- }
-
- fn update_values(&mut self, fleet_count: &ShipCount) {
- let total_max = fleet_count.total() as f32;
-
- let sum = fleet_count[0] as f32 * self.values[0]
- + fleet_count[1] as f32 * self.values[1]
- + fleet_count[2] as f32 * self.values[2];
-
- self.values[3] = sum / total_max;
- }
-
- fn text_pos<T: Into<Vector2f>>(
- &self,
- dir: T,
- fleet_pos: Vector2f,
- d: &FleetSelectData<'_>,
- ) -> Vector2f {
- let text_offset = self.ring1 + FLEET_SELECT_TEXT_OFFSET / self.zoom.sqrt();
- let pos = fleet_pos + T::into(dir).normalize() * text_offset;
-
- d.camera.world_to_window(pos)
- }
- }
-
- impl<'a> System<'a> for FleetSelect {
- type SystemData = FleetSelectData<'a>;
-
- fn run(&mut self, mut data: Self::SystemData) {
- self.update_camera(&data);
- self.handle_events(&mut data);
- self.update(&data);
-
- let progress = self.progress(data.global.delta);
-
- if let Some(progress) = progress {
- gl::enable(gl::BLEND);
- gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
-
- self.render(progress, &data);
-
- gl::disable(gl::BLEND);
- }
- }
- }
-
- impl SelectMode {
- pub fn is_init(&self) -> bool {
- matches!(self, Self::Init { .. })
- }
-
- pub fn is_simple(&self) -> bool {
- match self {
- Self::Simple { .. } => true,
- Self::SimpleClose { .. } => true,
- _ => false,
- }
- }
-
- pub fn is_active(&self) -> bool {
- match self {
- Self::Simple { .. } => true,
- Self::Detail { .. } => true,
- _ => false,
- }
- }
- }
-
- fn new_text(cache: &TextCache) -> Result<Text, Error> {
- cache
- .new_text()
- .scale(FLEET_SELECT_TEXT_SIZE)
- .font("resources/fonts/DroidSansMono.ttf")
- .color(1.0, 1.0, 1.0, 0.75)
- .nowrap()
- .vert_align(VerticalAlign::Center)
- .horz_align(HorizontalAlign::Center)
- .text("0")
- .build()
- }
|