nsys-gl-utils 0.11.3

OpenGL and graphics utilities
//! A rectangular viewport and associated 2D and 3D cameras.

use std;
use math_utils as math;
use glium;

use crate::{Camera2d, Camera3d};
use crate::render::params;

/// A viewport defined by a `glium::Rect` structure, with associated 2D and 3D
/// cameras
#[derive(Debug)]
pub struct Viewport {
  /// Describes the viewport position and size.
  ///
  /// `left` and `bottom` are the number of pixels between the left and bottom
  /// of the screen with the left and bottom borders of the viewport rectangle.
  rect     : glium::Rect,
  camera2d : Option <Camera2d>,
  camera3d : Option <Camera3d>
}

pub struct Builder {
  rect            : glium::Rect,
  /// Overrides `position_2d` and `zoom_2d`
  camera_2d       : bool,
  /// Overrides `orthographic_3d` and `pose_3d`
  camera_3d       : bool,
  /// Set 3D camera projection to orthographic with the given zoom
  orthographic_3d : Option <f32>,
  pose_3d         : Option <math::Pose3 <f32>>,
  position_2d     : Option <math::Point2 <f32>>,
  zoom_2d         : Option <f32>
}

impl Viewport {
  /// Create a new viewport with the given `Rect`
  ///
  /// # Panics
  ///
  /// Width or height are zero:
  ///
  /// ```should_panic
  /// # extern crate gl_utils;
  /// # extern crate math_utils as math;
  /// # extern crate glium;
  /// # fn main () {
  /// # use gl_utils::render::Viewport;
  /// let mut viewport = Viewport::new (glium::Rect {
  ///   left: 0, bottom: 0, width: 0, height: 0
  /// });
  /// # }
  /// ```
  ///
  /// Width or height are greater than `u16::MAX` (65535):
  ///
  /// ```should_panic
  /// # extern crate gl_utils;
  /// # extern crate math_utils as math;
  /// # extern crate glium;
  /// # fn main () {
  /// # use gl_utils::render::Viewport;
  /// let mut viewport = Viewport::new (glium::Rect {
  ///   left: 0, bottom: 0, width: 65536, height: 65536
  /// });
  /// # }
  /// ```
  pub fn new (rect : glium::Rect) -> Self {
    assert!(rect.width  <= std::u16::MAX as u32);
    assert!(rect.height <= std::u16::MAX as u32);
    Viewport {
      rect,
      camera2d: Some (Camera2d::new (rect.width as u16, rect.height as u16)),
      camera3d: Some (Camera3d::new (rect.width as u16, rect.height as u16))
    }
  }

  /// Also initializes `self.camera3d` with position and orientation
  pub fn with_pose_3d (
    rect : glium::Rect,
    pose : math::Pose3 <f32>
  ) -> Self {
    assert!(rect.width  <= std::u16::MAX as u32);
    assert!(rect.height <= std::u16::MAX as u32);
    Viewport {
      rect,
      camera2d: Some (Camera2d::new (rect.width as u16, rect.height as u16)),
      camera3d: Some (Camera3d::with_pose (
        rect.width as u16, rect.height as u16,
        pose
      ))
    }
  }

  pub fn rect (&self) -> &glium::Rect {
    &self.rect
  }
  pub fn camera2d (&self) -> Option <&Camera2d> {
    self.camera2d.as_ref()
  }
  pub fn camera3d (&self) -> Option <&Camera3d> {
    self.camera3d.as_ref()
  }

  /// Should be called when screen resolution changes.
  ///
  /// # Panics
  ///
  /// Width and height must be less than or equal to `u16::MAX` (65535):
  ///
  /// ```should_panic
  /// # extern crate gl_utils;
  /// # extern crate math_utils as math;
  /// # extern crate glium;
  /// # fn main () {
  /// # use gl_utils::render::Viewport;
  /// # let mut viewport = Viewport::new (glium::Rect {
  /// #   left: 0, bottom: 0, width: 320, height: 240
  /// # });
  /// viewport.set_rect (glium::Rect {
  ///   left:   0,       bottom: 0,
  ///   width:  100_000, height: 100_000 // panics
  /// });
  /// # }
  /// ```
  ///
  /// Width or height are zero:
  ///
  /// ```should_panic
  /// # extern crate gl_utils;
  /// # extern crate math_utils as math;
  /// # extern crate glium;
  /// # fn main () {
  /// # use gl_utils::render::Viewport;
  /// # let mut viewport = Viewport::new (glium::Rect {
  /// #   left: 0, bottom: 0, width: 320, height: 240
  /// # });
  /// viewport.set_rect (glium::Rect {
  ///   left:   0, bottom: 0,
  ///   width:  0, height: 0 // panics
  /// });
  /// # }
  /// ```
  pub fn set_rect (&mut self, rect : glium::Rect) {
    assert!(rect.width  <= std::u16::MAX as u32);
    assert!(rect.height <= std::u16::MAX as u32);
    self.rect = rect;
    self.camera2d.as_mut().map (|camera2d|
      camera2d.set_viewport_dimensions (rect.width as u16, rect.height as u16));
    self.camera3d.as_mut().map (|camera3d|
      camera3d.set_viewport_dimensions (rect.width as u16, rect.height as u16));
  }

  pub fn camera2d_set_position (&mut self, position : math::Point2 <f32>) {
    self.camera2d.as_mut().unwrap().set_position (position)
  }
  pub fn camera2d_set_zoom (&mut self, zoom : f32) {
    self.camera2d.as_mut().unwrap().set_zoom (zoom)
  }
  pub fn camera2d_move_local (&mut self, dx : f32, dy : f32) {
    self.camera2d.as_mut().unwrap().move_local (dx, dy)
  }
  pub fn camera2d_move_origin_to_bottom_left (&mut self) {
    self.camera2d.as_mut().unwrap().move_origin_to_bottom_left()
  }
  pub fn camera3d_set_position (&mut self, position : math::Point3 <f32>) {
    self.camera3d.as_mut().unwrap().set_position (position)
  }
  pub fn camera3d_set_orientation (&mut self,
    orientation : math::Rotation3 <f32>
  ) {
    self.camera3d.as_mut().unwrap().set_orientation (orientation)
  }
  pub fn camera3d_look_at (&mut self, target : math::Point3 <f32>) {
    self.camera3d.as_mut().unwrap().look_at (target)
  }
  pub fn camera3d_move_local_xy (&mut self, dx : f32, dy : f32, dz : f32) {
    self.camera3d.as_mut().unwrap().move_local_xy (dx, dy, dz)
  }
  pub fn camera3d_rotate (&mut self,
    dyaw : math::Rad <f32>, dpitch : math::Rad <f32>, droll : math::Rad <f32>
  ) {
    self.camera3d.as_mut().unwrap().rotate (dyaw, dpitch, droll)
  }
  pub fn camera3d_scale_fovy_or_zoom (&mut self, zoom : f32) {
    self.camera3d.as_mut().unwrap().scale_fovy_or_zoom (zoom)
  }
  /// Returns default glium draw parameters with the viewport set to the current
  /// rectangle
  #[allow(mismatched_lifetime_syntaxes)]
  pub fn draw_parameters (&self) -> glium::DrawParameters {
    glium::DrawParameters {
      viewport: Some (self.rect),
      .. Default::default()
    }
  }

  /// Draw parameters that will result in 'inverted' colors (used by tile
  /// renderer)
  #[allow(mismatched_lifetime_syntaxes)]
  pub fn draw_parameters_blend_invert (&self) -> glium::DrawParameters {
    glium::DrawParameters {
      //blend: params::BLEND_FUNC_NORMAL,
      // NOTE: inverse blending does not really have an effect when rendering
      // with 'Nearest' magnify filter below
      blend: params::BLEND_FUNC_INVERT_COLOR,
      .. self.draw_parameters()
    }
  }
}

impl Builder {
  #[inline]
  pub fn new (rect : glium::Rect) -> Self {
    Builder {
      rect,
      camera_2d:       true,
      camera_3d:       true,
      orthographic_3d: None,
      pose_3d:         None,
      position_2d:     None,
      zoom_2d:         None
    }
  }
  pub fn with_camera_2d (self, camera_2d : bool) -> Self {
    Builder { camera_2d, .. self }
  }
  pub fn with_camera_3d (self, camera_3d : bool) -> Self {
    Builder { camera_3d, .. self }
  }
  pub fn with_zoom_2d (self, zoom : f32) -> Self {
    Builder { zoom_2d: Some (zoom), .. self }
  }
  pub fn with_position_2d (self, position_2d : math::Point2 <f32>) -> Self {
    Builder { position_2d: Some (position_2d), .. self }
  }
  /// Changes the 3D camera projection from perspective (default) to orthographic
  pub fn orthographic_3d (self, zoom : f32) -> Self {
    Builder { orthographic_3d: Some (zoom), .. self }
  }
  pub fn with_pose_3d (self, pose_3d : math::Pose3 <f32>) -> Self {
    Builder { pose_3d: Some (pose_3d), .. self }
  }
  #[inline]
  pub fn build (self) -> Viewport {
    let mut viewport = if let Some (pose_3d) = self.pose_3d {
      Viewport::with_pose_3d (self.rect, pose_3d)
    } else {
      Viewport::new (self.rect)
    };
    if self.camera_2d {
      if let Some (position) = self.position_2d {
        viewport.camera2d.as_mut().unwrap().set_position (position);
      }
      if let Some (zoom) = self.zoom_2d {
        viewport.camera2d.as_mut().unwrap().set_zoom (zoom);
      }
    } else {
      let _ = viewport.camera2d.take();
    }
    if self.camera_3d {
      if let Some (zoom) = self.orthographic_3d {
        viewport.camera3d.as_mut().unwrap().to_orthographic (zoom);
      }
    } else {
      let _ = viewport.camera3d.take();
    }
    viewport
  }
}

#[cfg(test)]
mod tests {
  use super::*;
  use glium;

  #[test]
  fn viewport_matrices() {
    let viewport = Viewport::new (
      glium::Rect { left: 0, bottom: 0, width: 1600, height: 900 }
    );
    let (transform_view_matrix, projection_ortho_matrix) =
      viewport.camera2d().unwrap().view_ortho_mats();
    println!("transform view matrix:\n{:#?}", transform_view_matrix);
    println!("projection ortho matrix:\n{:#?}", projection_ortho_matrix);
  }
}