model-converter/src/renderer/mod.rs

303 lines
8.2 KiB
Rust

//! 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<SrgbTexture2d>,
}
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<D: Drawer + Facade + Sized>(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<Vertex>)>)>,
}
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<C: RenderCamera>(&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,
),
&params,
).unwrap();
} else {
target.draw(
buffer,
NoIndices(PrimitiveType::TrianglesList),
&self.program,
&uniform!(
perspective: perspective,
view: view,
),
&params,
).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<GlWindow> {
self.drawer.gl_window()
}
}