//! This module contains the rendering structs. use std::borrow::Cow; use std::cell::Ref; use std::ops::Deref; use image; use image::{DynamicImage, ImageBuffer, Rgba}; use glium::draw_parameters::{Blend, DepthTest}; use glium::glutin::PossiblyCurrent as Pc; use glium::index::{NoIndices, PrimitiveType}; use glium::program::ProgramCreationInput; use glium::texture::{RawImage2d, SrgbTexture2d, Texture2dDataSink}; use glium::{Depth, Display, DrawParameters, Frame, Program, Surface, VertexBuffer}; use camera::{mat_to_f32, RenderCamera}; use scene::Scene; use math::vector::Vector3; use model::{Model, Part, Vertex}; /// 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 the program with the default shader. pub fn default_shader(display: &Display) -> Program { Program::new( display, ProgramCreationInput::SourceCode { vertex_shader: include_str!("../assets/shaders/default.vert"), fragment_shader: include_str!("../assets/shaders/default.frag"), geometry_shader: None, tessellation_control_shader: None, tessellation_evaluation_shader: None, transform_feedback_varyings: None, outputs_srgb: false, uses_point_size: false, }, ) .unwrap() } /// Creates the shader with one color per face. pub fn color_shader(display: &Display) -> Program { Program::new( display, ProgramCreationInput::SourceCode { vertex_shader: include_str!("../assets/shaders/color.vert"), fragment_shader: include_str!("../assets/shaders/color.frag"), geometry_shader: None, tessellation_control_shader: None, tessellation_evaluation_shader: None, transform_feedback_varyings: None, outputs_srgb: true, uses_point_size: false, }, ) .unwrap() } /// 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 = Renderer::default_shader(&display); Renderer::from_display_and_program(display, program) } /// Creates a new colored renderer from a display. /// /// Is uses the face colors shaders and creates an empty vec of models. pub fn color(display: Display) -> Renderer { let program = Renderer::color_shader(&display); Renderer::from_display_and_program(display, program) } /// Creates a new renderer from a program. /// /// It allows you to use a custom shader. pub fn from_display_and_program(display: Display, program: Program) -> Renderer { 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(); let renderer = Renderer { display: display, program: program, clear_color: (0.0, 0.0, 0.0, 1.0), default_texture: texture, }; // Force capture, otherwise, first capture fails. renderer.capture(); renderer } /// Returns the inner GlWindows. pub fn gl_window(&self) -> Ref<'_, impl Deref>> { self.display.gl_window() } /// Creates a SrgbTexture from a path to an image. pub fn make_texture(&self, path: &str) -> SrgbTexture2d { let image = match image::open(path) { Ok(r) => r.to_rgba8(), Err(e) => panic!("Error while opening file {}: {}", path, e), }; 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 = mat_to_f32(camera.perspective()); let view = mat_to_f32(camera.view()); 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 { if let None = model.materials.get(name) { panic!("Material {} not found", name); } let t = model.materials.get(name).unwrap().diffuse; Vector3::new(t[0] as f32, t[1] as f32, t[2] as f32) } 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, Vector3::new(size[0] as f32, size[1] as f32, size[2] as f32), ) } 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().window().set_visible(true); } /// Returns a DynamicImage of the corresponding frame. pub fn capture(&self) -> DynamicImage { // Create temporary texture and blit the front buffer to it let image: RawImage2d = self.display.read_front_buffer().unwrap(); let image = ImageBuffer::from_raw(image.width, image.height, image.data.into_owned()).unwrap(); DynamicImage::ImageRgba8(image).flipv() } } /// 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 }