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)]
149pub enum DefaultTilesetId {
150  // for use with `BLEND_FUNC_NORMAL`
151  EasciiAcorn128,
152  EasciiAcorn256,
153  // for use with `BLEND_FUNC_INVERT_COLOR`
154  EasciiAcornInverse128,
155  EasciiAcornInverse256
156}
157
158////////////////////////////////////////////////////////////////////////////////
159//  impls                                                                     //
160////////////////////////////////////////////////////////////////////////////////
161
162impl Default {
163  /// Default debug grid vertices (3): [X plane (red), Y plane (green), Z plane
164  /// (blue)]
165  pub fn debug_grid_vertices() -> [vertex::Vert3dOrientationScaleColor; 3] {
166    use std::f32::consts::FRAC_PI_2;
167    const HALF_GRID_DIMS : f32 = 0.5 * draw3d::MESH_GRID_DIMS as f32;
168    [
169      // X plane
170      vertex::Vert3dOrientationScaleColor {
171        position:    [HALF_GRID_DIMS, 0.0, HALF_GRID_DIMS],
172        orientation: (*math::Rotation3::from_angle_y (math::Rad (FRAC_PI_2)))
173          .into_col_arrays(),
174        scale:       [1.0, 1.0, 1.0],
175        color:       color::rgba_u8_to_rgba_f32 ([255, 0, 0, 255])
176      },
177      // Y plane
178      vertex::Vert3dOrientationScaleColor {
179        position:    [0.0, HALF_GRID_DIMS, HALF_GRID_DIMS],
180        orientation: (*math::Rotation3::from_angle_x (math::Rad (FRAC_PI_2)))
181          .into_col_arrays(),
182        scale:       [1.0, 1.0, 1.0],
183        color:       color::rgba_u8_to_rgba_f32 ([0, 255, 0, 255])
184      },
185      // Z plane
186      vertex::Vert3dOrientationScaleColor {
187        position:    [0.0, 0.0, 0.0],
188        orientation: math::Matrix3::identity().into_col_arrays(),
189        scale:       [1.0, 1.0, 1.0],
190        color:       color::rgba_u8_to_rgba_f32 ([0, 0, 255, 255])
191      }
192    ]
193  }
194  /// Returns tile pixel [width, height] of the tileset.
195  #[inline]
196  pub fn tile_dimensions (&self, tileset_id : DefaultTilesetId) -> [u32; 2] {
197    let (width, height) = match tileset_id {
198      DefaultTilesetId::EasciiAcorn128 =>
199        self.tileset_128x128_texture.dimensions(),
200      DefaultTilesetId::EasciiAcorn256 =>
201        self.tileset_256x256_texture.dimensions(),
202      _ => unimplemented!()
203    };
204    debug_assert_eq!(width  % 16, 0);
205    debug_assert_eq!(height % 16, 0);
206    [width / 16, height / 16]
207  }
208  #[inline]
209  pub fn shader_programs (&self) -> &VecMap <glium::Program> {
210    &self.shader_programs
211  }
212  #[inline]
213  pub fn set_pointer_position (&mut self,
214    display  : &glium::Display <glutin::surface::WindowSurface>,
215    position : math::Point2 <f32>
216  ) {
217    let offset = self.draw2d.draw_pointer.clone().map_or (
218      math::Vector2::zero(),
219      |texture_index| self.textures_pointer.get (texture_index as usize)
220        .unwrap().1.numcast().unwrap()
221    );
222    self.draw2d.set_pointer_vertex (display, position + offset);
223  }
224  #[inline]
225  pub fn set_textures_16x16 (&mut self,
226    textures_16x16 : glium::texture::Texture2dArray
227  ) {
228    assert_eq!(textures_16x16.width(),  16);
229    assert_eq!(textures_16x16.height(), 16);
230    self.textures_16x16 = textures_16x16;
231  }
232  #[inline]
233  pub fn set_textures_64x64 (&mut self,
234    textures_64x64 : glium::texture::Texture2dArray
235  ) {
236    assert_eq!(textures_64x64.width(),  64);
237    assert_eq!(textures_64x64.height(), 64);
238    self.textures_64x64 = textures_64x64;
239  }
240}
241
242impl render::Resource for Default {
243  fn new (glium_display : &glium::Display <glutin::surface::WindowSurface>)
244    -> Self
245  {
246    // shaders
247    let shader_programs  = shader::build_programs (glium_display).unwrap();
248    // shared resources
249    let default_textures_16x16 =
250      texture::texture2darray_with_mipmaps_from_bytes (
251        glium_display,
252        &DEFAULT_TEXTURES_16X16_BYTES.values().map (Deref::deref)
253          .collect::<Vec <&[u8]>>(),
254        image::ImageFormat::Png,
255        glium::texture::MipmapsOption::NoMipmap
256      ).unwrap();
257    let textures_16x16   =
258      glium::texture::Texture2dArray::empty (glium_display, 16, 16, 0).unwrap();
259    let textures_64x64   =
260      glium::texture::Texture2dArray::empty (glium_display, 64, 64, 0).unwrap();
261    let textures_anysize = VecMap::new();
262    let textures_pointer = DEFAULT_TEXTURES_POINTER_BYTES_OFFSETS.iter()
263      .map (|(i, (bytes, offset))|{
264        let texture = texture::texture2d_with_mipmaps_from_bytes (
265          glium_display, bytes, image::ImageFormat::Png,
266          glium::texture::MipmapsOption::NoMipmap
267        ).unwrap();
268        (i, (texture, math::Vector2::from (*offset)))
269      }).collect();
270    let tileset_128x128_texture = texture::texture2d_with_mipmaps_from_bytes (
271      glium_display,
272      // for use with normal blending
273      texture::TILESET_EASCII_ACORN_8X8_PNG_FILE_BYTES,
274      image::ImageFormat::Png,
275      glium::texture::MipmapsOption::NoMipmap
276    ).unwrap();
277    let tileset_256x256_texture = texture::texture2d_with_mipmaps_from_bytes (
278      glium_display,
279      // for use with normal blending
280      texture::TILESET_EASCII_ACORN_16X16_PNG_FILE_BYTES,
281      image::ImageFormat::Png,
282      glium::texture::MipmapsOption::NoMipmap
283    ).unwrap();
284    // TODO
285    /*
286    let tileset_128x128_inverse_texture =
287      texture::texture2d_with_mipmaps_from_bytes (
288        glium_display,
289        // for use with inverse blending
290        texture::TILESET_EASCII_ACORN_8X8_INVERSE_PNG_FILE_BYTES,
291        image::PNG,
292        glium::texture::MipmapsOption::NoMipmap
293      ).unwrap();
294    */
295
296    let draw2d = Draw2d::new (glium_display);
297    let draw3d = Draw3d::new (glium_display);
298
299    Default {
300      shader_programs,
301      default_textures_16x16,
302      textures_16x16,
303      textures_64x64,
304      textures_anysize,
305      textures_pointer,
306      tileset_128x128_texture,
307      tileset_256x256_texture,
308      draw2d,
309      draw3d
310    }
311  }
312
313  #[inline]
314  fn init (render : &mut Render <Self>) {
315    render.update_viewport_line_loops();
316  }
317
318  #[inline]
319  fn draw_2d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
320    Draw2d::draw (render, glium_frame);
321  }
322
323  #[inline]
324  fn draw_3d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
325    Draw3d::draw (render, glium_frame);
326  } // end fn draw_3d
327
328  #[inline]
329  fn reset (render : &mut Render <Self>) {
330    render.resource.draw2d = Draw2d::new (&render.glium_display);
331    render.resource.draw3d = Draw3d::new (&render.glium_display);
332  }
333} // end impl Resource for DefaultResource
334
335impl Render <Default> {
336
337  #[inline]
338  pub fn camera3d_position_set (&mut self, position : math::Point3 <f32>) {
339    for (_, viewport) in self.viewports.iter_mut() {
340      if viewport.camera3d().is_some() {
341        viewport.camera3d_set_position (position);
342      }
343    }
344  }
345
346  #[inline]
347  pub fn camera3d_orientation_set (&mut self,
348    orientation : math::Rotation3 <f32>
349  ) {
350    for (_, viewport) in self.viewports.iter_mut() {
351      if viewport.camera3d().is_some() {
352        viewport.camera3d_set_orientation (orientation);
353      }
354    }
355  }
356
357  #[inline]
358  pub fn camera3d_look_at (&mut self, target : math::Point3 <f32>) {
359    for (_, viewport) in self.viewports.iter_mut() {
360      if viewport.camera3d().is_some() {
361        viewport.camera3d_look_at (target);
362      }
363    }
364  }
365
366  pub fn camera3d_move_local_xy (&mut self, dx : f32, dy : f32, dz : f32) {
367    self.viewports[MAIN_VIEWPORT].camera3d_move_local_xy (dx, dy, dz);
368    let position = self.viewports[MAIN_VIEWPORT].camera3d().unwrap().position();
369    for (_, viewport) in self.viewports.iter_mut().skip (1) {
370      if viewport.camera3d().is_some() {
371        viewport.camera3d_set_position (position);
372      }
373    }
374  }
375
376  #[inline]
377  pub fn camera3d_rotate (&mut self,
378    dyaw : math::Rad <f32>, dpitch : math::Rad <f32>, droll : math::Rad <f32>
379  ) {
380    self.viewports[MAIN_VIEWPORT].camera3d_rotate (dyaw, dpitch, droll);
381  }
382
383  /// Scales 3D zoom of viewports 1-3.
384  ///
385  /// Does nothing in single viewport mode.
386  ///
387  /// # Panics
388  ///
389  /// Panics if scale is negative.
390  pub fn camera3d_orthographic_zoom_scale (&mut self, scale : f32) {
391    assert!(0.0 < scale);
392    for (_, viewport) in self.viewports.iter_mut().skip(1) {
393      if viewport.camera3d().is_some() {
394        debug_assert!(viewport.camera3d().unwrap().projection()
395          .is_orthographic());
396        viewport.camera3d_scale_fovy_or_zoom (scale);
397      }
398    }
399  }
400
401  /// Scales 3D zoom of viewport 0 (the main viewport) only.
402  ///
403  /// # Panics
404  ///
405  /// Panics if scale is negative.
406  pub fn camera3d_perspective_fovy_scale (&mut self, scale : f32) {
407    assert!(0.0 < scale);
408    debug_assert!(self.viewports[MAIN_VIEWPORT].camera3d().unwrap().projection()
409      .is_perspective());
410    self.viewports[MAIN_VIEWPORT].camera3d_scale_fovy_or_zoom (scale);
411  }
412
413  pub fn camera2d_zoom_set (&mut self, zoom : f32) {
414    assert!(0.0 < zoom);
415    for (_, viewport) in self.viewports.iter_mut() {
416      if viewport.camera2d().is_some() {
417        viewport.camera2d_set_zoom (zoom);
418      }
419    }
420    self.update_viewport_line_loops();
421  }
422
423  /// Modifies the 2D zoom by the given amount for all viewports.
424  pub fn camera2d_zoom_shift (&mut self, shift : f32) {
425    let mut dirty = false;
426    for (_, viewport) in self.viewports.iter_mut() {
427      if viewport.camera2d().is_some() {
428        let zoom = viewport.camera2d().unwrap().zoom() + shift;
429        if 0.0 < zoom {
430          viewport.camera2d_set_zoom (zoom);
431          dirty = true;
432        }
433      }
434    }
435    if dirty {
436      self.update_viewport_line_loops();
437    }
438  }
439
440  pub fn camera2d_move_local (&mut self, dx : f32, dy : f32) {
441    self.viewports[MAIN_VIEWPORT].camera2d_move_local (dx, dy);
442    let position = self.viewports[MAIN_VIEWPORT].camera2d().unwrap().position();
443    for (_, viewport) in self.viewports.iter_mut().skip (1) {
444      if viewport.camera2d().is_some() {
445        viewport.camera2d_set_position (position);
446      }
447    }
448  }
449
450  /// Moves origin for all viewports to lower left
451  pub fn camera2d_move_origin_to_bottom_left (&mut self) {
452    for (_, viewport) in self.viewports.iter_mut() {
453      if viewport.camera2d().is_some() {
454        viewport.camera2d_move_origin_to_bottom_left()
455      }
456    }
457    self.update_viewport_line_loops();
458  }
459
460  /// Resize the viewport(s) based on the LogicalSize that was returned by a
461  /// WindowEvent::Resized event
462  // TODO: come up with a better scheme for quad viewports, overlay viewports
463  pub fn window_resized (&mut self,
464    physical_size : winit::dpi::PhysicalSize <u32>
465  ) {
466    let (width, height) = physical_size.into();
467    if self.viewports.len() < 4 {
468      self.viewports[MAIN_VIEWPORT].set_rect (
469        glium::Rect { left: 0, bottom: 0, width, height }
470      );
471      self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
472        viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
473    } else {
474      // quad viewports
475      let left_width    = left_width   (width);
476      let right_width   = right_width  (width);
477      let upper_height  = upper_height (height);
478      let lower_height  = lower_height (height);
479      self.viewports[LOWER_RIGHT_VIEWPORT].set_rect (
480        glium::Rect {
481          width:  right_width,
482          height: lower_height,
483          left:   left_width,
484          bottom: 0
485        }
486      );
487      self.viewports[UPPER_LEFT_VIEWPORT].set_rect (
488        glium::Rect {
489          width:  left_width,
490          height: upper_height,
491          left:   0,
492          bottom: lower_height
493        }
494      );
495      self.viewports[UPPER_RIGHT_VIEWPORT].set_rect (
496        glium::Rect {
497          width:  right_width,
498          height: upper_height,
499          left:   left_width,
500          bottom: lower_height
501        }
502      );
503      self.viewports[LOWER_LEFT_VIEWPORT].set_rect (
504        glium::Rect {
505          width:  left_width,
506          height: lower_height,
507          left:   0,
508          bottom: 0
509        }
510      );
511      self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
512        viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
513    }
514    self.update_viewport_line_loops();
515  }
516
517  /// Updates vertex and index buffers for viewport line loops.
518  ///
519  /// Should be called whenever viewport sizes or 2D camera zoom or position
520  /// changes.
521  fn update_viewport_line_loops (&mut self) {
522    const VERTS_PER_VIEWPORT : usize = 4;
523    let num_vertices = VERTS_PER_VIEWPORT * self.viewports.len();
524    let vertices = {
525      let mut vertices = Vec::<vertex::Vert2d>::with_capacity (num_vertices);
526      for (_, viewport) in self.viewports.iter() {
527        if let Some (camera2d) = viewport.camera2d() {
528          // pixels are centered on 0.5 unit centers
529          let pixel_radius = 0.5 / camera2d.zoom();
530          let position     = camera2d.position();
531          let ortho        = camera2d.ortho();
532          let left         = position.0.x + ortho.left;
533          let bottom       = position.0.y + ortho.bottom;
534          let top          = position.0.y + ortho.top;
535          let right        = position.0.x + ortho.right;
536          vertices.append (&mut vec![
537            vertex::Vert2d {
538              position: [left  + pixel_radius, bottom + pixel_radius] },
539            vertex::Vert2d {
540              position: [left  + pixel_radius, top    - pixel_radius] },
541            vertex::Vert2d {
542              position: [right - pixel_radius, top    - pixel_radius] },
543            vertex::Vert2d {
544              position: [right - pixel_radius, bottom + pixel_radius] }
545          ]);
546        }
547      }
548      vertices
549    };
550    self.resource.draw2d
551      .line_loop_vertices_set (&self.glium_display, &vertices[..]);
552  }
553}
554
555impl std::default::Default for DefaultTilesetId {
556  fn default() -> Self {
557    DefaultTilesetId::EasciiAcorn128
558  }
559}
560
561////////////////////////////////////////////////////////////////////////////////
562//  private functions                                                         //
563////////////////////////////////////////////////////////////////////////////////
564
565// some functions to compute viewport widths for odd window dimensions:
566// right viewports will be 1 pixel wider on odd width windows and bottom
567// viewports will be 1 pixel taller on odd height windows
568
569#[inline]
570fn left_width (window_width : u32) -> u32 {
571  window_width / 2
572}
573#[inline]
574fn right_width (window_width : u32) -> u32 {
575  window_width / 2 + window_width % 2
576}
577#[inline]
578fn upper_height (window_height : u32) -> u32 {
579  window_height / 2
580}
581#[inline]
582fn lower_height (window_height : u32) -> u32 {
583  window_height / 2 + window_height % 2
584}