318 lines
10 KiB
Rust
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),
|
|
),
|
|
¶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<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
|
|
}
|