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.

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