model-converter/src/renderer.rs

318 lines
10 KiB
Rust

//! 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<Target = glium::glutin::WindowedContext<Pc>>> {
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<Rgba<u8>, Vec<u8>>,
) -> 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<C: RenderCamera>(&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),
),
&params,
)
.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<f64>)> {
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<Vertex>) -> VertexBuffer<Vertex> {
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<u8> = 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
}