use std::fs::read_to_string; use std::io::Error as IoError; use std::path::{Path, PathBuf}; use crate::material::{Builder as MaterialBuilder, Material}; use crate::opengl::shader::{ Error as ShaderError, Kind as ShaderKind, Program as ShaderProgram, Shader, }; use crate::renderer::Renderable; use crate::shader_preprocessor::{Error as ShaderPreprocessorError, ShaderPreprocessor}; use crate::texture::Texture; use crate::transform::Transform; use crate::vector::Vec3; use crate::vertex::Vertex; const SHADER_DIR: &str = "engine"; const VERTEX_SHADER_FILE: &str = "vertex.glsl"; const FRAG_SHADER_COLOR_FILE: &str = "fragment-color.glsl"; const FRAG_SHADER_TEXTURE_FILE: &str = "fragment-texture.glsl"; #[derive(Debug)] pub struct Object { id: Id, renderable: Renderable, transform: Transform, texture: Option, material: Material, } impl Object { /// Returns the object ID. #[must_use] pub fn id(&self) -> Id { self.id } #[must_use] pub fn position(&self) -> &Vec3 { self.transform.position() } pub fn translate(&mut self, translation: Vec3) { self.transform .set_position(self.transform.position().clone() + translation); } pub fn scale(&mut self, scaling: Vec3) { self.transform.set_scale(scaling); } #[must_use] pub fn texture(&self) -> Option<&Texture> { self.texture.as_ref() } #[must_use] pub fn material(&self) -> &Material { &self.material } pub(crate) fn renderable(&self) -> &Renderable { &self.renderable } pub(crate) fn transform(&self) -> &Transform { &self.transform } } /// Object builder. #[derive(Debug)] pub struct Builder { vertices: Vec, indices: Option>, texture: Option, material: Material, } impl Builder { /// Returns a new [`Object`] builder. #[must_use] pub fn new() -> Self { Self { vertices: Vec::new(), indices: None, texture: None, material: MaterialBuilder::new().build(), } } #[must_use] pub fn vertices(mut self, vertices: impl IntoIterator) -> Self { self.vertices = vertices.into_iter().collect(); self } #[must_use] pub fn indices(mut self, indices: impl IntoIterator) -> Self { self.indices = Some(indices.into_iter().collect()); self } #[must_use] pub fn texture(mut self, texture: Texture) -> Self { self.texture = Some(texture); self } #[must_use] pub fn material(mut self, material: Material) -> Self { self.material = material; self } /// Builds a new [`Object`]. /// /// # Errors /// Will return `Err` if: /// - Creating shaders fails /// - Linking the shader program fails pub fn build(self, id: Id) -> Result { let vertex_shader_file = Path::new(SHADER_DIR).join(VERTEX_SHADER_FILE); let fragment_shader_color_file = Path::new(SHADER_DIR).join(FRAG_SHADER_COLOR_FILE); let fragment_shader_texture_file = Path::new(SHADER_DIR).join(FRAG_SHADER_TEXTURE_FILE); let vertex_shader_content = read_to_string(&vertex_shader_file).map_err(|err| { Error::ReadShaderFailed { source: err, shader_file: vertex_shader_file.clone(), } })?; let fragment_shader_color_content = read_to_string(&fragment_shader_color_file) .map_err(|err| Error::ReadShaderFailed { source: err, shader_file: fragment_shader_color_file.clone(), })?; let fragment_shader_texture_content = read_to_string(&fragment_shader_texture_file).map_err(|err| { Error::ReadShaderFailed { source: err, shader_file: fragment_shader_texture_file.clone(), } })?; let shader_preprocessor = ShaderPreprocessor::new(PathBuf::from(SHADER_DIR)); let vertex_shader_content_preprocessed = shader_preprocessor .preprocess(vertex_shader_content, &vertex_shader_file) .map_err(|err| Error::PreprocessShaderFailed { source: err, shader_file: vertex_shader_file, })?; let frag_shader_color_content_preprocessed = shader_preprocessor .preprocess(fragment_shader_color_content, &fragment_shader_color_file) .map_err(|err| Error::PreprocessShaderFailed { source: err, shader_file: fragment_shader_color_file, })?; let frag_shader_texture_content_preprocessed = shader_preprocessor .preprocess( fragment_shader_texture_content, &fragment_shader_texture_file, ) .map_err(|err| Error::PreprocessShaderFailed { source: err, shader_file: fragment_shader_texture_file, })?; let vertex_shader = Self::create_shader(ShaderKind::Vertex, &vertex_shader_content_preprocessed) .map_err(Error::CreateVertexShaderFailed)?; let fragment_shader = Self::create_shader( ShaderKind::Fragment, if self.texture.is_some() { &frag_shader_texture_content_preprocessed } else { &frag_shader_color_content_preprocessed }, ) .map_err(Error::CreateFragmentShaderFailed)?; let shader_program = ShaderProgram::new(); shader_program.attach(&vertex_shader); shader_program.attach(&fragment_shader); shader_program .link() .map_err(Error::LinkShaderProgramFailed)?; let renderable = Renderable::new(shader_program, &self.vertices, self.indices.as_deref()); let transform = Transform::new(); Ok(Object { id, renderable, transform, texture: self.texture, material: self.material, }) } fn create_shader(kind: ShaderKind, source: &str) -> Result { let shader = Shader::new(kind); shader.set_source(source)?; shader.compile()?; Ok(shader) } } impl Default for Builder { fn default() -> Self { Self::new() } } /// Object error #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Failed to create vertex shader")] CreateVertexShaderFailed(#[source] ShaderError), #[error("Failed to create fragment shader")] CreateFragmentShaderFailed(#[source] ShaderError), #[error("Failed to link shader program")] LinkShaderProgramFailed(#[source] ShaderError), #[error("Failed to read shader {}", shader_file.display())] ReadShaderFailed { #[source] source: IoError, shader_file: PathBuf, }, #[error("Failed to preprocess shader {}", shader_file.display())] PreprocessShaderFailed { #[source] source: ShaderPreprocessorError, shader_file: PathBuf, }, } /// Object ID. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Id { id: u16, } impl Id { /// Returns a new object ID. #[must_use] pub fn new(id: u16) -> Self { Self { id } } }