You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

587 lines
19 KiB

  1. use std::mem::take;
  2. use std::time::Instant;
  3. use glc::{
  4. math::{clamp, linear_step},
  5. matrix::Matrix4f,
  6. misc::BindGuard,
  7. shader::{Program, Type, Uniform},
  8. vector::{Vector2f, Vector4f},
  9. };
  10. use shrev::{EventChannel, ReaderId};
  11. use space_crush_common::{
  12. components::{Fleet, Orbit, OrbitOwned, Position, ShipCount},
  13. constants::VECTOR_2F_POS_X,
  14. continue_if_none,
  15. misc::{LogResult, WorldHelper as _},
  16. resources::Global,
  17. return_if_none,
  18. };
  19. use specs::{prelude::*, ReadExpect, ReadStorage, System, World};
  20. use crate::{
  21. constants::{
  22. FLEET_SELECT_ANIMATION_TIME, FLEET_SELECT_DETAIL_TIMEOUT, FLEET_SELECT_OFFSET,
  23. FLEET_SELECT_TEXT_OFFSET, FLEET_SELECT_TEXT_SIZE, FLEET_SELECT_WIDTH,
  24. UNIFORM_BUFFER_INDEX_CAMERA,
  25. },
  26. misc::{
  27. HorizontalAlign, MouseEvent, Text, TextCache, TextManager, VerticalAlign, WorldHelper as _,
  28. },
  29. resources::{Camera, Config, GameState, Geometry, InputState, Selection},
  30. Error,
  31. };
  32. pub struct FleetSelect {
  33. program_simple: Program,
  34. program_detail: Program,
  35. cache: TextCache,
  36. text_total: Text,
  37. text_fighter: Text,
  38. text_bomber: Text,
  39. text_transporter: Text,
  40. mouse_event_id: ReaderId<MouseEvent>,
  41. select_mode: SelectMode,
  42. camera_counter: usize,
  43. need_value_update: bool,
  44. values_changed_once: bool,
  45. is_new_selection: bool,
  46. mouse_pos: Vector2f,
  47. count: ShipCount,
  48. values: Vector4f,
  49. marker: Vector2f,
  50. shape_size: f32,
  51. ring0: f32,
  52. ring1: f32,
  53. zoom: f32,
  54. }
  55. #[derive(Copy, Clone, Debug)]
  56. enum SelectMode {
  57. None,
  58. Init(Instant),
  59. Simple(f32),
  60. Detail(f32),
  61. SimpleClose(f32),
  62. DetailClose(f32),
  63. }
  64. #[derive(SystemData)]
  65. pub struct FleetSelectData<'a> {
  66. game_state: WriteExpect<'a, GameState>,
  67. camera: ReadExpect<'a, Camera>,
  68. mouse_events: ReadExpect<'a, EventChannel<MouseEvent>>,
  69. input_state: ReadExpect<'a, InputState>,
  70. geometry: ReadExpect<'a, Geometry>,
  71. global: ReadExpect<'a, Global>,
  72. config: ReadExpect<'a, Config>,
  73. orbit_owned: ReadStorage<'a, OrbitOwned>,
  74. positions: ReadStorage<'a, Position>,
  75. orbits: ReadStorage<'a, Orbit>,
  76. fleets: ReadStorage<'a, Fleet>,
  77. }
  78. macro_rules! selection {
  79. (&$data:expr) => {
  80. return_if_none!($data.game_state.selection())
  81. };
  82. (&mut $data:expr) => {
  83. return_if_none!($data.game_state.selection_mut())
  84. };
  85. }
  86. macro_rules! fleet {
  87. (&$data:expr, $id:expr) => {
  88. return_if_none!($data.fleets.get($id))
  89. };
  90. }
  91. macro_rules! position {
  92. (&$data:expr, $id:expr) => {
  93. return_if_none!($data.positions.get($id))
  94. };
  95. }
  96. macro_rules! orbit_owned {
  97. (&$data:expr, $id:expr) => {
  98. return_if_none!($data.orbit_owned.get($id))
  99. };
  100. }
  101. impl FleetSelect {
  102. pub fn new(world: &World, text_manager: &TextManager) -> Result<Self, Error> {
  103. let program_simple = world.load_program(vec![
  104. (Type::Vertex, "resources/shader/fleet_select/vert.glsl"),
  105. (
  106. Type::Fragment,
  107. "resources/shader/fleet_select/simple_frag.glsl",
  108. ),
  109. ])?;
  110. program_simple.uniform_block_binding("Camera", UNIFORM_BUFFER_INDEX_CAMERA)?;
  111. let program_detail = world.load_program(vec![
  112. (Type::Vertex, "resources/shader/fleet_select/vert.glsl"),
  113. (
  114. Type::Fragment,
  115. "resources/shader/fleet_select/detail_frag.glsl",
  116. ),
  117. ])?;
  118. program_detail.uniform_block_binding("Camera", UNIFORM_BUFFER_INDEX_CAMERA)?;
  119. let cache = text_manager.create_cache()?;
  120. let text_total = new_text(&cache)?;
  121. let text_fighter = new_text(&cache)?;
  122. let text_bomber = new_text(&cache)?;
  123. let text_transporter = new_text(&cache)?;
  124. let select_mode = SelectMode::None;
  125. let mouse_event_id = world.register_event_reader::<MouseEvent>()?;
  126. Ok(Self {
  127. program_simple,
  128. program_detail,
  129. cache,
  130. text_total,
  131. text_fighter,
  132. text_bomber,
  133. text_transporter,
  134. mouse_event_id,
  135. select_mode,
  136. camera_counter: 0,
  137. need_value_update: true,
  138. values_changed_once: false,
  139. is_new_selection: true,
  140. mouse_pos: Default::default(),
  141. count: Default::default(),
  142. values: Default::default(),
  143. marker: Default::default(),
  144. shape_size: Default::default(),
  145. ring0: Default::default(),
  146. ring1: Default::default(),
  147. zoom: Default::default(),
  148. })
  149. }
  150. fn update_camera(&mut self, d: &FleetSelectData<'_>) {
  151. let camera_counter = d.camera.update_counter();
  152. if self.camera_counter != camera_counter {
  153. self.camera_counter = camera_counter;
  154. self.need_value_update = true;
  155. }
  156. }
  157. fn handle_events(&mut self, d: &mut FleetSelectData<'_>) {
  158. let events = d.mouse_events.read(&mut self.mouse_event_id);
  159. for event in events {
  160. match event {
  161. MouseEvent::ButtonDown(button) if button == &d.config.input.fleet_select_button => {
  162. let pos = d.camera.view_to_world(d.input_state.mouse_pos);
  163. let selection = d.game_state.selection_mut().take();
  164. for (position, orbit) in (&d.positions, &d.orbits).join() {
  165. let r = orbit.max() * orbit.max();
  166. if (position.pos() - pos).length_sqr() <= r {
  167. let player_index = d.game_state.player_index();
  168. let fleet_id = continue_if_none!(orbit.fleets().get(player_index));
  169. let fleet_id = *continue_if_none!(fleet_id);
  170. *d.game_state.selection_mut() = match selection {
  171. Some(s) if s.fleet == fleet_id => {
  172. self.is_new_selection = false;
  173. Some(s)
  174. }
  175. _ => {
  176. self.is_new_selection = true;
  177. Some(Selection {
  178. fleet: fleet_id,
  179. count: ShipCount::none(),
  180. })
  181. }
  182. };
  183. let selection = selection!(&d);
  184. let fleet = fleet!(&d, selection.fleet);
  185. let timeout = Instant::now() + FLEET_SELECT_DETAIL_TIMEOUT;
  186. self.mouse_pos = d.input_state.mouse_pos;
  187. self.select_mode = SelectMode::Init(timeout);
  188. self.values_changed_once = false;
  189. self.set_count(selection.count, &fleet.count());
  190. break;
  191. }
  192. }
  193. }
  194. MouseEvent::ButtonUp(button) if button == &d.config.input.fleet_select_button => {
  195. self.select_mode = match self.select_mode {
  196. SelectMode::Simple(progress) => {
  197. selection!(&mut d).count = self.count;
  198. self.mouse_pos = d.input_state.mouse_pos;
  199. SelectMode::SimpleClose(progress)
  200. }
  201. SelectMode::Detail(progress) => {
  202. selection!(&mut d).count = self.count;
  203. self.mouse_pos = d.input_state.mouse_pos;
  204. SelectMode::DetailClose(progress)
  205. }
  206. SelectMode::Init(_) if self.is_new_selection => {
  207. selection!(&mut d).count = ShipCount::all();
  208. SelectMode::None
  209. }
  210. _ => SelectMode::None,
  211. }
  212. }
  213. MouseEvent::Move(_, _) if self.select_mode.is_init() => {
  214. self.need_value_update = true;
  215. self.values_changed_once = false;
  216. self.mouse_pos = d.input_state.mouse_pos;
  217. self.select_mode = SelectMode::Simple(0.0);
  218. }
  219. MouseEvent::Move(_, _) if self.select_mode.is_active() => {
  220. self.need_value_update = true;
  221. self.mouse_pos = d.input_state.mouse_pos;
  222. }
  223. _ => (),
  224. }
  225. }
  226. }
  227. fn update(&mut self, d: &FleetSelectData<'_>) {
  228. if !self.select_mode.is_active() {
  229. return;
  230. }
  231. if !take(&mut self.need_value_update) {
  232. return;
  233. }
  234. /* calculate values */
  235. let selection = selection!(&d);
  236. let orbit_owned = orbit_owned!(&d, selection.fleet);
  237. let position = position!(&d, orbit_owned.owner());
  238. let fleet = fleet!(&d, selection.fleet);
  239. self.marker = d.camera.view_to_world(self.mouse_pos) - position.pos();
  240. self.zoom = d.camera.view().axis_x.as_vec3().length();
  241. self.shape_size = position.shape().radius();
  242. self.ring0 = self.shape_size + FLEET_SELECT_OFFSET / self.zoom;
  243. self.ring1 = self.ring0 + FLEET_SELECT_WIDTH / self.zoom;
  244. let is_simple = self.select_mode.is_simple();
  245. let angle = self
  246. .marker
  247. .angle2(&VECTOR_2F_POS_X)
  248. .normalize()
  249. .into_deg()
  250. .into_inner();
  251. let sector = match angle {
  252. x if (135.0 >= x && x > 45.0) || is_simple => 3,
  253. x if 45.0 >= x && x > -45.0 => 0,
  254. x if -45.0 >= x && x > -135.0 => 1,
  255. _ => 2,
  256. };
  257. /* calulate ship value */
  258. let value = self.marker.length();
  259. if value > self.ring1 {
  260. self.values_changed_once = true;
  261. match sector {
  262. x @ 0..=2 => {
  263. let mut count = selection.count;
  264. count[x] = usize::MAX;
  265. self.set_count(count, fleet.count());
  266. self.values[x] = 1.0;
  267. self.update_values(fleet.count());
  268. }
  269. _ => {
  270. self.count = ShipCount::all();
  271. self.values = Vector4f::new(1.0, 1.0, 1.0, 1.0);
  272. }
  273. }
  274. } else if value > self.shape_size {
  275. let value = linear_step(self.ring0, self.ring1, value);
  276. self.values_changed_once = true;
  277. match sector {
  278. x @ 0..=2 => {
  279. let mut count = selection.count;
  280. count[x] = (fleet.count()[x] as f32 * value).round() as usize;
  281. self.set_count(count, fleet.count());
  282. self.values[x] = value;
  283. self.update_values(fleet.count());
  284. }
  285. _ => {
  286. self.count = *fleet.count() * value;
  287. self.values = value.into();
  288. }
  289. }
  290. } else if self.values_changed_once {
  291. match sector {
  292. x @ 0..=2 => {
  293. let mut count = selection.count;
  294. count[x] = 0;
  295. self.set_count(count, fleet.count());
  296. self.values[x] = 0.0;
  297. self.update_values(fleet.count());
  298. }
  299. _ => {
  300. self.count = Default::default();
  301. self.values = Default::default();
  302. }
  303. }
  304. }
  305. /* update texts */
  306. let c = self.count.merge(fleet.count());
  307. let _guard = self.cache.begin_update();
  308. self.text_total
  309. .update(0, c.total().to_string())
  310. .error("Unable to update text for ship count (total)");
  311. self.text_fighter
  312. .update(0, c.fighter.to_string())
  313. .error("Unable to update text for ship count (fighter)");
  314. self.text_bomber
  315. .update(0, c.bomber.to_string())
  316. .error("Unable to update text for ship count (bomber)");
  317. self.text_transporter
  318. .update(0, c.transporter.to_string())
  319. .error("Unable to update text for ship count (transporter)");
  320. }
  321. fn progress(&mut self, delta: f32) -> Option<f32> {
  322. match &mut self.select_mode {
  323. SelectMode::Init(detail_timeout) if *detail_timeout < Instant::now() => {
  324. self.need_value_update = true;
  325. self.values_changed_once = false;
  326. self.select_mode = SelectMode::Detail(0.0);
  327. Some(0.0)
  328. }
  329. SelectMode::Simple(progress) => {
  330. *progress += delta / FLEET_SELECT_ANIMATION_TIME;
  331. *progress = clamp(0.0, 1.0, *progress);
  332. Some(*progress)
  333. }
  334. SelectMode::SimpleClose(progress) => {
  335. *progress += delta / FLEET_SELECT_ANIMATION_TIME;
  336. if *progress > 2.0 {
  337. self.select_mode = SelectMode::None;
  338. Some(2.0)
  339. } else {
  340. Some(*progress)
  341. }
  342. }
  343. SelectMode::Detail(progress) => {
  344. *progress += delta / FLEET_SELECT_ANIMATION_TIME;
  345. *progress = clamp(0.0, 1.0, *progress);
  346. Some(*progress)
  347. }
  348. SelectMode::DetailClose(progress) => {
  349. *progress += delta / FLEET_SELECT_ANIMATION_TIME;
  350. if *progress > 2.0 {
  351. self.select_mode = SelectMode::None;
  352. Some(2.0)
  353. } else {
  354. Some(*progress)
  355. }
  356. }
  357. _ => None,
  358. }
  359. }
  360. fn render(&mut self, progress: f32, d: &FleetSelectData<'_>) {
  361. /* select program */
  362. let is_simple = self.select_mode.is_simple();
  363. let program = if is_simple {
  364. &self.program_simple
  365. } else {
  366. &self.program_detail
  367. };
  368. /* extract system data */
  369. let selection = selection!(&d);
  370. let orbit_owned = orbit_owned!(&d, selection.fleet);
  371. let position = position!(&d, orbit_owned.owner());
  372. /* calculate shared values */
  373. let size = self.ring1 + 50.0;
  374. let rings = Vector2f::new(self.ring0 / size, self.ring1 / size);
  375. let px = position.pos().x;
  376. let py = position.pos().y;
  377. let m = Matrix4f::new(
  378. Vector4f::new(size, 0.0, 0.0, 0.0),
  379. Vector4f::new(0.0, size, 0.0, 0.0),
  380. Vector4f::new(0.0, 0.0, size, 0.0),
  381. Vector4f::new(px, py, 0.0, 1.0),
  382. );
  383. /* update uniforms */
  384. let guard = BindGuard::new(&program);
  385. program
  386. .uniform(0, Uniform::Matrix4f(&m))
  387. .error("Error while updating model matrix");
  388. program
  389. .uniform(1, Uniform::Vector2f(&rings))
  390. .error("Error while updating rings");
  391. program
  392. .uniform(2, Uniform::Vector2f(&self.marker))
  393. .error("Error while updating marker");
  394. program
  395. .uniform(3, Uniform::Vector4f(&self.values))
  396. .error("Error while updating value");
  397. program
  398. .uniform(4, Uniform::Float(progress))
  399. .error("Error while updating progress");
  400. program
  401. .uniform(5, Uniform::Float(self.shape_size / size))
  402. .error("Error while updating size");
  403. /* render selection menu */
  404. d.geometry.render_quad();
  405. drop(guard);
  406. /* render text */
  407. let alpha = if progress <= 1.0 {
  408. progress
  409. } else {
  410. 2.0 - progress
  411. };
  412. if is_simple {
  413. self.text_total
  414. .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
  415. .render_offset(&self.text_pos(self.marker, *position.pos(), d));
  416. } else {
  417. self.text_total
  418. .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
  419. .render_offset(&self.text_pos((0.0, 1.0), *position.pos(), d));
  420. self.text_fighter
  421. .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
  422. .render_offset(&self.text_pos((1.0, 0.0), *position.pos(), d));
  423. self.text_bomber
  424. .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
  425. .render_offset(&self.text_pos((0.0, -1.0), *position.pos(), d));
  426. self.text_transporter
  427. .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
  428. .render_offset(&self.text_pos((-1.0, 0.0), *position.pos(), d));
  429. }
  430. }
  431. fn set_count(&mut self, count: ShipCount, fleet_count: &ShipCount) {
  432. let c = count.merge(fleet_count);
  433. let f = &fleet_count;
  434. self.count = count;
  435. self.values = Vector4f::new(
  436. clamp(0.0, 1.0, c.fighter as f32 / f.fighter as f32),
  437. clamp(0.0, 1.0, c.bomber as f32 / f.bomber as f32),
  438. clamp(0.0, 1.0, c.transporter as f32 / f.transporter as f32),
  439. clamp(0.0, 1.0, c.total() as f32 / f.total() as f32),
  440. );
  441. }
  442. fn update_values(&mut self, fleet_count: &ShipCount) {
  443. let total_max = fleet_count.total() as f32;
  444. let sum = fleet_count[0] as f32 * self.values[0]
  445. + fleet_count[1] as f32 * self.values[1]
  446. + fleet_count[2] as f32 * self.values[2];
  447. self.values[3] = sum / total_max;
  448. }
  449. fn text_pos<T: Into<Vector2f>>(
  450. &self,
  451. dir: T,
  452. fleet_pos: Vector2f,
  453. d: &FleetSelectData<'_>,
  454. ) -> Vector2f {
  455. let text_offset = self.ring1 + FLEET_SELECT_TEXT_OFFSET / self.zoom.sqrt();
  456. let pos = fleet_pos + T::into(dir).normalize() * text_offset;
  457. d.camera.world_to_window(pos)
  458. }
  459. }
  460. impl<'a> System<'a> for FleetSelect {
  461. type SystemData = FleetSelectData<'a>;
  462. fn run(&mut self, mut data: Self::SystemData) {
  463. self.update_camera(&data);
  464. self.handle_events(&mut data);
  465. self.update(&data);
  466. let progress = self.progress(data.global.delta);
  467. if let Some(progress) = progress {
  468. gl::enable(gl::BLEND);
  469. gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
  470. self.render(progress, &data);
  471. gl::disable(gl::BLEND);
  472. }
  473. }
  474. }
  475. impl SelectMode {
  476. pub fn is_init(&self) -> bool {
  477. matches!(self, Self::Init { .. })
  478. }
  479. pub fn is_simple(&self) -> bool {
  480. match self {
  481. Self::Simple { .. } => true,
  482. Self::SimpleClose { .. } => true,
  483. _ => false,
  484. }
  485. }
  486. pub fn is_active(&self) -> bool {
  487. match self {
  488. Self::Simple { .. } => true,
  489. Self::Detail { .. } => true,
  490. _ => false,
  491. }
  492. }
  493. }
  494. fn new_text(cache: &TextCache) -> Result<Text, Error> {
  495. cache
  496. .new_text()
  497. .scale(FLEET_SELECT_TEXT_SIZE)
  498. .font("resources/fonts/DroidSansMono.ttf")
  499. .color(1.0, 1.0, 1.0, 0.75)
  500. .nowrap()
  501. .vert_align(VerticalAlign::Center)
  502. .horz_align(HorizontalAlign::Center)
  503. .text("0")
  504. .build()
  505. }