use std::iter::FromIterator;
use std::ops::Deref;
use glium;
use image;
use lazy_static::lazy_static;
use strum::{EnumCount, EnumIter, FromRepr};
use vec_map::VecMap;
use winit;
use math_utils as math;
use crate::{color, render, shader, texture, vertex, Render};
pub mod draw2d;
pub mod draw3d;
#[cfg(feature="demo")]
pub mod demo;
pub use self::draw2d::Draw2d;
pub use self::draw3d::Draw3d;
pub const MAIN_VIEWPORT : usize = 0;
#[allow(dead_code)]
pub const LOWER_RIGHT_VIEWPORT : usize = MAIN_VIEWPORT;
pub const UPPER_LEFT_VIEWPORT : usize = 1;
pub const UPPER_RIGHT_VIEWPORT : usize = 2;
pub const LOWER_LEFT_VIEWPORT : usize = 3;
pub const OVERLAY_VIEWPORT : usize = 4;
lazy_static!{
static ref DEFAULT_TEXTURES_16X16_BYTES : VecMap <&'static [u8]> =
VecMap::from_iter ([
( DefaultTexture16Id::Crosshair as usize,
texture::CROSSHAIR_PNG_FILE_BYTES.as_slice()
), (
DefaultTexture16Id::CrosshairInverse as usize,
texture::CROSSHAIR_INVERSE_PNG_FILE_BYTES.as_slice()
)
]);
static ref DEFAULT_TEXTURES_POINTER_BYTES_OFFSETS :
VecMap <(&'static [u8], [i16; 2])> = VecMap::from_iter ([
( DefaultTexturePointerId::Hand as usize,
( texture::POINTER_HAND_PNG_FILE_BYTES_OFFSET.0.as_slice(),
texture::POINTER_HAND_PNG_FILE_BYTES_OFFSET.1
)
)
]);
}
pub type PointerTextureIndexRepr = u16;
pub trait Resource {
fn new (glium_display : &glium::Display <glutin::surface::WindowSurface>)
-> Self;
fn init (_render : &mut Render <Self>) where Self : Sized
{ }
fn reset (render : &mut Render <Self>) where Self : Sized {
render.resource = Self::new (&render.glium_display)
}
fn draw_2d (_render : &Render <Self>, _glium_frame : &mut glium::Frame) where
Self : Sized { }
fn draw_3d (_render : &Render <Self>, _glium_frame : &mut glium::Frame) where
Self : Sized { }
}
pub struct Default {
pub draw2d : Draw2d,
pub draw3d : Draw3d,
pub textures_anysize : VecMap <glium::texture::Texture2d>,
pub textures_pointer :
VecMap <(glium::texture::Texture2d, math::Vector2 <i16>)>,
pub tileset_128x128_texture : glium::texture::Texture2d,
pub tileset_256x256_texture : glium::texture::Texture2d,
shader_programs : VecMap <glium::Program>,
default_textures_16x16 : glium::texture::Texture2dArray,
textures_16x16 : glium::texture::Texture2dArray,
textures_64x64 : glium::texture::Texture2dArray
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
FromRepr)]
#[repr(u16)]
pub enum DefaultTexturePointerId {
Hand
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
FromRepr)]
#[repr(u16)]
pub enum DefaultTexture16Id {
Crosshair,
CrosshairInverse
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
FromRepr)]
#[repr(u16)]
pub enum DefaultTilesetId {
EasciiAcorn128,
EasciiAcorn256,
EasciiAcornInverse128,
EasciiAcornInverse256
}
impl Default {
pub fn debug_grid_vertices() -> [vertex::Vert3dOrientationScaleColor; 3] {
use std::f32::consts::FRAC_PI_2;
const HALF_GRID_DIMS : f32 = 0.5 * draw3d::MESH_GRID_DIMS as f32;
[
vertex::Vert3dOrientationScaleColor {
position: [HALF_GRID_DIMS, 0.0, HALF_GRID_DIMS],
orientation: (*math::Rotation3::from_angle_y (math::Rad (FRAC_PI_2)))
.into_col_arrays(),
scale: [1.0, 1.0, 1.0],
color: color::rgba_u8_to_rgba_f32 ([255, 0, 0, 255])
},
vertex::Vert3dOrientationScaleColor {
position: [0.0, HALF_GRID_DIMS, HALF_GRID_DIMS],
orientation: (*math::Rotation3::from_angle_x (math::Rad (FRAC_PI_2)))
.into_col_arrays(),
scale: [1.0, 1.0, 1.0],
color: color::rgba_u8_to_rgba_f32 ([0, 255, 0, 255])
},
vertex::Vert3dOrientationScaleColor {
position: [0.0, 0.0, 0.0],
orientation: math::Matrix3::identity().into_col_arrays(),
scale: [1.0, 1.0, 1.0],
color: color::rgba_u8_to_rgba_f32 ([0, 0, 255, 255])
}
]
}
#[inline]
pub fn tile_dimensions (&self, tileset_id : DefaultTilesetId) -> [u32; 2] {
let (width, height) = match tileset_id {
DefaultTilesetId::EasciiAcorn128 =>
self.tileset_128x128_texture.dimensions(),
DefaultTilesetId::EasciiAcorn256 =>
self.tileset_256x256_texture.dimensions(),
_ => unimplemented!()
};
debug_assert_eq!(width % 16, 0);
debug_assert_eq!(height % 16, 0);
[width / 16, height / 16]
}
#[inline]
pub fn shader_programs (&self) -> &VecMap <glium::Program> {
&self.shader_programs
}
#[inline]
pub fn set_pointer_position (&mut self,
display : &glium::Display <glutin::surface::WindowSurface>,
position : math::Point2 <f32>
) {
let offset = self.draw2d.draw_pointer.clone().map_or (
math::Vector2::zero(),
|texture_index| self.textures_pointer.get (texture_index as usize)
.unwrap().1.numcast().unwrap()
);
self.draw2d.set_pointer_vertex (display, position + offset);
}
#[inline]
pub fn set_textures_16x16 (&mut self,
textures_16x16 : glium::texture::Texture2dArray
) {
assert_eq!(textures_16x16.width(), 16);
assert_eq!(textures_16x16.height(), 16);
self.textures_16x16 = textures_16x16;
}
#[inline]
pub fn set_textures_64x64 (&mut self,
textures_64x64 : glium::texture::Texture2dArray
) {
assert_eq!(textures_64x64.width(), 64);
assert_eq!(textures_64x64.height(), 64);
self.textures_64x64 = textures_64x64;
}
}
impl render::Resource for Default {
fn new (glium_display : &glium::Display <glutin::surface::WindowSurface>)
-> Self
{
let shader_programs = shader::build_programs (glium_display).unwrap();
let default_textures_16x16 =
texture::texture2darray_with_mipmaps_from_bytes (
glium_display,
&DEFAULT_TEXTURES_16X16_BYTES.values().map (Deref::deref)
.collect::<Vec <&[u8]>>(),
image::ImageFormat::Png,
glium::texture::MipmapsOption::NoMipmap
).unwrap();
let textures_16x16 =
glium::texture::Texture2dArray::empty (glium_display, 16, 16, 0).unwrap();
let textures_64x64 =
glium::texture::Texture2dArray::empty (glium_display, 64, 64, 0).unwrap();
let textures_anysize = VecMap::new();
let textures_pointer = DEFAULT_TEXTURES_POINTER_BYTES_OFFSETS.iter()
.map (|(i, (bytes, offset))|{
let texture = texture::texture2d_with_mipmaps_from_bytes (
glium_display, bytes, image::ImageFormat::Png,
glium::texture::MipmapsOption::NoMipmap
).unwrap();
(i, (texture, math::Vector2::from (*offset)))
}).collect();
let tileset_128x128_texture = texture::texture2d_with_mipmaps_from_bytes (
glium_display,
texture::TILESET_EASCII_ACORN_8X8_PNG_FILE_BYTES,
image::ImageFormat::Png,
glium::texture::MipmapsOption::NoMipmap
).unwrap();
let tileset_256x256_texture = texture::texture2d_with_mipmaps_from_bytes (
glium_display,
texture::TILESET_EASCII_ACORN_16X16_PNG_FILE_BYTES,
image::ImageFormat::Png,
glium::texture::MipmapsOption::NoMipmap
).unwrap();
let draw2d = Draw2d::new (glium_display);
let draw3d = Draw3d::new (glium_display);
Default {
shader_programs,
default_textures_16x16,
textures_16x16,
textures_64x64,
textures_anysize,
textures_pointer,
tileset_128x128_texture,
tileset_256x256_texture,
draw2d,
draw3d
}
}
#[inline]
fn init (render : &mut Render <Self>) {
render.update_viewport_line_loops();
}
#[inline]
fn draw_2d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
Draw2d::draw (render, glium_frame);
}
#[inline]
fn draw_3d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
Draw3d::draw (render, glium_frame);
}
#[inline]
fn reset (render : &mut Render <Self>) {
render.resource.draw2d = Draw2d::new (&render.glium_display);
render.resource.draw3d = Draw3d::new (&render.glium_display);
}
}
impl Render <Default> {
#[inline]
pub fn camera3d_position_set (&mut self, position : math::Point3 <f32>) {
for (_, viewport) in self.viewports.iter_mut() {
if viewport.camera3d().is_some() {
viewport.camera3d_set_position (position);
}
}
}
#[inline]
pub fn camera3d_orientation_set (&mut self,
orientation : math::Rotation3 <f32>
) {
for (_, viewport) in self.viewports.iter_mut() {
if viewport.camera3d().is_some() {
viewport.camera3d_set_orientation (orientation);
}
}
}
#[inline]
pub fn camera3d_look_at (&mut self, target : math::Point3 <f32>) {
for (_, viewport) in self.viewports.iter_mut() {
if viewport.camera3d().is_some() {
viewport.camera3d_look_at (target);
}
}
}
pub fn camera3d_move_local_xy (&mut self, dx : f32, dy : f32, dz : f32) {
self.viewports[MAIN_VIEWPORT].camera3d_move_local_xy (dx, dy, dz);
let position = self.viewports[MAIN_VIEWPORT].camera3d().unwrap().position();
for (_, viewport) in self.viewports.iter_mut().skip (1) {
if viewport.camera3d().is_some() {
viewport.camera3d_set_position (position);
}
}
}
#[inline]
pub fn camera3d_rotate (&mut self,
dyaw : math::Rad <f32>, dpitch : math::Rad <f32>, droll : math::Rad <f32>
) {
self.viewports[MAIN_VIEWPORT].camera3d_rotate (dyaw, dpitch, droll);
}
pub fn camera3d_orthographic_zoom_scale (&mut self, scale : f32) {
assert!(0.0 < scale);
for (_, viewport) in self.viewports.iter_mut().skip(1) {
if viewport.camera3d().is_some() {
debug_assert!(viewport.camera3d().unwrap().projection()
.is_orthographic());
viewport.camera3d_scale_fovy_or_zoom (scale);
}
}
}
pub fn camera3d_perspective_fovy_scale (&mut self, scale : f32) {
assert!(0.0 < scale);
debug_assert!(self.viewports[MAIN_VIEWPORT].camera3d().unwrap().projection()
.is_perspective());
self.viewports[MAIN_VIEWPORT].camera3d_scale_fovy_or_zoom (scale);
}
pub fn camera2d_zoom_set (&mut self, zoom : f32) {
assert!(0.0 < zoom);
for (_, viewport) in self.viewports.iter_mut() {
if viewport.camera2d().is_some() {
viewport.camera2d_set_zoom (zoom);
}
}
self.update_viewport_line_loops();
}
pub fn camera2d_zoom_shift (&mut self, shift : f32) {
let mut dirty = false;
for (_, viewport) in self.viewports.iter_mut() {
if viewport.camera2d().is_some() {
let zoom = viewport.camera2d().unwrap().zoom() + shift;
if 0.0 < zoom {
viewport.camera2d_set_zoom (zoom);
dirty = true;
}
}
}
if dirty {
self.update_viewport_line_loops();
}
}
pub fn camera2d_move_local (&mut self, dx : f32, dy : f32) {
self.viewports[MAIN_VIEWPORT].camera2d_move_local (dx, dy);
let position = self.viewports[MAIN_VIEWPORT].camera2d().unwrap().position();
for (_, viewport) in self.viewports.iter_mut().skip (1) {
if viewport.camera2d().is_some() {
viewport.camera2d_set_position (position);
}
}
}
pub fn camera2d_move_origin_to_bottom_left (&mut self) {
for (_, viewport) in self.viewports.iter_mut() {
if viewport.camera2d().is_some() {
viewport.camera2d_move_origin_to_bottom_left()
}
}
self.update_viewport_line_loops();
}
pub fn window_resized (&mut self,
physical_size : winit::dpi::PhysicalSize <u32>
) {
let (width, height) = physical_size.into();
if self.viewports.len() < 4 {
self.viewports[MAIN_VIEWPORT].set_rect (
glium::Rect { left: 0, bottom: 0, width, height }
);
self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
} else {
let left_width = left_width (width);
let right_width = right_width (width);
let upper_height = upper_height (height);
let lower_height = lower_height (height);
self.viewports[LOWER_RIGHT_VIEWPORT].set_rect (
glium::Rect {
width: right_width,
height: lower_height,
left: left_width,
bottom: 0
}
);
self.viewports[UPPER_LEFT_VIEWPORT].set_rect (
glium::Rect {
width: left_width,
height: upper_height,
left: 0,
bottom: lower_height
}
);
self.viewports[UPPER_RIGHT_VIEWPORT].set_rect (
glium::Rect {
width: right_width,
height: upper_height,
left: left_width,
bottom: lower_height
}
);
self.viewports[LOWER_LEFT_VIEWPORT].set_rect (
glium::Rect {
width: left_width,
height: lower_height,
left: 0,
bottom: 0
}
);
self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
}
self.update_viewport_line_loops();
}
fn update_viewport_line_loops (&mut self) {
const VERTS_PER_VIEWPORT : usize = 4;
let num_vertices = VERTS_PER_VIEWPORT * self.viewports.len();
let vertices = {
let mut vertices = Vec::<vertex::Vert2d>::with_capacity (num_vertices);
for (_, viewport) in self.viewports.iter() {
if let Some (camera2d) = viewport.camera2d() {
let pixel_radius = 0.5 / camera2d.zoom();
let position = camera2d.position();
let ortho = camera2d.ortho();
let left = position.0.x + ortho.left;
let bottom = position.0.y + ortho.bottom;
let top = position.0.y + ortho.top;
let right = position.0.x + ortho.right;
vertices.append (&mut vec![
vertex::Vert2d {
position: [left + pixel_radius, bottom + pixel_radius] },
vertex::Vert2d {
position: [left + pixel_radius, top - pixel_radius] },
vertex::Vert2d {
position: [right - pixel_radius, top - pixel_radius] },
vertex::Vert2d {
position: [right - pixel_radius, bottom + pixel_radius] }
]);
}
}
vertices
};
self.resource.draw2d
.line_loop_vertices_set (&self.glium_display, &vertices[..]);
}
}
impl std::default::Default for DefaultTilesetId {
fn default() -> Self {
DefaultTilesetId::EasciiAcorn128
}
}
#[inline]
fn left_width (window_width : u32) -> u32 {
window_width / 2
}
#[inline]
fn right_width (window_width : u32) -> u32 {
window_width / 2 + window_width % 2
}
#[inline]
fn upper_height (window_height : u32) -> u32 {
window_height / 2
}
#[inline]
fn lower_height (window_height : u32) -> u32 {
window_height / 2 + window_height % 2
}