gl_utils/render/resource/
mod.rs

1//! A trait for resource types with arbitrary `draw` functionality and a
2//! default implementation that supports switching to a "quad viewport" mode.
3
4use std::iter::FromIterator;
5use std::ops::Deref;
6use std::sync::LazyLock;
7
8use glium;
9use image;
10use strum::{EnumCount, EnumIter, FromRepr};
11use vec_map::VecMap;
12use winit;
13
14use math_utils as math;
15
16use crate::{color, render, shader, texture, vertex, Render};
17
18pub mod draw2d;
19pub mod draw3d;
20#[cfg(feature="demo")]
21#[cfg_attr(docsrs, doc(cfg(feature="demo")))]
22pub mod demo;
23
24pub use self::draw2d::Draw2d;
25pub use self::draw3d::Draw3d;
26
27////////////////////////////////////////////////////////////////////////////////
28//  constants                                                                 //
29////////////////////////////////////////////////////////////////////////////////
30
31pub const MAIN_VIEWPORT        : usize = 0;
32pub const LOWER_RIGHT_VIEWPORT : usize = MAIN_VIEWPORT;
33pub const UPPER_LEFT_VIEWPORT  : usize = 1;
34pub const UPPER_RIGHT_VIEWPORT : usize = 2;
35pub const LOWER_LEFT_VIEWPORT  : usize = 3;
36pub const OVERLAY_VIEWPORT     : usize = 4;
37
38////////////////////////////////////////////////////////////////////////////////
39//  statics                                                                   //
40////////////////////////////////////////////////////////////////////////////////
41
42static DEFAULT_TEXTURES_16X16_BYTES : LazyLock <VecMap <&'static [u8]>> =
43  LazyLock::new (|| VecMap::from_iter ([
44    ( DefaultTexture16Id::Crosshair         as usize,
45      texture::CROSSHAIR_PNG_FILE_BYTES.as_slice()
46    ), (
47      DefaultTexture16Id::CrosshairInverse  as usize,
48      texture::CROSSHAIR_INVERSE_PNG_FILE_BYTES.as_slice()
49    )
50  ]));
51
52static DEFAULT_TEXTURES_POINTER_BYTES_OFFSETS :
53  LazyLock <VecMap <(&'static [u8], [i16; 2])>> = LazyLock::new (|| VecMap::from_iter ([
54    ( DefaultTexturePointerId::Hand as usize,
55      ( texture::POINTER_HAND_PNG_FILE_BYTES_OFFSET.0.as_slice(),
56        texture::POINTER_HAND_PNG_FILE_BYTES_OFFSET.1
57      )
58    )
59  ]));
60
61////////////////////////////////////////////////////////////////////////////////
62//  typedefs                                                                  //
63////////////////////////////////////////////////////////////////////////////////
64
65pub type PointerTextureIndexRepr = u16;
66
67////////////////////////////////////////////////////////////////////////////////
68//  traits                                                                    //
69////////////////////////////////////////////////////////////////////////////////
70
71/// Represents a generic Glium resource type for a renderer to use as a source
72/// of *drawing* (vertex rendering).
73///
74/// The resource should have all the shader programs, vertex buffers, textures,
75/// etc. necessary to make a `draw` call on a given `glium::Frame`.
76///
77/// The default frame functions will call the `draw_2d` and `draw_3d` methods,
78/// which do nothing by default.
79pub trait Resource {
80  fn new (glium_display : &glium::Display <glutin::surface::WindowSurface>)
81    -> Self;
82  fn init (_render : &mut Render <Self>) where Self : Sized
83    { /* default: do nothing */ }
84  /// Default replace self with `Self::new`. Can be overridden.
85  fn reset (render : &mut Render <Self>)  where Self : Sized {
86    render.resource = Self::new (&render.glium_display)
87  }
88  fn draw_2d (_render : &Render <Self>, _glium_frame : &mut glium::Frame) where
89    Self : Sized { /* default: do nothing */ }
90  fn draw_3d (_render : &Render <Self>, _glium_frame : &mut glium::Frame) where
91    Self : Sized { /* default: do nothing */ }
92}
93
94////////////////////////////////////////////////////////////////////////////////
95//  structs                                                                   //
96////////////////////////////////////////////////////////////////////////////////
97
98/// A default implementation of `Resource` containing the builtin shader programs and
99/// some pre-defined vertex sources.
100pub struct Default {
101  pub draw2d : Draw2d,
102  pub draw3d : Draw3d,
103  /// Textures of heterogeneous dimensions
104  pub textures_anysize        : VecMap <glium::texture::Texture2d>,
105  /// Custom pointer textures with offsets for non-centered cursors
106  pub textures_pointer : VecMap <(glium::texture::Texture2d, math::Vector2 <i16>)>,
107  /// 128x128 pixel tileset texture with 8x8 tile dimensions
108  pub tileset_128x128_texture : glium::texture::Texture2d,
109  /// 256x256 pixel tileset texture with 16x16 tile dimensions
110  pub tileset_256x256_texture : glium::texture::Texture2d,
111  // TODO: more tilesets?
112  shader_programs             : VecMap <glium::Program>,
113  /// Default 16x16 textures indexed by `DefaultTexture16Id`
114  default_textures_16x16      : glium::texture::Texture2dArray,
115  /// Texture array of 16x16 pixel general purpose textures
116  textures_16x16              : glium::texture::Texture2dArray,
117  /// Texture array of 64x64 pixel general purpose textures
118  textures_64x64              : glium::texture::Texture2dArray
119}
120
121////////////////////////////////////////////////////////////////////////////////
122//  enums                                                                     //
123////////////////////////////////////////////////////////////////////////////////
124
125#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
126  FromRepr)]
127#[repr(u16)]
128pub enum DefaultTexturePointerId {
129  Hand
130}
131
132#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
133  FromRepr)]
134#[repr(u16)]
135pub enum DefaultTexture16Id {
136  // for use with `BLEND_FUNC_NORMAL`
137  Crosshair,
138  // for use with `BLEND_FUNC_INVERT_COLOR`
139  CrosshairInverse
140}
141
142#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
143  FromRepr)]
144#[repr(u16)]
145#[derive(Default)]
146pub enum DefaultTilesetId {
147  // for use with `BLEND_FUNC_NORMAL`
148  #[default]
149  EasciiAcorn128,
150  EasciiAcorn256,
151  // for use with `BLEND_FUNC_INVERT_COLOR`
152  EasciiAcornInverse128,
153  EasciiAcornInverse256
154}
155
156////////////////////////////////////////////////////////////////////////////////
157//  impls                                                                     //
158////////////////////////////////////////////////////////////////////////////////
159
160impl Default {
161  /// Default debug grid vertices (3): [X plane (red), Y plane (green), Z plane
162  /// (blue)]
163  pub fn debug_grid_vertices() -> [vertex::Vert3dOrientationScaleColor; 3] {
164    use std::f32::consts::FRAC_PI_2;
165    const HALF_GRID_DIMS : f32 = 0.5 * draw3d::MESH_GRID_DIMS as f32;
166    [
167      // X plane
168      vertex::Vert3dOrientationScaleColor {
169        position:    [HALF_GRID_DIMS, 0.0, HALF_GRID_DIMS],
170        orientation: (*math::Rotation3::from_angle_y (math::Rad (FRAC_PI_2)))
171          .into_col_arrays(),
172        scale:       [1.0, 1.0, 1.0],
173        color:       color::rgba_u8_to_rgba_f32 ([255, 0, 0, 255])
174      },
175      // Y plane
176      vertex::Vert3dOrientationScaleColor {
177        position:    [0.0, HALF_GRID_DIMS, HALF_GRID_DIMS],
178        orientation: (*math::Rotation3::from_angle_x (math::Rad (FRAC_PI_2)))
179          .into_col_arrays(),
180        scale:       [1.0, 1.0, 1.0],
181        color:       color::rgba_u8_to_rgba_f32 ([0, 255, 0, 255])
182      },
183      // Z plane
184      vertex::Vert3dOrientationScaleColor {
185        position:    [0.0, 0.0, 0.0],
186        orientation: math::Matrix3::identity().into_col_arrays(),
187        scale:       [1.0, 1.0, 1.0],
188        color:       color::rgba_u8_to_rgba_f32 ([0, 0, 255, 255])
189      }
190    ]
191  }
192  /// Returns tile pixel [width, height] of the tileset.
193  #[inline]
194  pub fn tile_dimensions (&self, tileset_id : DefaultTilesetId) -> [u32; 2] {
195    let (width, height) = match tileset_id {
196      DefaultTilesetId::EasciiAcorn128 =>
197        self.tileset_128x128_texture.dimensions(),
198      DefaultTilesetId::EasciiAcorn256 =>
199        self.tileset_256x256_texture.dimensions(),
200      _ => unimplemented!()
201    };
202    debug_assert_eq!(width  % 16, 0);
203    debug_assert_eq!(height % 16, 0);
204    [width / 16, height / 16]
205  }
206  #[inline]
207  pub const fn shader_programs (&self) -> &VecMap <glium::Program> {
208    &self.shader_programs
209  }
210  #[inline]
211  pub fn set_pointer_position (&mut self,
212    display  : &glium::Display <glutin::surface::WindowSurface>,
213    position : math::Point2 <f32>
214  ) {
215    let offset = self.draw2d.draw_pointer.map_or (
216      math::Vector2::zero(),
217      |texture_index| self.textures_pointer.get (texture_index as usize)
218        .unwrap().1.numcast().unwrap()
219    );
220    self.draw2d.set_pointer_vertex (display, position + offset);
221  }
222  #[inline]
223  pub fn set_textures_16x16 (&mut self,
224    textures_16x16 : glium::texture::Texture2dArray
225  ) {
226    assert_eq!(textures_16x16.width(),  16);
227    assert_eq!(textures_16x16.height(), 16);
228    self.textures_16x16 = textures_16x16;
229  }
230  #[inline]
231  pub fn set_textures_64x64 (&mut self,
232    textures_64x64 : glium::texture::Texture2dArray
233  ) {
234    assert_eq!(textures_64x64.width(),  64);
235    assert_eq!(textures_64x64.height(), 64);
236    self.textures_64x64 = textures_64x64;
237  }
238}
239
240impl render::Resource for Default {
241  fn new (glium_display : &glium::Display <glutin::surface::WindowSurface>)
242    -> Self
243  {
244    // shaders
245    let shader_programs  = shader::build_programs (glium_display).unwrap();
246    // shared resources
247    let default_textures_16x16 =
248      texture::texture2darray_with_mipmaps_from_bytes (
249        glium_display,
250        &DEFAULT_TEXTURES_16X16_BYTES.values().map (Deref::deref)
251          .collect::<Vec <&[u8]>>(),
252        image::ImageFormat::Png,
253        glium::texture::MipmapsOption::NoMipmap
254      ).unwrap();
255    let textures_16x16   =
256      glium::texture::Texture2dArray::empty (glium_display, 16, 16, 0).unwrap();
257    let textures_64x64   =
258      glium::texture::Texture2dArray::empty (glium_display, 64, 64, 0).unwrap();
259    let textures_anysize = VecMap::new();
260    let textures_pointer = DEFAULT_TEXTURES_POINTER_BYTES_OFFSETS.iter()
261      .map (|(i, (bytes, offset))|{
262        let texture = texture::texture2d_with_mipmaps_from_bytes (
263          glium_display, bytes, image::ImageFormat::Png,
264          glium::texture::MipmapsOption::NoMipmap
265        ).unwrap();
266        (i, (texture, math::Vector2::from (*offset)))
267      }).collect();
268    let tileset_128x128_texture = texture::texture2d_with_mipmaps_from_bytes (
269      glium_display,
270      // for use with normal blending
271      texture::TILESET_EASCII_ACORN_8X8_PNG_FILE_BYTES,
272      image::ImageFormat::Png,
273      glium::texture::MipmapsOption::NoMipmap
274    ).unwrap();
275    let tileset_256x256_texture = texture::texture2d_with_mipmaps_from_bytes (
276      glium_display,
277      // for use with normal blending
278      texture::TILESET_EASCII_ACORN_16X16_PNG_FILE_BYTES,
279      image::ImageFormat::Png,
280      glium::texture::MipmapsOption::NoMipmap
281    ).unwrap();
282    // TODO
283    /*
284    let tileset_128x128_inverse_texture =
285      texture::texture2d_with_mipmaps_from_bytes (
286        glium_display,
287        // for use with inverse blending
288        texture::TILESET_EASCII_ACORN_8X8_INVERSE_PNG_FILE_BYTES,
289        image::PNG,
290        glium::texture::MipmapsOption::NoMipmap
291      ).unwrap();
292    */
293
294    let draw2d = Draw2d::new (glium_display);
295    let draw3d = Draw3d::new (glium_display);
296
297    Default {
298      shader_programs,
299      default_textures_16x16,
300      textures_16x16,
301      textures_64x64,
302      textures_anysize,
303      textures_pointer,
304      tileset_128x128_texture,
305      tileset_256x256_texture,
306      draw2d,
307      draw3d
308    }
309  }
310
311  #[inline]
312  fn init (render : &mut Render <Self>) {
313    render.update_viewport_line_loops();
314  }
315
316  #[inline]
317  fn draw_2d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
318    Draw2d::draw (render, glium_frame);
319  }
320
321  #[inline]
322  fn draw_3d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
323    Draw3d::draw (render, glium_frame);
324  } // end fn draw_3d
325
326  #[inline]
327  fn reset (render : &mut Render <Self>) {
328    render.resource.draw2d = Draw2d::new (&render.glium_display);
329    render.resource.draw3d = Draw3d::new (&render.glium_display);
330  }
331} // end impl Resource for DefaultResource
332
333impl Render <Default> {
334
335  #[inline]
336  pub fn camera3d_position_set (&mut self, position : math::Point3 <f32>) {
337    for (_, viewport) in self.viewports.iter_mut() {
338      if viewport.camera3d().is_some() {
339        viewport.camera3d_set_position (position);
340      }
341    }
342  }
343
344  #[inline]
345  pub fn camera3d_orientation_set (&mut self,
346    orientation : math::Rotation3 <f32>
347  ) {
348    for (_, viewport) in self.viewports.iter_mut() {
349      if viewport.camera3d().is_some() {
350        viewport.camera3d_set_orientation (orientation);
351      }
352    }
353  }
354
355  #[inline]
356  pub fn camera3d_look_at (&mut self, target : math::Point3 <f32>) {
357    for (_, viewport) in self.viewports.iter_mut() {
358      if viewport.camera3d().is_some() {
359        viewport.camera3d_look_at (target);
360      }
361    }
362  }
363
364  pub fn camera3d_move_local_xy (&mut self, dx : f32, dy : f32, dz : f32) {
365    self.viewports[MAIN_VIEWPORT].camera3d_move_local_xy (dx, dy, dz);
366    let position = self.viewports[MAIN_VIEWPORT].camera3d().unwrap().position();
367    for (_, viewport) in self.viewports.iter_mut().skip (1) {
368      if viewport.camera3d().is_some() {
369        viewport.camera3d_set_position (position);
370      }
371    }
372  }
373
374  #[inline]
375  pub fn camera3d_rotate (&mut self,
376    dyaw : math::Rad <f32>, dpitch : math::Rad <f32>, droll : math::Rad <f32>
377  ) {
378    self.viewports[MAIN_VIEWPORT].camera3d_rotate (dyaw, dpitch, droll);
379  }
380
381  /// Scales 3D zoom of viewports 1-3.
382  ///
383  /// Does nothing in single viewport mode.
384  ///
385  /// # Panics
386  ///
387  /// Panics if scale is negative.
388  pub fn camera3d_orthographic_zoom_scale (&mut self, scale : f32) {
389    assert!(0.0 < scale);
390    for (_, viewport) in self.viewports.iter_mut().skip(1) {
391      if viewport.camera3d().is_some() {
392        debug_assert!(viewport.camera3d().unwrap().projection()
393          .is_orthographic());
394        viewport.camera3d_scale_fovy_or_zoom (scale);
395      }
396    }
397  }
398
399  /// Scales 3D zoom of viewport 0 (the main viewport) only.
400  ///
401  /// # Panics
402  ///
403  /// Panics if scale is negative.
404  pub fn camera3d_perspective_fovy_scale (&mut self, scale : f32) {
405    assert!(0.0 < scale);
406    debug_assert!(self.viewports[MAIN_VIEWPORT].camera3d().unwrap().projection()
407      .is_perspective());
408    self.viewports[MAIN_VIEWPORT].camera3d_scale_fovy_or_zoom (scale);
409  }
410
411  pub fn camera2d_zoom_set (&mut self, zoom : f32) {
412    assert!(0.0 < zoom);
413    for (_, viewport) in self.viewports.iter_mut() {
414      if viewport.camera2d().is_some() {
415        viewport.camera2d_set_zoom (zoom);
416      }
417    }
418    self.update_viewport_line_loops();
419  }
420
421  /// Modifies the 2D zoom by the given amount for all viewports.
422  pub fn camera2d_zoom_shift (&mut self, shift : f32) {
423    let mut dirty = false;
424    for (_, viewport) in self.viewports.iter_mut() {
425      if viewport.camera2d().is_some() {
426        let zoom = viewport.camera2d().unwrap().zoom() + shift;
427        if 0.0 < zoom {
428          viewport.camera2d_set_zoom (zoom);
429          dirty = true;
430        }
431      }
432    }
433    if dirty {
434      self.update_viewport_line_loops();
435    }
436  }
437
438  pub fn camera2d_move_local (&mut self, dx : f32, dy : f32) {
439    self.viewports[MAIN_VIEWPORT].camera2d_move_local (dx, dy);
440    let position = self.viewports[MAIN_VIEWPORT].camera2d().unwrap().position();
441    for (_, viewport) in self.viewports.iter_mut().skip (1) {
442      if viewport.camera2d().is_some() {
443        viewport.camera2d_set_position (position);
444      }
445    }
446  }
447
448  /// Moves origin for all viewports to lower left
449  pub fn camera2d_move_origin_to_bottom_left (&mut self) {
450    for (_, viewport) in self.viewports.iter_mut() {
451      if viewport.camera2d().is_some() {
452        viewport.camera2d_move_origin_to_bottom_left()
453      }
454    }
455    self.update_viewport_line_loops();
456  }
457
458  /// Resize the viewport(s) based on the `LogicalSize` that was returned by a
459  /// `WindowEvent::Resized` event
460  // TODO: come up with a better scheme for quad viewports, overlay viewports
461  pub fn window_resized (&mut self,
462    physical_size : winit::dpi::PhysicalSize <u32>
463  ) {
464    let (width, height) = physical_size.into();
465    if self.viewports.len() < 4 {
466      self.viewports[MAIN_VIEWPORT].set_rect (
467        glium::Rect { left: 0, bottom: 0, width, height }
468      );
469      self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
470        viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
471    } else {
472      // quad viewports
473      let left_width    = left_width   (width);
474      let right_width   = right_width  (width);
475      let upper_height  = upper_height (height);
476      let lower_height  = lower_height (height);
477      self.viewports[LOWER_RIGHT_VIEWPORT].set_rect (
478        glium::Rect {
479          width:  right_width,
480          height: lower_height,
481          left:   left_width,
482          bottom: 0
483        }
484      );
485      self.viewports[UPPER_LEFT_VIEWPORT].set_rect (
486        glium::Rect {
487          width:  left_width,
488          height: upper_height,
489          left:   0,
490          bottom: lower_height
491        }
492      );
493      self.viewports[UPPER_RIGHT_VIEWPORT].set_rect (
494        glium::Rect {
495          width:  right_width,
496          height: upper_height,
497          left:   left_width,
498          bottom: lower_height
499        }
500      );
501      self.viewports[LOWER_LEFT_VIEWPORT].set_rect (
502        glium::Rect {
503          width:  left_width,
504          height: lower_height,
505          left:   0,
506          bottom: 0
507        }
508      );
509      self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
510        viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
511    }
512    self.update_viewport_line_loops();
513  }
514
515  /// Updates vertex and index buffers for viewport line loops.
516  ///
517  /// Should be called whenever viewport sizes or 2D camera zoom or position
518  /// changes.
519  fn update_viewport_line_loops (&mut self) {
520    const VERTS_PER_VIEWPORT : usize = 4;
521    let num_vertices = VERTS_PER_VIEWPORT * self.viewports.len();
522    let vertices = {
523      let mut vertices = Vec::<vertex::Vert2d>::with_capacity (num_vertices);
524      for (_, viewport) in self.viewports.iter() {
525        if let Some (camera2d) = viewport.camera2d() {
526          // pixels are centered on 0.5 unit centers
527          let pixel_radius = 0.5 / camera2d.zoom();
528          let position     = camera2d.position();
529          let ortho        = camera2d.ortho();
530          let left         = position.0.x + ortho.left;
531          let bottom       = position.0.y + ortho.bottom;
532          let top          = position.0.y + ortho.top;
533          let right        = position.0.x + ortho.right;
534          vertices.append (&mut vec![
535            vertex::Vert2d {
536              position: [left  + pixel_radius, bottom + pixel_radius] },
537            vertex::Vert2d {
538              position: [left  + pixel_radius, top    - pixel_radius] },
539            vertex::Vert2d {
540              position: [right - pixel_radius, top    - pixel_radius] },
541            vertex::Vert2d {
542              position: [right - pixel_radius, bottom + pixel_radius] }
543          ]);
544        }
545      }
546      vertices
547    };
548    self.resource.draw2d
549      .line_loop_vertices_set (&self.glium_display, &vertices[..]);
550  }
551}
552
553
554////////////////////////////////////////////////////////////////////////////////
555//  private functions                                                         //
556////////////////////////////////////////////////////////////////////////////////
557
558// some functions to compute viewport widths for odd window dimensions:
559// right viewports will be 1 pixel wider on odd width windows and bottom
560// viewports will be 1 pixel taller on odd height windows
561
562#[inline]
563const fn left_width (window_width : u32) -> u32 {
564  window_width / 2
565}
566#[inline]
567const fn right_width (window_width : u32) -> u32 {
568  window_width / 2 + window_width % 2
569}
570#[inline]
571const fn upper_height (window_height : u32) -> u32 {
572  window_height / 2
573}
574#[inline]
575const fn lower_height (window_height : u32) -> u32 {
576  window_height / 2 + window_height % 2
577}