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