darkengine 0.1.0

2D game engine written in Rust
mod texture;
mod font;
mod bitmap_font;
mod dynamic_font;
pub use glfw_backend::Display;
pub use self::{texture::*, font::Font, bitmap_font::BitmapFont, dynamic_font::DynamicFont};
use std::rc::Rc;
use glium::{self, Surface};
use cgmath::{self, Matrix4, Transform, Vector3, Rad, Deg};

#[derive(Copy, Clone, Debug)]
pub(crate) struct Vertex {
	pub pos: [f32; 2],
	pub uv: [f32; 2]
}

/// Struct passed to `App::render` each frame, used for rendering.
pub struct Renderer<'a> {
	display: &'a Display,
	target: &'a mut glium::Frame,
	quad: &'a glium::VertexBuffer<Vertex>,
	tex_program: &'a glium::Program,
	rect_program: &'a glium::Program,
	text_program: &'a glium::Program,
	default_fonts: &'a mut Vec<(u32, Rc<Font>)>,
	font: Option<Rc<Font>>,
	transform: Matrix4<f32>,
	stack: Vec<Matrix4<f32>>,
	screen_width: u32,
	screen_height: u32
}

impl<'a> Renderer<'a> {
	pub(crate) fn new(
		display: &'a Display,
		target: &'a mut glium::Frame,
		quad: &'a glium::VertexBuffer<Vertex>,
		tex_program: &'a glium::Program,
		rect_program: &'a glium::Program,
		text_program: &'a glium::Program,
		default_fonts: &'a mut Vec<(u32, Rc<Font>)>,
		screen_width: u32,
		screen_height: u32
	) -> Renderer<'a> {
		Renderer {
			display,
			target,
			quad,
			tex_program,
			rect_program,
			text_program,
			default_fonts,
			font: None,
			transform: cgmath::ortho(0.0, screen_width as f32, screen_height as f32, 0.0, -1.0, 1.0),
			stack: Vec::new(),
			screen_width,
			screen_height
		}
	}

	/// Push the matrix to the stack.
	pub fn push_matrix(&mut self) {
		self.stack.push(self.transform);
	}

	/// Pop the matrix from the stack. If it's empty, does nothing.
	pub fn pop_matrix(&mut self) {
		match self.stack.pop() {
			Some(v) => {self.transform = v;},
			None => {}
		}
	}

	/// Run a closure, after which the matrix will be restored.
	/// # Example
	/// ```rust,no_run
	/// # let mut renderer: darkengine::graphics::Renderer = None.unwrap();
	/// renderer.keep_matrix(|renderer| {
	/// 	renderer.scale(2.0, 2.0);
	/// 	renderer.draw_text("abc", 0, 0, 1.0, 1.0, 1.0, 1.0);
	/// });
	/// ```
	pub fn keep_matrix<F: FnOnce(&mut Renderer)>(&mut self, f: F) {
		let prev = self.transform;
		f(self);
		self.transform = prev;
	}

	/// Use an orthographic projection matrix.
	pub fn set_ortho(&mut self, left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) {
		self.transform = cgmath::ortho(left, right, bottom, top, near, far);
	}

	/// Use a perspective projection matrix.
	pub fn set_perspective(&mut self, fov: f32, near: f32, far: f32) {
		self.transform = cgmath::perspective(Deg(fov), self.screen_width as f32 / self.screen_height as f32, near, far);
	}

	/// Translate the matrix.
	pub fn translate(&mut self, x: i32, y: i32) {
		self.transform.concat_self(&Matrix4::from_translation(Vector3::new(x as f32, y as f32, 0.0)));
	}

	/// Scale the matrix.
	pub fn scale(&mut self, x: f32, y: f32) {
		self.transform.concat_self(&Matrix4::from_nonuniform_scale(x, y, 1.0));
	}

	/// Rotate the matrix around the Z axis, in degrees.
	pub fn rotate(&mut self, angle: f32) {
		self.transform.concat_self(&Matrix4::from_angle_z(Deg(angle)));
	}

	/// Rotate the matrix around the Z axis, in radians.
	pub fn rotate_rad(&mut self, angle: f32) {
		self.transform.concat_self(&Matrix4::from_angle_z(Rad(angle)));
	}

	/// Translate the matrix in 3D.
	pub fn translate_3d(&mut self, x: f32, y: f32, z: f32) {
		self.transform.concat_self(&Matrix4::from_translation(Vector3::new(x, y, z)));
	}

	/// Scale the matrix in 3D.
	pub fn scale_3d(&mut self, x: f32, y: f32, z: f32) {
		self.transform.concat_self(&Matrix4::from_nonuniform_scale(x, y, z));
	}

	/// Rotate the matrix around all axes, in degrees.
	pub fn rotate_3d(&mut self, x: f32, y: f32, z: f32) {
		self.transform.concat_self(&Matrix4::from_angle_x(Deg(x)));
		self.transform.concat_self(&Matrix4::from_angle_y(Deg(y)));
		self.transform.concat_self(&Matrix4::from_angle_z(Deg(z)));
	}

	/// Rotate the matrix around all axes, in radians.
	pub fn rotate_rad_3d(&mut self, x: f32, y: f32, z: f32) {
		self.transform.concat_self(&Matrix4::from_angle_x(Rad(x)));
		self.transform.concat_self(&Matrix4::from_angle_y(Rad(y)));
		self.transform.concat_self(&Matrix4::from_angle_z(Rad(z)));
	}

	/// Clear the screen to black.
	pub fn clear(&mut self) {
		self.target.clear_color(0.0, 0.0, 0.0, 1.0);
	}

	/// Get the current font. If no font was set yet, returns the default font.
	pub fn font(&mut self) -> Rc<Font> {
		if let Some(ref font) = self.font {
			font.clone()
		} else {
			self.set_default_font(24);
			self.font.as_ref().unwrap().clone()
		}
	}

	/// Use a custom font.
	pub fn set_font(&mut self, font: Rc<Font>) {
		self.font = Some(font);
	}

	/// Use the default font, with a custom height.
	pub fn set_default_font(&mut self, height: u32) {
		for (height2, font) in self.default_fonts.iter() {
			if *height2 == height {
				self.font = Some(font.clone());
				return;
			}
		}
		let font = Rc::new(Font::Dynamic(DynamicFont::load_default(height)));
		self.default_fonts.push((height, font.clone()));
		self.font = Some(font);
	}

	/// Draw text at (x, y) tinted (r, g, b, a) in color.
	pub fn draw_text(&mut self, text: &str, x: i32, y: i32, r: f32, g: f32, b: f32, a: f32) {
		if let None = self.font {
			self.set_default_font(24);
		}
		let transform = self.transform.concat(&Matrix4::from_translation(Vector3::new(x as f32, y as f32, 0.0)));
		match &**self.font.as_ref().unwrap() {
			Font::Dynamic(font) => {
				let texture = font.draw_to_texture(text, self.display);
				let (w, h) = texture.size();
				self.target.draw(
					self.quad,
					&glium::index::NoIndices(glium::index::PrimitiveType::TriangleFan),
					self.text_program,
					&uniform! {
						size: [w as f32, h as f32],
						tex: texture.internal(),
						color: [r, g, b, a],
						mvp: [
							[transform.x.x, transform.x.y, transform.x.z, transform.x.w],
							[transform.y.x, transform.y.y, transform.y.z, transform.y.w],
							[transform.z.x, transform.z.y, transform.z.z, transform.z.w],
							[transform.w.x, transform.w.y, transform.w.z, transform.w.w]
						]
					},
					&glium::DrawParameters {
						blend: glium::draw_parameters::Blend::alpha_blending(),
						.. glium::DrawParameters::default()
					}
				).unwrap();
			},
			Font::Bitmap(font) => {
				font.draw(text, transform, r, g, b, a, self.text_program, self.target, self.display);
			}
		}
	}

	/// Draw a colored rectangle at (x, y) with size (w, h) and tinted (r, g, b, a) in color.
	pub fn draw_rect(&mut self, x: i32, y: i32, w: i32, h: i32, r: f32, g: f32, b: f32, a: f32) {
		let transform = self.transform.concat(&Matrix4::from_translation(Vector3::new(x as f32, y as f32, 0.0)));
		if x < self.screen_width as i32 && y < self.screen_height as i32 && x > -w && y > -h {
			self.target.draw(
				self.quad,
				&glium::index::NoIndices(glium::index::PrimitiveType::TriangleFan),
				self.rect_program,
				&uniform! {
					size: [w as f32, h as f32],
					color: [r, g, b, a],
					mvp: [
						[transform.x.x, transform.x.y, transform.x.z, transform.x.w],
						[transform.y.x, transform.y.y, transform.y.z, transform.y.w],
						[transform.z.x, transform.z.y, transform.z.z, transform.z.w],
						[transform.w.x, transform.w.y, transform.w.z, transform.w.w]
					]
				},
				&glium::DrawParameters {
					blend: glium::draw_parameters::Blend::alpha_blending(),
					.. glium::DrawParameters::default()
				}
			).unwrap();
		}
	}

	/// Draw a texture at (x, y).
	pub fn draw_texture(&mut self, texture: &Texture, x: i32, y: i32) {
		let (w, h) = texture.size();
		self.draw_texture_region_sized_tinted(texture, x, y, 0, 0, w, h, w, h, 1.0, 1.0, 1.0, 1.0);
	}

	/// Draw a texture at (x, y) to a rectangle of size (sw, sh).
	pub fn draw_texture_sized(&mut self, texture: &Texture, x: i32, y: i32, sw: u32, sh: u32) {
		let (w, h) = texture.size();
		self.draw_texture_region_sized_tinted(texture, x, y, 0, 0, w, h, sw, sh, 1.0, 1.0, 1.0, 1.0);
	}

	/// Draw the region of a texture at (rx, ry) with size (rw, rh) at (x, y).
	pub fn draw_texture_region(&mut self, texture: &Texture, x: i32, y: i32, rx: u32, ry: u32, rw: u32, rh: u32) {
		self.draw_texture_region_sized_tinted(texture, x, y, rx, ry, rw, rh, rw, rh, 1.0, 1.0, 1.0, 1.0);
	}

	/// Draw a texture at (x, y) tinted (r, g, b, a) in color.
	pub fn draw_texture_tinted(&mut self, texture: &Texture, x: i32, y: i32, r: f32, g: f32, b: f32, a: f32) {
		let (w, h) = texture.size();
		self.draw_texture_region_sized_tinted(texture, x, y, 0, 0, w, h, w, h, r, g, b, a);
	}

	/// Draw a texture at (x, y) to a rectangle of size (sw, sh) and tinted (r, g, b, a) in color.
	pub fn draw_texture_sized_tinted(&mut self, texture: &Texture, x: i32, y: i32, sw: u32, sh: u32, r: f32, g: f32, b: f32, a: f32) {
		let (w, h) = texture.size();
		self.draw_texture_region_sized_tinted(texture, x, y, 0, 0, w, h, sw, sh, r, g, b, a);
	}

	/// Draw the region of a texture at (rx, ry) with size (rw, rh) at (x, y) to a rectangle of size (sw, sh).
	pub fn draw_texture_region_sized(&mut self, texture: &Texture, x: i32, y: i32, rx: u32, ry: u32, rw: u32, rh: u32, sw: u32, sh: u32) {
		self.draw_texture_region_sized_tinted(texture, x, y, rx, ry, rw, rh, sw, sh, 1.0, 1.0, 1.0, 1.0);
	}

	/// Draw the region of a texture at (rx, ry) with size (rw, rh) at (x, y) tinted (r, g, b, a) in color.
	pub fn draw_texture_region_tinted(&mut self, texture: &Texture, x: i32, y: i32, rx: u32, ry: u32, rw: u32, rh: u32, r: f32, g: f32, b: f32, a: f32) {
		self.draw_texture_region_sized_tinted(texture, x, y, rx, ry, rw, rh, rw, rh, r, g, b, a);
	}

	/// Draw the region of a texture at (rx, ry) with size (rw, rh) at (x, y) to a rectangle of size (sw, sh) and tinted (r, g, b, a) in color.
	pub fn draw_texture_region_sized_tinted(&mut self, texture: &Texture, x: i32, y: i32, rx: u32, ry: u32, rw: u32, rh: u32, sw: u32, sh: u32, r: f32, g: f32, b: f32, a: f32) {
		let (w, h) = texture.size();
		let transform = self.transform.concat(&Matrix4::from_translation(Vector3::new(x as f32, y as f32, 0.0)));
		self.target.draw(
			self.quad,
			&glium::index::NoIndices(glium::index::PrimitiveType::TriangleFan),
			self.tex_program,
			&uniform! {
				size: [sw as f32, sh as f32],
				region: [rx as f32 / w as f32, ry as f32 / w as f32, (rx + rw) as f32 / w as f32, (ry + rh) as f32 / h as f32],
				tex: texture.internal().sampled().wrap_function(texture.wrap).minify_filter(texture.min).magnify_filter(texture.mag),
				color: [r, g, b, a],
				mvp: [
					[transform.x.x, transform.x.y, transform.x.z, transform.x.w],
					[transform.y.x, transform.y.y, transform.y.z, transform.y.w],
					[transform.z.x, transform.z.y, transform.z.z, transform.z.w],
					[transform.w.x, transform.w.y, transform.w.z, transform.w.w]
				]
			},
			&glium::DrawParameters {
				blend: glium::draw_parameters::Blend::alpha_blending(),
				.. glium::DrawParameters::default()
			}
		).unwrap();
	}
}