//! This module contains the rendering structs. use std::cell::Ref; use std::borrow::Cow; use image; use image::{ImageBuffer, Rgba}; use glium::texture::{RawImage2d, SrgbTexture2d, Texture2dDataSink}; use glium::{Frame, Display, Surface, Program, DrawParameters, Depth, VertexBuffer}; use glium::{Rect, BlitTarget}; use glium::framebuffer::SimpleFrameBuffer; use glium::uniforms::MagnifySamplerFilter; use glium::draw_parameters::{DepthTest, Blend}; use glium::index::{NoIndices, PrimitiveType}; use glium::glutin::GlWindow; use scene::Scene; use camera::RenderCamera; use model::{Vertex, Part, Model}; use math::vector::Vector3; /// Image data stored as RGBA. pub struct RgbaImageData { /// The array of colors. pub data: Vec<(u8, u8, u8, u8)>, /// The width of the image. pub width: u32, /// The height of the image. pub height: u32, } impl Texture2dDataSink<(u8, u8, u8, u8)> for RgbaImageData { fn from_raw(data: Cow<[(u8, u8, u8, u8)]>, width: u32, height: u32) -> Self { RgbaImageData { data: data.into_owned(), width: width, height: height, } } } /// A renderer. It contains a display, shaders, and some rendering parameters. pub struct Renderer { /// The display on which the rendering will be done. display: Display, /// The shading program. program: Program, /// The background color. clear_color: (f32, f32, f32, f32), /// The texture that will be applied to non textured models. default_texture: SrgbTexture2d, } impl Renderer { /// Creates a new renderer from a display. /// /// Is uses the default shaders and creates an empty vec of models. pub fn new(display: Display) -> Renderer { let program = Program::from_source( &display, include_str!("../assets/shaders/shader.vert"), include_str!("../assets/shaders/shader.frag"), None ).unwrap(); let image = RawImage2d::from_raw_rgba(vec![1.0, 1.0, 1.0, 1.0], (1, 1)); let texture = SrgbTexture2d::new(&display, image).ok().unwrap(); Renderer { display: display, program: program, clear_color: (0.0, 0.0, 0.0, 1.0), default_texture: texture, } } /// Returns the inner GlWindows. pub fn gl_window(&self) -> Ref { self.display.gl_window() } /// Creates a SrgbTexture from a path to an image. pub fn make_texture(&self, path: &str) -> SrgbTexture2d { let image = image::open(path).unwrap().to_rgba(); self.make_texture_from_buffer(image) } /// Creates a SrgbTexture from an image buffer. pub fn make_texture_from_buffer(&self, buffer: ImageBuffer, Vec>) -> SrgbTexture2d { let dimensions = buffer.dimensions(); let buffer = RawImage2d::from_raw_rgba_reversed(&buffer.into_raw(), dimensions); SrgbTexture2d::new(&self.display, buffer).ok().unwrap() } /// Creates a 1x1 SrgbTexture with the color passed as parameter. pub fn make_texture_from_color_channels(&self, r: f32, g: f32, b: f32, a: f32) -> SrgbTexture2d { let image = RawImage2d::from_raw_rgba(vec![r, g, b, a], (1, 1)); SrgbTexture2d::new(&self.display, image).ok().unwrap() } /// Creates a 1x1 SrgbTexture with the color passed as parameter. pub fn make_texture_from_color(&self, c: (f32, f32, f32, f32)) -> SrgbTexture2d { let image = RawImage2d::from_raw_rgba(vec![c.0, c.1, c.2, c.3], (1, 1)); SrgbTexture2d::new(&self.display, image).ok().unwrap() } /// Creates a frame from the display. pub fn draw(&self) -> Frame { self.display.draw() } /// Renders on the display. pub fn render(&self, scene: &Scene, camera: &C) { let mut target = self.draw(); target.clear_color_srgb_and_depth(self.clear_color, 1.0); let perspective = camera.get_perspective_matrix(); let view = camera.get_view_matrix(); let params = DrawParameters { depth: Depth { test: DepthTest::IfLess, write: true, .. Default::default() }, blend: Blend::alpha_blending(), .. Default::default() }; for model in scene.iter() { let model = &*model.borrow(); for part in &model.parts { if let &Some(ref buffer) = part.vertex_buffer() { let diffuse = if let Some(ref name) = part.material_name { model.materials.get(name).unwrap().diffuse } else { Vector3::new(1.0, 1.0, 1.0) }; let texture = self.get_texture_of_part(&model, part); let (texture, size) = if let Some((texture, size)) = texture { (texture, size) } else { (&self.default_texture, Vector3::new(1.0, 1.0, 1.0)) }; target.draw( buffer, NoIndices(PrimitiveType::TrianglesList), &self.program, &uniform!( diffuse: Into::<[f32; 3]>::into(diffuse), tex: texture, perspective: Into::<[[f32; 4]; 4]>::into(perspective), view: Into::<[[f32; 4]; 4]>::into(view), texture_size: Into::<[f32; 3]>::into(size), ), ¶ms, ).unwrap(); } } } target.finish().unwrap(); } /// Renders a part of a model. fn get_texture_of_part<'a>(&self, model: &'a Model, part: &Part) -> Option<(&'a SrgbTexture2d, Vector3)> { if let Some(ref material_name) = part.material_name { if let Some(ref material) = model.materials.get(material_name) { if let Some((texture, size)) = material.textures.get("map_Kd") { if let Some(texture) = model.get_texture_by_name(texture) { Some((texture, *size)) } else { None } } else { None } } else { None } } else { None } } /// Builds a vertex buffer from a vector of vertex. pub fn build_vertex_buffer(&self, vertices: &Vec) -> VertexBuffer { VertexBuffer::new(&self.display, vertices).unwrap() } /// Changes the clear color of the renderer. pub fn set_clear_color(&mut self, r: f32, g: f32, b: f32, a: f32) { self.clear_color = (r, g, b, a); } /// Shows the window if hidden. pub fn show(&mut self) { self.gl_window().show(); } /// Returns a DynamicImage of the corresponding frame. pub fn capture(&self, dimensions: (u32, u32)) -> image::DynamicImage { rgba_image_data_to_image(self.capture_rgba_image_data(dimensions)) } /// Returns a RgbaImageData of the corresponding frame. pub fn capture_rgba_image_data(&self, dimensions: (u32, u32)) -> RgbaImageData { let rect = Rect { left: 0, bottom: 0, width: dimensions.0, height: dimensions.1, }; let blit_target = BlitTarget { left: 0, bottom: 0, width: dimensions.0 as i32, height: dimensions.1 as i32, }; // Create temporary texture and blit the front buffer to it let texture = SrgbTexture2d::empty(&self.display, dimensions.0, dimensions.1) .unwrap(); let framebuffer = SimpleFrameBuffer::new(&self.display, &texture) .unwrap(); use glium::Surface; framebuffer.blit_from_frame( &rect, &blit_target, MagnifySamplerFilter::Nearest ); // Read the texture into new pixel buffer let pixel_buffer = texture.read_to_pixel_buffer(); pixel_buffer.read_as_texture_2d().unwrap() } } /// Converts a RgbaImamgeData to a DynamicImage. pub fn rgba_image_data_to_image(image_data: RgbaImageData) -> image::DynamicImage { let pixels = { let mut v = Vec::with_capacity(image_data.data.len() * 4); for (a, b, c, d) in image_data.data { v.push(a); v.push(b); v.push(c); v.push(d); } v }; // Create ImageBuffer let image_buffer = image::ImageBuffer::from_raw(image_data.width, image_data.height, pixels) .unwrap(); // Save the screenshot to file let image = image::DynamicImage::ImageRgba8(image_buffer).flipv(); image }