gooey/interface/view/
coordinates.rs

1//! Tile and pixel positions and dimensions.
2//!
3//! # Coordinate systems
4//!
5//! The interface works with two types of coordinates: tile and pixel.
6//!
7//! Tile coordinates are given in (row,column) pairs, while pixel coordinates
8//! are defined as (x,y) pairs, where $x$ is understood to be the horizontal
9//! component and $y$ the vertical component.
10//!
11//! Both coordinate systems are left-handed, however they are oriented
12//! differently:
13//!
14//! - The tile coordinate origin is taken to be the upper-left, with
15//!   rows increasing towards the bottom and columns increasing towards the
16//!   right.
17//! - The pixel coordinate origin is taken to be the bottom-left, with $x$
18//!   increasing towards the right and $y$ increasing towards the top.
19//!
20//! The main layout component is the view *`Canvas`* component which contains a
21//! coordinates field.
22//!
23//! ## Presentation backends
24//!
25//! Different presentation backends may have their own coordinate systems.
26//!
27//! **Curses**
28//!
29//! TODO
30//!
31//! **OpenGL**
32//!
33//! The OpenGL rendering backend supports both pixel-based and tile-based
34//! rendering.
35//!
36//! By default the 2D camera is centered with (0,0) in the center of the screen.
37//! In the presentation implementation, the camera is moved so that (0,0) is at
38//! the bottom-left of the screen.
39//!
40//! TODO: more
41
42use std;
43use derive_more::{From, TryInto};
44use lazy_static::lazy_static;
45
46// TODO: use tagged coordinates?
47
48use crate::math::Vector2;
49use crate::geometry::integer::Aabb2;
50
51pub use self::position::Position;
52pub use self::dimensions::Dimensions;
53
54lazy_static!{
55  pub (crate) static ref TILE_WH : [u32; 2] = {
56    let width  = std::env::var ("GOOEY_TILE_WIDTH").unwrap().parse().unwrap();
57    let height = std::env::var ("GOOEY_TILE_HEIGHT").unwrap().parse().unwrap();
58    [width, height]
59  };
60  pub (crate) static ref SCREEN_WH : std::sync::RwLock <[u32; 2]> =
61    std::sync::RwLock::new ([0, 0]);
62}
63
64#[derive(Clone, Copy, Debug, Eq, PartialEq, From, TryInto)]
65pub enum Coordinates {
66  Tile  (position::Tile,  dimensions::Tile),
67  Pixel (position::Pixel, dimensions::Pixel)
68}
69
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
71pub enum Kind {
72  Tile, Pixel
73}
74
75/// Convert a screen pixel (x, y) coordinate to a tile (row, column) coordinate
76pub fn pixel_to_tile (x : i32, y : i32) -> [i32; 2] {
77  let [tile_w,   tile_h]    = *TILE_WH;
78  let [_screen_w, screen_h] = *SCREEN_WH.read().unwrap();
79  let column = x / tile_w as i32;
80  let row    = (screen_h as i32 - y) / tile_h as i32;
81  [row, column]
82}
83
84/// Convert a screen pixel (x, y) AABB to tile (row, column) coordinate AABB
85pub fn pixel_to_tile_aabb (aabb : Aabb2 <i32>) -> Aabb2 <i32> {
86  let [min_x, min_y] = aabb.min().0.into_array();
87  let [max_x, max_y] = aabb.max().0.into_array();
88  let min = pixel_to_tile (min_x, max_y).into();
89  let max = pixel_to_tile (max_x, min_y).into();
90  Aabb2::with_minmax (min, max)
91}
92
93/// Convert a (row, column) coordinate to a screen pixel (x, y) coordinate
94pub fn tile_to_pixel (row : i32, column : i32) -> [i32; 2] {
95  let [tile_w,   tile_h]    = *TILE_WH;
96  let [_screen_w, screen_h] = *SCREEN_WH.read().unwrap();
97  let x = column * tile_w as i32;
98  let y = screen_h as i32 - row * tile_h as i32;
99  [x, y]
100}
101
102/// Convert a (row, column) AABB to a screen pixel (x, y) coordinate AABB
103pub fn tile_to_pixel_aabb (aabb : Aabb2 <i32>) -> Aabb2 <i32> {
104  let aabb = Aabb2::with_minmax (
105    *aabb.min(),
106    aabb.max() + Vector2::new (1, 1)
107  );
108  let [min_row, min_col] = aabb.min().0.into_array();
109  let [max_row, max_col] = aabb.max().0.into_array();
110  let min = tile_to_pixel (max_row, min_col).into();
111  let max = tile_to_pixel (min_row, max_col).into();
112  Aabb2::with_minmax (min, max)
113}
114
115impl Coordinates {
116  #[inline]
117  pub fn default_tile() -> Self {
118    (position::Tile::default(), dimensions::Tile::default()).into()
119  }
120  #[inline]
121  pub fn default_pixel() -> Self {
122    (position::Pixel::default(), dimensions::Pixel::default()).into()
123  }
124  pub fn tile_from_aabb (aabb : Aabb2 <i32>) -> Self {
125    let position   = (*aabb.min()).into();
126    let dimensions = ((*aabb.max() - aabb.min()) + Vector2::new (1, 1))
127      .numcast().unwrap().into();
128    Coordinates::Tile (position, dimensions)
129  }
130  pub fn pixel_from_aabb (aabb : Aabb2 <i32>) -> Self {
131    let position   = (*aabb.min()).into();
132    let dimensions = ((*aabb.max() - aabb.min()) + Vector2::new (1, 1))
133      .numcast().unwrap().into();
134    Coordinates::Pixel (position, dimensions)
135  }
136  #[inline]
137  pub fn kind (&self) -> Kind {
138    match self {
139      Coordinates::Tile  (_, _) => Kind::Tile,
140      Coordinates::Pixel (_, _) => Kind::Pixel
141    }
142  }
143  #[inline]
144  pub fn dimensions (&self) -> Dimensions {
145    match self {
146      Coordinates::Tile  (_, dimensions) => dimensions.clone().into(),
147      Coordinates::Pixel (_, dimensions) => dimensions.clone().into()
148    }
149  }
150  #[inline]
151  pub fn position (&self) -> Position {
152    match self {
153      Coordinates::Tile  (position, _) => position.clone().into(),
154      Coordinates::Pixel (position, _) => position.clone().into()
155    }
156  }
157  #[inline]
158  pub fn dimensions_horizontal (&self) -> u32 {
159    match self {
160      Coordinates::Tile  (_, dimensions) => dimensions.columns(),
161      Coordinates::Pixel (_, dimensions) => dimensions.width()
162    }
163  }
164  #[inline]
165  pub fn dimensions_vertical (&self) -> u32 {
166    match self {
167      Coordinates::Tile  (_, dimensions) => dimensions.rows(),
168      Coordinates::Pixel (_, dimensions) => dimensions.height()
169    }
170  }
171  #[inline]
172  pub fn position_horizontal (&self) -> i32 {
173    match self {
174      Coordinates::Tile  (position, _) => position.column(),
175      Coordinates::Pixel (position, _) => position.0.x
176    }
177  }
178  #[inline]
179  pub fn position_vertical (&self) -> i32 {
180    match self {
181      Coordinates::Tile  (position, _) => position.row(),
182      Coordinates::Pixel (position, _) => position.0.y
183    }
184  }
185  #[inline]
186  pub fn modify_dimensions_horizontal (&mut self, d : i32) {
187    match self {
188      Coordinates::Tile  (_, dimensions) =>
189        *dimensions.columns_mut() =
190          std::cmp::max (0, dimensions.columns() as i32 + d) as u32,
191      Coordinates::Pixel (_, dimensions) =>
192        dimensions.x = std::cmp::max (0, dimensions.x as i32 + d) as u32
193    }
194  }
195  #[inline]
196  pub fn modify_dimensions_vertical (&mut self, d : i32) {
197    match self {
198      Coordinates::Tile  (_, dimensions) =>
199        *dimensions.rows_mut() =
200          std::cmp::max (0, dimensions.rows() as i32 + d) as u32,
201      Coordinates::Pixel (_, dimensions) =>
202        dimensions.y = std::cmp::max (0, dimensions.y as i32 + d) as u32
203    }
204  }
205  #[inline]
206  pub fn modify_position_horizontal (&mut self, d : i32) {
207    match self {
208      Coordinates::Tile  (position, _) =>
209        *position.column_mut() = position.column() + d,
210      Coordinates::Pixel (position, _) => position.0.x = position.0.x + d
211    }
212  }
213  #[inline]
214  pub fn modify_position_vertical (&mut self, d : i32) {
215    match self {
216      Coordinates::Tile  (position, _) =>
217        *position.row_mut() = position.row() + d,
218      Coordinates::Pixel (position, _) => position.0.y = position.0.y + d
219    }
220  }
221  #[inline]
222  /// &#9888; Position types must match
223  pub fn set_position (&mut self, position : Position) {
224    match (self, position) {
225      (Coordinates::Tile  (position, _), Position::Tile  (p)) => *position = p,
226      (Coordinates::Pixel (position, _), Position::Pixel (p)) => *position = p,
227      _ => unreachable!()
228    }
229  }
230  #[inline]
231  /// &#9888; Dimensions types must match
232  pub fn set_dimensions (&mut self, dimensions : Dimensions) {
233    match (self, dimensions) {
234      (Coordinates::Tile  (_, dimensions), Dimensions::Tile  (d)) =>
235        *dimensions = d,
236      (Coordinates::Pixel (_, dimensions), Dimensions::Pixel (d)) =>
237        *dimensions = d,
238      _ => unreachable!()
239    }
240  }
241}
242
243impl From <Coordinates> for Aabb2 <i32> {
244  fn from (coordinates : Coordinates) -> Aabb2 <i32> {
245    log::trace!("aabb2 from coordinates: {:?}", coordinates);
246    // NOTE: we subtract (1, 1) below because integer aabbs are inclusive of
247    // their max endpoints; however if the dimensions are zero then we can't
248    // subtract so it will result in the same Aabb as when the dimensions are 1
249    let (min, max) = match coordinates {
250      Coordinates::Tile  (position, dimensions) => {
251        let mut max = *position + dimensions.numcast().unwrap();
252        if dimensions.x > 0 {
253          max.0.x -= 1;
254        }
255        if dimensions.y > 0 {
256          max.0.y -= 1;
257        }
258        (*position, max)
259      }
260      Coordinates::Pixel (position, dimensions) => {
261        let mut max = *position + dimensions.numcast().unwrap();
262        if dimensions.x > 0 {
263          max.0.x -= 1;
264        }
265        if dimensions.y > 0 {
266          max.0.y -= 1;
267        }
268        (*position, max)
269      }
270    };
271    log::trace!("aabb2 with min, max: {:?}", (min, max));
272    Aabb2::with_minmax (min, max)
273  }
274}
275
276impl std::convert::TryFrom <(Position, Dimensions)> for Coordinates {
277  type Error = (Position, Dimensions);
278  fn try_from ((position, dimensions) : (Position, Dimensions))
279    -> Result <Coordinates, (Position, Dimensions)>
280  {
281    let coordinates = match (position, dimensions) {
282      (Position::Tile  (position), Dimensions::Tile  (dimensions)) =>
283        Coordinates::Tile  (position, dimensions),
284      (Position::Pixel (position), Dimensions::Pixel (dimensions)) =>
285        Coordinates::Pixel (position, dimensions),
286      _ => return Err ((position, dimensions))
287    };
288    Ok (coordinates)
289  }
290}
291
292pub mod position {
293  use derive_more::{From, TryInto};
294  use crate::math::Point2;
295
296  #[derive(Clone, Copy, Debug, Eq, PartialEq, From, TryInto)]
297  pub enum Position {
298    Tile  (Tile),
299    Pixel (Pixel)
300  }
301
302  #[derive(Clone, Copy, Debug, Eq, PartialEq)]
303  pub struct Tile {
304    pub position_rc : Point2 <i32>
305  }
306
307  #[derive(Clone, Copy, Debug, Eq, PartialEq)]
308  pub struct Pixel {
309    pub position_xy : Point2 <i32>
310  }
311
312  // TODO: 3d coordinates?
313
314  impl Tile {
315    #[inline]
316    pub const fn origin() -> Self {
317      Tile {
318        position_rc: Point2::new (0, 0)
319      }
320    }
321    #[inline]
322    pub fn new_rc (row : i32, column : i32) -> Self {
323      Point2::new (row, column).into()
324    }
325    #[inline]
326    pub fn row (&self) -> i32 {
327      self.position_rc.0.x
328    }
329    #[inline]
330    pub fn column (&self) -> i32 {
331      self.position_rc.0.y
332    }
333    #[inline]
334    pub fn row_mut (&mut self) -> &mut i32 {
335      &mut self.position_rc.0.x
336    }
337    #[inline]
338    pub fn column_mut (&mut self) -> &mut i32 {
339      &mut self.position_rc.0.y
340    }
341  }
342
343  impl Default for Tile {
344    fn default() -> Self {
345      Tile { position_rc: [0,0].into() }
346    }
347  }
348
349  impl From <Point2 <i32>> for Tile {
350    fn from (position_rc : Point2 <i32>) -> Self {
351      Tile { position_rc }
352    }
353  }
354
355  impl std::ops::Deref for Tile {
356    type Target = Point2 <i32>;
357    fn deref (&self) -> &Point2 <i32> {
358      &self.position_rc
359    }
360  }
361
362  impl std::ops::DerefMut for Tile {
363    fn deref_mut (&mut self) -> &mut Point2 <i32> {
364      &mut self.position_rc
365    }
366  }
367
368  impl Pixel {
369    #[inline]
370    pub const fn origin() -> Self {
371      Pixel {
372        position_xy: Point2::new (0, 0)
373      }
374    }
375    #[inline]
376    pub fn new_xy (x : i32, y : i32) -> Self {
377      Point2::new (x, y).into()
378    }
379  }
380
381  impl Default for Pixel {
382    fn default() -> Self {
383      Pixel { position_xy: [0,0].into() }
384    }
385  }
386
387  impl From <Point2 <i32>> for Pixel {
388    fn from (position_xy : Point2 <i32>) -> Self {
389      Pixel { position_xy }
390    }
391  }
392
393  impl std::ops::Deref for Pixel {
394    type Target = Point2 <i32>;
395    fn deref (&self) -> &Point2 <i32> {
396      &self.position_xy
397    }
398  }
399
400  impl std::ops::DerefMut for Pixel {
401    fn deref_mut (&mut self) -> &mut Point2 <i32> {
402      &mut self.position_xy
403    }
404  }
405}
406
407pub mod dimensions {
408  use derive_more::{From, TryInto};
409  use crate::math::Vector2;
410
411  #[derive(Clone, Copy, Debug, Eq, PartialEq, From, TryInto)]
412  pub enum Dimensions {
413    Tile  (Tile),
414    Pixel (Pixel)
415  }
416
417  #[derive(Clone, Copy, Debug, Eq, PartialEq)]
418  pub struct Tile {
419    pub dimensions_rc : Vector2 <u32>
420  }
421
422  #[derive(Clone, Copy, Debug, Eq, PartialEq)]
423  pub struct Pixel {
424    pub dimensions_xy : Vector2 <u32>
425  }
426
427  // TODO: 3d dimensions?
428
429  impl Dimensions {
430    pub fn horizontal (&self) -> u32 {
431      match self {
432        Dimensions::Tile  (tile)  => tile.columns(),
433        Dimensions::Pixel (pixel) => pixel.width()
434      }
435    }
436
437    pub fn vertical (&self) -> u32 {
438      match self {
439        Dimensions::Tile  (tile)  => tile.rows(),
440        Dimensions::Pixel (pixel) => pixel.height()
441      }
442    }
443
444    pub fn vec (&self) -> Vector2 <u32> {
445      match self {
446        Dimensions::Tile  (tile)  => **tile,
447        Dimensions::Pixel (pixel) => **pixel
448      }
449    }
450  }
451
452  impl Tile {
453    #[inline]
454    pub fn new_rc (rows : u32, columns : u32) -> Self {
455      Vector2::new (rows, columns).into()
456    }
457    #[inline]
458    pub fn columns (&self) -> u32 {
459      self.dimensions_rc.y
460    }
461    #[inline]
462    pub fn rows (&self) -> u32 {
463      self.dimensions_rc.x
464    }
465    #[inline]
466    pub fn rows_mut (&mut self) -> &mut u32 {
467      &mut self.dimensions_rc.x
468    }
469    #[inline]
470    pub fn columns_mut (&mut self) -> &mut u32 {
471      &mut self.dimensions_rc.y
472    }
473    #[inline]
474    pub fn to_pixel (self) -> Pixel {
475      Vector2::new (self.columns(), self.rows()).into()
476    }
477  }
478
479  impl Pixel {
480    #[inline]
481    pub fn new_wh (width : u32, height : u32) -> Self {
482      Vector2::new (width, height).into()
483    }
484    #[inline]
485    pub fn width (&self) -> u32 {
486      self.dimensions_xy.x
487    }
488    #[inline]
489    pub fn height (&self) -> u32 {
490      self.dimensions_xy.y
491    }
492    #[inline]
493    pub fn to_tile (self) -> Tile {
494      Vector2::new (self.height(), self.width()).into()
495    }
496  }
497
498  impl Default for Tile {
499    fn default() -> Self {
500      Tile { dimensions_rc: [0,0].into() }
501    }
502  }
503
504  impl From <Vector2 <u32>> for Tile {
505    fn from (dimensions_rc : Vector2 <u32>) -> Self {
506      Tile { dimensions_rc }
507    }
508  }
509
510  impl std::ops::Deref for Tile {
511    type Target = Vector2 <u32>;
512    fn deref (&self) -> &Self::Target {
513      &self.dimensions_rc
514    }
515  }
516
517  impl std::ops::DerefMut for Tile {
518    fn deref_mut (&mut self) -> &mut Self::Target {
519      &mut self.dimensions_rc
520    }
521  }
522
523  impl Default for Pixel {
524    fn default() -> Self {
525      Pixel { dimensions_xy: [0,0].into() }
526    }
527  }
528
529  impl From <Vector2 <u32>> for Pixel {
530    fn from (dimensions_xy : Vector2 <u32>) -> Self {
531      Pixel { dimensions_xy }
532    }
533  }
534
535  impl std::ops::Deref for Pixel {
536    type Target = Vector2 <u32>;
537    fn deref (&self) -> &Self::Target {
538      &self.dimensions_xy
539    }
540  }
541
542  impl std::ops::DerefMut for Pixel {
543    fn deref_mut (&mut self) -> &mut Self::Target {
544      &mut self.dimensions_xy
545    }
546  }
547}
548
549impl std::ops::Add <dimensions::Tile> for position::Tile {
550  type Output = Self;
551  fn add (self, rhs : dimensions::Tile) -> Self {
552    (self.position_rc + rhs.dimensions_rc.numcast().unwrap()).into()
553  }
554}
555
556impl std::ops::Add <dimensions::Pixel> for position::Pixel {
557  type Output = Self;
558  fn add (self, rhs : dimensions::Pixel) -> Self {
559    (self.position_xy + rhs.dimensions_xy.numcast().unwrap()).into()
560  }
561}
562
563impl std::ops::Sub <dimensions::Tile> for position::Tile {
564  type Output = Self;
565  fn sub (self, rhs : dimensions::Tile) -> Self {
566    (self.position_rc - rhs.dimensions_rc.numcast().unwrap()).into()
567  }
568}
569
570impl std::ops::Sub <dimensions::Pixel> for position::Pixel {
571  type Output = Self;
572  fn sub (self, rhs : dimensions::Pixel) -> Self {
573    (self.position_xy - rhs.dimensions_xy.numcast().unwrap()).into()
574  }
575}
576
577#[cfg(test)]
578mod tests {
579  use crate::geometry::integer::Aabb2;
580  use super::*;
581  /// ```text
582  /// [0,0]
583  /// (tile)    [8,100]
584  ///       +---+---------------+
585  ///       |   |               |
586  /// [0,92]+---+               |
587  ///       |    [1,1](tile)    |
588  ///       |                   | 100
589  ///       |                   |
590  ///       |                   |
591  ///       |                   |
592  ///       |                   |
593  ///       +-------------------+
594  ///   [0,0]       100
595  /// (pixel)
596  /// ```
597  ///
598  /// Sets the screen to dimensions [100,100] and tile dimensions [8,8].
599  ///
600  /// Given tile coordinates with position [0,0] and dimensions [1,1], computes
601  /// an AABB in pixel coordinates which is min = [0,92] and max = [8,100].
602  #[test]
603  fn test_tile_to_pixel_aabb() {
604    unsafe {
605      std::env::set_var ("GOOEY_TILE_WIDTH",  "8");
606      std::env::set_var ("GOOEY_TILE_HEIGHT", "8");
607    }
608    *SCREEN_WH.write().unwrap() = [100, 100];
609    let coordinates = Coordinates::Tile (
610      position::Tile::new_rc (0, 0),
611      dimensions::Tile::new_rc (1, 1));
612    let aabb = Aabb2::from (coordinates);
613    assert!(aabb.contains (&[0, 0].into()));
614    assert!(!aabb.contains (&[1, 1].into()));
615    let aabb_pixel = tile_to_pixel_aabb (aabb);
616    assert_eq!(*aabb_pixel.min(), [0, 92].into());
617    assert_eq!(*aabb_pixel.max(), [8, 100].into());
618  }
619}