//! This module contains everything related to rendering. pub mod camera; pub mod controls; use std::cell::Ref; use std::borrow::Cow; use glium; use glium::glutin::GlWindow; use glium::draw_parameters::{DepthTest, Blend}; use glium::texture::{ RawImage2d, SrgbTexture2d, }; use glium::index::{ NoIndices, PrimitiveType }; use glium::{ Program, Display, VertexBuffer, Frame, DrawParameters, Depth, }; use glium::backend::Facade; use glium::backend::glutin::headless::Headless; use image; use model::{Model, Vertex}; use renderer::camera::RenderCamera; struct RGBAImageData { pub data: Vec<(u8, u8, u8, u8)>, pub width: u32, pub height: u32, } impl glium::texture::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, } } } /// Anything on which you can call draw to get a frame. pub trait Drawer { /// Get a frame from the drawer. fn draw(&self) -> Frame; } impl Drawer for Display { fn draw(&self) -> Frame { Display::draw(self) } } impl Drawer for Headless { fn draw(&self) -> Frame { Headless::draw(self) } } /// A material that can be bound to OpenGL. /// /// Only textures are supported for now. pub struct RenderMaterial { texture: Option, } impl RenderMaterial { /// Creates a new material with no texture. pub fn new() -> RenderMaterial { RenderMaterial { texture: None } } /// Creates a material from a path to an image file. pub fn from_texture_path(path: &str, drawer: &D) -> RenderMaterial { let image = image::open(path); if let Ok(image) = image { let image = image.to_rgba(); let dim = image.dimensions(); let image = RawImage2d::from_raw_rgba_reversed(&image.into_raw(), dim); RenderMaterial { texture: SrgbTexture2d::new(drawer, image).ok() } } else { RenderMaterial { texture: None } } } } /// A renderer. It contains a display, shaders, and a vector of models it can render. pub struct Renderer<'a, D: Drawer + Facade> { drawer: D, program: Program, models: Vec<(&'a Model, Vec<(RenderMaterial, VertexBuffer)>)>, } impl<'a, D: Drawer + Facade + Sized> Renderer<'a, D> { /// Creates a new renderer from a display. /// /// Is uses the default shaders and creates an empty vec of models. pub fn new(drawer: D) -> Renderer<'a, D> { let program = Program::from_source( &drawer, include_str!("../../assets/shaders/shader.vert"), include_str!("../../assets/shaders/shader.frag"), None ); if let Ok(program) = program { Renderer { drawer: drawer, program: program, models: vec![], } } else { println!("{:?}", program.err().unwrap()); panic!() } } /// Adds a model to the renderer, and compute the corresponding buffers for rendering. pub fn add_model(&mut self, model: &'a Model) { let mut buffers = vec![]; for part in &model.parts { let material = if let Some(ref material_name) = part.material_name { if let Some(material) = model.materials.get(material_name) { if let Some(path) = material.textures.get("map_Kd") { RenderMaterial::from_texture_path(path, &self.drawer) } else { RenderMaterial::new() } } else { RenderMaterial::new() } } else { RenderMaterial::new() }; let shape = part.build_shape(&model.vertices, &model.texture_coordinates, &model.normals); buffers.push((material, VertexBuffer::new(&self.drawer, &shape).unwrap())); } self.models.push((model, buffers)); } /// Returns the reference to the drawer used for this renderer. pub fn facade(&self) -> &D { &self.drawer } /// Creates a frame from the display. pub fn draw(&self) -> Frame { self.drawer.draw() } /// Renders the result of the models from the camera and paint it on the target. pub fn render(&self, camera: &C, target: &mut Frame) { use glium::Surface; target.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0); let params = DrawParameters { depth: Depth { test: DepthTest::IfLess, write: true, .. Default::default() }, blend: Blend::alpha_blending(), .. Default::default() }; for &(_, ref buffers) in &self.models { for &(ref material, ref buffer) in buffers { let perspective = camera.get_perspective_matrix(); let view = camera.get_view_matrix(); if let &Some(ref texture) = &material.texture { target.draw( buffer, NoIndices(PrimitiveType::TrianglesList), &self.program, &uniform!( tex: texture, perspective: perspective, view: view, ), ¶ms, ).unwrap(); } else { target.draw( buffer, NoIndices(PrimitiveType::TrianglesList), &self.program, &uniform!( perspective: perspective, view: view, ), ¶ms, ).unwrap(); }; } } } /// Returns an image of the corresponding frame. pub fn capture(&self, dimensions: (u32, u32)) -> image::DynamicImage { let rect = glium::Rect { left: 0, bottom: 0, width: dimensions.0, height: dimensions.1, }; let blit_target = glium::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 = glium::texture::Texture2d::empty(self.facade(), dimensions.0, dimensions.1) .unwrap(); let framebuffer = glium::framebuffer::SimpleFrameBuffer::new(self.facade(), &texture) .unwrap(); use glium::Surface; framebuffer.blit_from_frame( &rect, &blit_target, glium::uniforms::MagnifySamplerFilter::Nearest ); // Read the texture into new pixel buffer let pixel_buffer = texture.read_to_pixel_buffer(); let image_data: RGBAImageData = pixel_buffer.read_as_texture_2d().unwrap(); 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 } } impl<'a> Renderer<'a, Display> { /// Shows the window if it was hidden. pub fn show(&mut self) { self.drawer.gl_window().show(); } /// Hides the window if it was visible. pub fn hide(&mut self) { self.drawer.gl_window().hide(); } /// Returns a reference to the gl window. pub fn gl_window(&self) -> Ref { self.drawer.gl_window() } }