From 0f451c715c56990a2f48e34be96bf03c0fea3723 Mon Sep 17 00:00:00 2001 From: Victor Vobis Date: Wed, 14 Jan 2026 12:55:56 +0100 Subject: [PATCH] TODO: Finish draw function and scene rendering logic --- Cargo.lock | 33 ++- Cargo.toml | 1 + src/app/app.rs | 161 ++---------- src/app/camera.rs | 3 + src/app/light.rs | 3 + src/app/renderer.rs | 199 --------------- src/app/scene.rs | 36 +++ src/assets/error.rs | 33 +++ src/assets/mtl.rs | 143 +++++++++++ src/assets/obj.rs | 105 ++++++++ src/assets/parser.rs | 62 +++++ src/assets/types.rs | 41 +++ src/backend/backend.rs | 33 +++ src/backend/gpu/mesh.rs | 7 + src/{app => backend/opengl}/gl.rs | 23 +- src/backend/opengl/gl_backend.rs | 163 ++++++++++++ src/backend/opengl/gl_renderer.rs | 240 ++++++++++++++++++ src/backend/opengl/gl_shader.rs | 4 + src/{models => geometry}/mesh.rs | 2 +- src/geometry/vertex.rs | 8 + src/handles.rs | 5 + src/lib.rs | 75 ++++-- src/main.rs | 28 +- .../primitives.rs => math/matrix/mat4.rs} | 81 +----- src/math/vector/vec2.rs | 155 +++++++++++ src/math/vector/vec3.rs | 171 +++++++++++++ src/math/vector/vec4.rs | 171 +++++++++++++ src/math/vector/vector.rs | 68 +++++ src/models/material.rs | 26 -- src/models/model.rs | 42 +-- src/models/object.rs | 200 --------------- src/models/scene.rs | 8 - src/models/vertex.rs | 15 -- src/shaders/constants.rs | 69 +++-- src/shaders/shader.rs | 43 ---- src/shaders/types.rs | 5 + 36 files changed, 1677 insertions(+), 785 deletions(-) create mode 100644 src/app/camera.rs create mode 100644 src/app/light.rs delete mode 100644 src/app/renderer.rs create mode 100644 src/app/scene.rs create mode 100644 src/assets/error.rs create mode 100644 src/assets/mtl.rs create mode 100644 src/assets/obj.rs create mode 100644 src/assets/parser.rs create mode 100644 src/assets/types.rs create mode 100644 src/backend/backend.rs create mode 100644 src/backend/gpu/mesh.rs rename src/{app => backend/opengl}/gl.rs (80%) create mode 100644 src/backend/opengl/gl_backend.rs create mode 100644 src/backend/opengl/gl_renderer.rs create mode 100644 src/backend/opengl/gl_shader.rs rename src/{models => geometry}/mesh.rs (95%) create mode 100644 src/geometry/vertex.rs create mode 100644 src/handles.rs rename src/{models/primitives.rs => math/matrix/mat4.rs} (65%) create mode 100644 src/math/vector/vec2.rs create mode 100644 src/math/vector/vec3.rs create mode 100644 src/math/vector/vec4.rs create mode 100644 src/math/vector/vector.rs delete mode 100644 src/models/material.rs delete mode 100644 src/models/object.rs delete mode 100644 src/models/scene.rs delete mode 100644 src/models/vertex.rs delete mode 100644 src/shaders/shader.rs create mode 100644 src/shaders/types.rs diff --git a/Cargo.lock b/Cargo.lock index 901c142..6f9c6b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,7 +49,7 @@ dependencies = [ "ndk-context", "ndk-sys", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -132,7 +132,7 @@ dependencies = [ "polling", "rustix 0.38.44", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -490,7 +490,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -599,7 +599,7 @@ dependencies = [ "ndk-sys", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -991,6 +991,7 @@ dependencies = [ "glutin", "glutin-winit", "raw-window-handle", + "thiserror 2.0.17", "winit", ] @@ -1163,7 +1164,7 @@ dependencies = [ "log", "memmap2", "rustix 0.38.44", - "thiserror", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -1206,7 +1207,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", ] [[package]] @@ -1220,6 +1230,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tiny-skia" version = "0.11.4" diff --git a/Cargo.toml b/Cargo.toml index 40da7db..5903f3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ gl = "0.14.0" glutin = "0.32.3" glutin-winit = "0.5.0" raw-window-handle = "0.6.2" +thiserror = "2.0.17" winit = "0.30.12" [build-dependencies] diff --git a/src/app/app.rs b/src/app/app.rs index 0146165..e5411b6 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -1,62 +1,21 @@ -use std::num::NonZero; -use glutin::config::{ ConfigTemplateBuilder, GetGlConfig }; -use glutin::context::PossiblyCurrentContext; -use glutin::display::GetGlDisplay; -use glutin::prelude::{GlDisplay, NotCurrentGlContext, PossiblyCurrentGlContext}; -use glutin::surface::{GlSurface, Surface, SwapInterval, WindowSurface}; -use glutin_winit::{DisplayBuilder, GlWindow}; use winit::application::ApplicationHandler; use winit::event::KeyEvent; use winit::keyboard::{Key, NamedKey}; -use winit::window::{ Window, WindowAttributes }; +use winit::window::WindowAttributes; use winit::{event, window}; -use crate::app::gl::{gl_config_picker, gl_create_context}; -use crate::app::renderer::Renderer; -use crate::models::Scene; +use crate::backend::RenderBackend; -enum GlDisplayCreationState { - Builder(Box), - Init +pub struct App { + backend: B, } -struct AppState { - gl_surface: Surface, - window: Window, -} - -pub struct App { - gl_display: GlDisplayCreationState, - gl_context: Option, - template: ConfigTemplateBuilder, - - renderer: Option, - current_scene: Option, - - state: Option, -} - -impl App { +impl App { pub fn new() -> Self { - let template = ConfigTemplateBuilder::new() - .with_alpha_size(8) - .with_transparency(cfg!(target_os = "macos")); - - let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes())); - Self { - template, - gl_display: GlDisplayCreationState::Builder(Box::new(display_builder)), - gl_context: None, - renderer: None, - state: None, - current_scene: None, + backend: B::new(), } } - - pub fn set_current_scene(&mut self, current_scene: &str) { - self.current_scene = Some(current_scene.to_string()); - } } pub fn window_attributes() -> WindowAttributes { @@ -65,74 +24,13 @@ pub fn window_attributes() -> WindowAttributes { .with_title("Test Window") } -impl ApplicationHandler for App { +impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { - let (window, gl_config) = match &self.gl_display { - GlDisplayCreationState::Builder(builder) => { - let (window, gl_config) = match builder.clone().build( - event_loop, - self.template.clone(), - gl_config_picker - ) { - Ok((window, gl_config)) => (window.unwrap(), gl_config), - Err(e) => { - panic!("Encountered Error {:#?}", e); - } - }; - - self.gl_display = GlDisplayCreationState::Init; - self.gl_context = Some(gl_create_context(&window, &gl_config).treat_as_possibly_current()); - - (window, gl_config) - }, - GlDisplayCreationState::Init => { - let gl_config = self.gl_context.as_ref().unwrap().config(); - match glutin_winit::finalize_window(event_loop, window_attributes(), &gl_config) { - Ok(window) => (window, gl_config), - Err(e) => { - panic!("Todo: Handle error for {:#?}", e); - } - } - }, - }; - - let attrs = window - .build_surface_attributes(Default::default()) - .expect("Failed to build surface attributes"); - - let gl_surface = unsafe { - gl_config.display().create_window_surface(&gl_config, &attrs).unwrap() - }; - - let gl_context = self.gl_context.as_ref().unwrap(); - gl_context.make_current(&gl_surface).unwrap(); - - if self.renderer.is_none() { - let mut renderer = Renderer::new(&gl_context.display()); - - // renderer.upload_scene_data(&self.scene_data); - - println!("Built Renderer"); - - self.renderer.get_or_insert_with(|| renderer); - } - - if let Err(res) = gl_surface.set_swap_interval( - gl_context, - SwapInterval::Wait(NonZero::new(1).unwrap()) - ) { - eprintln!("Error setting vsync: {:?}", res) - } - - assert!(self.state.replace(AppState { gl_surface, window }).is_none()); + self.backend.resume(event_loop); } fn suspended(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { println!("Android window removed"); - self.state = None; - self.gl_context = Some( - self.gl_context.take().unwrap().make_not_current().unwrap().treat_as_possibly_current() - ); } fn window_event( @@ -143,18 +41,6 @@ impl ApplicationHandler for App { ) { match event { event::WindowEvent::Resized(size) if size.width != 0 && size.height != 0 => { - if let Some(AppState { gl_surface, window: _ }) = &self.state { - let gl_context = self.gl_context.as_ref().unwrap(); - - gl_surface.resize( - gl_context, - NonZero::new(size.width).unwrap(), - NonZero::new(size.height).unwrap() - ); - - let renderer = self.renderer.as_ref().unwrap(); - renderer.resize(size.width as i32, size.height as i32); - } }, event::WindowEvent::CloseRequested | event::WindowEvent::KeyboardInput { event: KeyEvent { logical_key: Key::Named(NamedKey::Escape), .. }, .. } => { @@ -165,30 +51,17 @@ impl ApplicationHandler for App { } fn exiting(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { - let _gl_display = self.gl_context.take().unwrap().display(); - - self.state = None; - - #[cfg(egl_backend)] - #[allow(irrefutable_let_patterns)] - if let glutin::display::Display::Egl(display) = _gl_display { - unsafe { - display.terminate(); - } - } + // let _gl_display = self.gl_context.take().unwrap().display(); + // + // #[cfg(egl_backend)] + // #[allow(irrefutable_let_patterns)] + // if let glutin::display::Display::Egl(display) = _gl_display { + // unsafe { + // display.terminate(); + // } + // } } fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { - if let Some(AppState { gl_surface, window }) = self.state.as_ref() { - let gl_context = self.gl_context.as_ref().unwrap(); - let renderer = self.renderer.as_ref().unwrap(); - - if let Some(current_scene) = &self.current_scene { - renderer.render(current_scene); - } - - window.request_redraw(); - gl_surface.swap_buffers(gl_context).unwrap(); - } } } diff --git a/src/app/camera.rs b/src/app/camera.rs new file mode 100644 index 0000000..b979b64 --- /dev/null +++ b/src/app/camera.rs @@ -0,0 +1,3 @@ +pub struct Camera { + +} diff --git a/src/app/light.rs b/src/app/light.rs new file mode 100644 index 0000000..1f7c88c --- /dev/null +++ b/src/app/light.rs @@ -0,0 +1,3 @@ +pub struct Light { + +} diff --git a/src/app/renderer.rs b/src/app/renderer.rs deleted file mode 100644 index ad496a3..0000000 --- a/src/app/renderer.rs +++ /dev/null @@ -1,199 +0,0 @@ -use glutin::prelude::GlDisplay; -use crate::app::gl; -use crate::models::{self, GpuMesh, Scene, ShaderSource}; -use crate::shaders::Shader; -use std::collections::HashMap; -use std::ops::Deref; -use std::ffi::{ CStr, CString }; - -fn get_gl_string(gl: &gl::Gl, variant: gl::types::GLenum) -> Option<&'static CStr>{ - unsafe { - let s = gl.GetString(variant); - (!s.is_null()).then(|| CStr::from_ptr(s.cast())) - } -} - -pub struct Renderer { - gl: std::rc::Rc, - shaders: HashMap, - scenes: HashMap -} - -impl Renderer { - pub fn new(gl_display: &D) -> Self { - let gl = gl::Gl::load_with(|symbol| { - let symbol = CString::new(symbol).unwrap(); - gl_display.get_proc_address(symbol.as_c_str()).cast() - }); - - if let Some(renderer) = get_gl_string(&gl, gl::RENDERER) { - println!("Running on {}", renderer.to_string_lossy()); - } - - if let Some(version) = get_gl_string(&gl, gl::VERSION) { - println!("OpenGL version {}", version.to_string_lossy()); - } - - if let Some(shaders_version) = get_gl_string(&gl, gl::SHADING_LANGUAGE_VERSION) { - println!("Shaders version on {}", shaders_version.to_string_lossy()); - } - - Self { - gl: std::rc::Rc::new(gl), - shaders: HashMap::new(), - scenes: HashMap::new() - } - } - - pub fn upload_mesh(&self, mesh: &models::Mesh) -> GpuMesh { - unsafe { - let gl = &self.gl; - - let mut vao = std::mem::zeroed(); - gl.GenVertexArrays(1, &mut vao); - gl.BindVertexArray(vao); - - let mut vbo = std::mem::zeroed(); - gl.GenBuffers(1, &mut vbo); - gl.BindBuffer(gl::ARRAY_BUFFER, vbo); - let vertex_data = mesh.vertices - .iter() - .map(|v| { - [v.position.x, v.position.y, v.position.z, 1.0, 0.1, 0.1] - }) - .collect::>(); - - gl.BufferData( - gl::ARRAY_BUFFER, - (vertex_data.len() * std::mem::size_of::<[f32; 6]>()) as gl::types::GLsizeiptr, - vertex_data.as_ptr() as *const _, - gl::STATIC_DRAW, - ); - - let pos_attrib = 0; - let color_attrib = 1; - - gl.VertexAttribPointer( - pos_attrib as gl::types::GLuint, - 3, - gl::FLOAT, - 0, - 6 * std::mem::size_of::() as gl::types::GLsizei, - std::ptr::null() - ); - - gl.VertexAttribPointer( - color_attrib as gl::types::GLuint, - 3, - gl::FLOAT, - 0, - 6 * std::mem::size_of::() as gl::types::GLsizei, - (3 * std::mem::size_of::()) as *const _, - ); - - gl.EnableVertexAttribArray(pos_attrib as gl::types::GLuint); - gl.EnableVertexAttribArray(color_attrib as gl::types::GLuint); - - GpuMesh { - vao, - vbo, - _ebo: None, - vertex_count: mesh.vertices.len() as i32, - _index_count: None - } - } - } - - fn compile_shader(gl: &gl::Gl, shader: gl::types::GLenum, source: &[u8]) -> gl::types::GLuint { - unsafe { - let shader = gl.CreateShader(shader); - gl.ShaderSource(shader, 1, [source.as_ptr().cast()].as_ptr(), std::ptr::null()); - gl.CompileShader(shader); - shader - } - } - - pub fn create_shader(&self, shader_src: &ShaderSource) -> Shader { - unsafe { - let vertex_shader = Renderer::compile_shader(&self.gl, gl::VERTEX_SHADER, shader_src.vertex_src); - let fragment_shader = Renderer::compile_shader(&self.gl, gl::FRAGMENT_SHADER, shader_src.fragment_src); - - let program = self.gl.CreateProgram(); - - self.gl.AttachShader(program, vertex_shader); - self.gl.AttachShader(program, fragment_shader); - - self.gl.LinkProgram(program); - - self.gl.DeleteShader(vertex_shader); - self.gl.DeleteShader(fragment_shader); - - Shader { - program, - gl: self.gl.clone() - } - } - } - - pub fn add_shader(&mut self, name: &str, shader: gl::types::GLuint) { - if let None = self.shaders.get(name) { - self.shaders.insert(name.to_string(), shader); - } - } - - pub fn get_shader(&self, name: &str) -> Option<&gl::types::GLuint> { - self.shaders.get(name) - } - - pub fn remove_shader(&mut self, name: &str) { - self.shaders.remove(name); - } - - pub fn resize(&self, width: i32, height: i32) { - unsafe { - self.gl.Viewport(0, 0, width, height); - } - } - - pub fn draw_object(&self, obj: &GpuMesh, shader: gl::types::GLuint) { - unsafe { - self.UseProgram(shader); - - self.BindBuffer(gl::ARRAY_BUFFER, obj.vbo); - self.BindVertexArray(obj.vao); - - self.DrawArrays(gl::TRIANGLES, 0, obj.vertex_count); - } - } - - pub fn render(&self, current_scene: &String) { - unsafe { - self.ClearColor(1.0, 1.0, 1.0, 0.9); - self.Clear(gl::COLOR_BUFFER_BIT); - } - - if let Some(scene) = self.scenes.get(current_scene) { - scene.models.iter().for_each(|(obj, shader)| { - self.draw_object(obj, *shader); - }); - } else { - eprintln!("Unknown Scene Requested: {}", current_scene); - } - } -} - -impl Deref for Renderer { - type Target = gl::Gl; - - fn deref(&self) -> &Self::Target { - &self.gl - } -} - -impl Drop for Renderer { - fn drop(&mut self) { - unsafe { - self.shaders.values().for_each(|shader| self.gl.DeleteProgram(*shader)); - } - } -} diff --git a/src/app/scene.rs b/src/app/scene.rs new file mode 100644 index 0000000..4ba472d --- /dev/null +++ b/src/app/scene.rs @@ -0,0 +1,36 @@ +use std::collections::HashMap; + +use crate::{app::{camera::Camera, light::Light}, assets, backend::{backend, opengl::GlDrawCommand}, handles, models::Model}; + +pub struct Scene { + pub light: Light, + pub camera: Camera, + pub models: Vec, + pub materials: HashMap +} + +impl Scene { + pub fn new() -> Self { + Self { + light: Light {}, + camera: Camera {}, + materials: HashMap::new(), + models: vec![] + } + } + + pub fn render(&self, backend: &impl backend::RenderBackend) { + for model in &self.models { + for (mesh, mtl) in &model.parts { + let mtl = &self.materials[&mtl]; + let draw_cmd = GlDrawCommand { + mesh: *mesh, + shader: mtl.shader, + uniforms: mtl.to_uniforms(), + textures: None, + }; + + } + } + } +} diff --git a/src/assets/error.rs b/src/assets/error.rs new file mode 100644 index 0000000..b161eff --- /dev/null +++ b/src/assets/error.rs @@ -0,0 +1,33 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ParseError { + #[error("\n expected {expected} values, got {got}")] + TooFewValues { expected: usize, got: usize }, + + #[error("\n expected {expected} values, got {got}")] + TooManyValues { expected: usize, got: usize }, + + #[error("\n invalid float: {0}")] + InvalidFloat(#[from] std::num::ParseFloatError), + + #[error("\n invalid value: {0}")] + InvalidValue(#[from] Box), + + #[error("\n unknown prefix: {0}")] + UnknownPrefix(String), +} + +#[derive(Debug, Error)] +pub enum AssetError { + #[error("\n Parse in {file} at line {line}: {source}")] + Parse { + file: String, + line: usize, + #[source] + source: ParseError + }, + + #[error("I/O: {0}")] + IO(#[from] std::io::Error), +} diff --git a/src/assets/mtl.rs b/src/assets/mtl.rs new file mode 100644 index 0000000..b95edb8 --- /dev/null +++ b/src/assets/mtl.rs @@ -0,0 +1,143 @@ +use crate::{assets::{error::AssetError, parser::parse_args}, backend::opengl::Uniform, math::Vec3, shaders::ShaderHandle}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct TextureId(pub u32); + +#[derive(Clone, Debug, Default)] +pub struct Material { + pub name: String, + pub shader: ShaderHandle, + + pub ambient: Vec3, + pub diffuse: Vec3, + pub specular: Vec3, + pub emissive: Vec3, + pub shininess: f32, + pub opacity: f32, + + pub diffuse_texture: Option, + pub normal_texture: Option, + pub specular_texture: Option, +} + +impl Material { + pub fn new() -> Self { + Default::default() + } + + pub fn to_uniforms(&self) -> [Uniform; 8] { + [ + Uniform::Vec3(c"u_ambient".into(), self.ambient), + Uniform::Vec3(c"u_diffuse".into(), self.diffuse), + Uniform::Vec3(c"u_specular".into(), self.specular), + Uniform::Float(c"u_shininess".into(), self.shininess), + Uniform::None, + Uniform::None, + Uniform::None, + Uniform::None, + ] + } +} + +const NEW_MATERIAL_DELIMITER: &str = "newmtl "; + +pub fn load_materials(path: &str) -> Result, AssetError> { + let content = std::fs::read_to_string(path)?; + let mut materials = vec![]; + let mut current_material: Option = None; + + for (i, line) in content.lines().enumerate() { + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + + if line.starts_with(NEW_MATERIAL_DELIMITER) { + if let Some(mat) = current_material.take() { + materials.push(mat); + } + + let mat_name = line.strip_prefix(NEW_MATERIAL_DELIMITER).unwrap().trim(); + let mut material = Material::new(); + + material.name = mat_name.to_string(); + current_material = Some(material); + + } else if let Some(ref mut material) = current_material { + let delim = " "; + let i = i + 1; + + if let Some((key, values)) = line.split_once(' ') { + match key.trim() { + "Ka" => { + match parse_args::<3, f32>(values, delim) { + Ok(v) => material.ambient = Vec3(v), + Err(e) => return Err(AssetError::Parse { + file: path.to_string(), + line: i, + source: e, + }) + } + }, + "Kd" => { + match parse_args::<3, f32>(values, delim) { + Ok(v) => material.diffuse = Vec3(v), + Err(e) => return Err(AssetError::Parse { + file: path.to_string(), + line: i, + source: e, + }) + } + }, + "Ks" => { + match parse_args::<3, f32>(values, delim) { + Ok(v) => material.specular = Vec3(v), + Err(e) => return Err(AssetError::Parse { + file: path.to_string(), + line: i, + source: e, + }) + } + }, + "Ke" => { + match parse_args::<3, f32>(values, delim) { + Ok(v) => material.emissive = Vec3(v), + Err(e) => return Err(AssetError::Parse { + file: path.to_string(), + line: i, + source: e, + }) + } + } + "Ns" => { + match parse_args::<1, f32>(values, delim) { + Ok(v) => material.shininess = v[0], + Err(e) => return Err(AssetError::Parse { + file: path.to_string(), + line: i, + source: e, + }) + } + }, + "d" => { + match parse_args::<1, f32>(values, delim) { + Ok(v) => material.opacity = v[0], + Err(e) => return Err(AssetError::Parse { + file: path.to_string(), + line: i, + source: e, + }) + } + }, + _ => {} + } + } + } + } + + if let Some(mat) = current_material { + materials.push(mat); + } + + Ok(materials) +} diff --git a/src/assets/obj.rs b/src/assets/obj.rs new file mode 100644 index 0000000..6dfe99b --- /dev/null +++ b/src/assets/obj.rs @@ -0,0 +1,105 @@ +use crate::{assets::parser::parse_args, math::{ Vec2, Vec3 }}; +use super::types; +use super::error::AssetError; + +pub fn load_obj(path: &str) -> Result { + let mut obj_data = types::ObjData::new(); + let content = std::fs::read_to_string(path)?; + let mut current_name = String::new(); + + let mut current_mash = types::ObjMesh { + name: String::new(), + material_name: None, + faces: vec![] + }; + + let delim = " "; + + for (i, line) in content.lines().enumerate() { + let i = i + 1; + let line = line.trim(); + if let Some((prefix, values)) = line.split_once(' ') { + match prefix.trim() { + "v" => { + match parse_args::<3, f32>(values, delim) { + Ok(vec) => obj_data.vertices.push(Vec3(vec)), + Err(e) => return Err(AssetError::Parse { + file: path.to_string(), + line: i, + source: e, + }) + } + }, + "vt" => { + match parse_args::<2, f32>(values, delim) { + Ok(vec) => obj_data.tex_coords.push(Vec2(vec)), + Err(e) => return Err(AssetError::Parse { + file: path.to_string(), + line: i, + source: e, + }) + } + }, + "vn" => { + match parse_args::<3, f32>(values, delim) { + Ok(vec) => obj_data.normals.push(Vec3(vec)), + Err(e) => return Err(AssetError::Parse { + file: path.to_string(), + line: i, + source: e, + }) + } + }, + "o" | "g" => { + current_name = values.to_string(); + current_mash.name = current_name.clone(); + } + "usemtl" => { + if !current_mash.faces.is_empty() { + obj_data.meshes.push(current_mash); + } + + let material_name = Some(values.to_string()); + current_mash = types::ObjMesh { + name: current_name.clone(), + material_name, + faces: vec![] + }; + }, + "mtllib" => { + obj_data.material_lib = Some(values.to_string()); + } + "f" => { + let mut current_face = types::ObjFace { indices: vec![] }; + let mut line_split = values.split_whitespace(); + + while let Some(val) = line_split.next() { + let face_index: [u32; 3] = parse_args::<3, u32>(val, "/") + .map_err(|e| AssetError::Parse { + file: path.to_string(), + line: i, + source: e + })?; + + // OBJ indices are 1-based, convert to 0-based + let vertex_index = types::VertexIndex { + position: face_index[0] - 1, + tex_coord: if face_index[1] != 0 { Some(face_index[1] - 1) } else { None }, + normal: if face_index[2] != 0 { Some(face_index[2] - 1) } else { None }, + }; + + current_face.indices.push(vertex_index); + } + current_mash.faces.push(current_face); + } + _ => {} + } + } + } + + if !current_mash.faces.is_empty() { + obj_data.meshes.push(current_mash); + } + + Ok(obj_data) +} diff --git a/src/assets/parser.rs b/src/assets/parser.rs new file mode 100644 index 0000000..c55a971 --- /dev/null +++ b/src/assets/parser.rs @@ -0,0 +1,62 @@ +use std::str::FromStr; + +use crate::assets::error::ParseError; + +pub fn parse_args( + values: &str, + delim: &str +) -> Result<[T; N], ParseError> + where + T: FromStr + Copy + Default, + T::Err: std::error::Error + 'static + Send + Sync +{ + let mut result = [T::default(); N]; + let mut i = 0; + + let values = values.split(delim); + + for v in values { + if i < N { + let parsed = if !v.is_empty() { + v.parse::().map_err(|e| {ParseError::InvalidValue(Box::new(e))})? + } else { + Default::default() + }; + result[i] = parsed; + } + i += 1; + } + + if i > N { + Err(ParseError::TooManyValues{ expected: N, got: i }) + } else if i < N { + Err(ParseError::TooFewValues{ expected: N, got: i }) + } else { + Ok(result) + } +} + +pub fn parse_all_args( + values: &str, + delim: &str +) -> Result, ParseError> + where + T: FromStr + Copy + Default, + T::Err: std::error::Error + 'static + Send + Sync +{ + let mut result: Vec = Vec::new(); + let mut i = 0; + + let values = values.split(delim); + + for v in values { + let parsed = if !v.is_empty() { + v.parse::().map_err(|e| {ParseError::InvalidValue(Box::new(e))})? + } else { + Default::default() + }; + result[i] = parsed; + i += 1; + } + Ok(result) +} diff --git a/src/assets/types.rs b/src/assets/types.rs new file mode 100644 index 0000000..2657e07 --- /dev/null +++ b/src/assets/types.rs @@ -0,0 +1,41 @@ +use crate::math::{ Vec2, Vec3 }; + +#[derive(Debug)] +pub struct ObjData { + pub normals: Vec, + pub vertices: Vec, + pub tex_coords: Vec, + pub meshes: Vec, + pub material_lib: Option +} + +#[derive(Debug)] +pub struct ObjMesh { + pub name: String, + pub material_name: Option, + pub faces: Vec, +} + +#[derive(Debug)] +pub struct ObjFace { + pub indices: Vec, +} + +impl ObjData { + pub fn new() -> Self { + Self { + normals: vec![], + vertices: vec![], + tex_coords: vec![], + meshes: vec![], + material_lib: None + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct VertexIndex { + pub position: u32, + pub tex_coord: Option, + pub normal: Option +} diff --git a/src/backend/backend.rs b/src/backend/backend.rs new file mode 100644 index 0000000..9fcf311 --- /dev/null +++ b/src/backend/backend.rs @@ -0,0 +1,33 @@ +use crate::shaders; + +pub trait RenderBackend { + type ShaderSource; + type DrawCommand; + fn new() -> Self where Self: Sized; + + fn resume(&mut self, event_loop: &winit::event_loop::ActiveEventLoop); + fn resize(&mut self, width: u32, height: u32); + fn draw(&self, cmd: &Self::DrawCommand); + fn suspend(&mut self); + + // // Frame + // fn begin_frame(&mut self) -> FrameContext; + // fn end_frame(&mut self); + // + // // Resources + fn create_shader(&mut self, desc: Self::ShaderSource) -> Result; + // fn create_buffer(&mut self, data: &[u8], usage: BufferUsage) -> BufferHandle; + // fn create_texture(&mut self, desc: TextureDescriptor) -> TextureHandle; + // + // // Material = shader + uniforms + textures bundled + // fn create_material(&mut self, shader: ShaderHandle) -> MaterialHandle; + // fn set_material_param(&mut self, material: MaterialHandle, name: &str, value: UniformValue); +} + +// pub struct FrameContext<'a> { +// backend: &'a mut dyn RenderBackend, +// } +// +// impl FrameContext<'_> { +// pub fn draw(&mut self, mesh: &Mesh, material: MaterialHandle, transform: Mat4); +// } diff --git a/src/backend/gpu/mesh.rs b/src/backend/gpu/mesh.rs new file mode 100644 index 0000000..9513ab0 --- /dev/null +++ b/src/backend/gpu/mesh.rs @@ -0,0 +1,7 @@ +pub struct GpuMesh { + pub vao: gl::types::GLuint, + pub vbo: gl::types::GLuint, + pub ebo: Option, + pub vertex_count: i32, + pub index_count: Option, +} diff --git a/src/app/gl.rs b/src/backend/opengl/gl.rs similarity index 80% rename from src/app/gl.rs rename to src/backend/opengl/gl.rs index 5766115..8032198 100644 --- a/src/app/gl.rs +++ b/src/backend/opengl/gl.rs @@ -2,7 +2,10 @@ #![allow(unsafe_op_in_unsafe_fn)] include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); +use std::ffi::CString; + pub use Gles2 as Gl; +pub use crate::math; use glutin::config::{Config, GlConfig}; use glutin::context::{ContextApi, ContextAttributesBuilder, NotCurrentContext, Version}; @@ -11,6 +14,25 @@ use glutin::prelude::GlDisplay; use raw_window_handle::HasWindowHandle; use winit::window::Window; +use crate::handles::MeshHandle; +use crate::shaders::ShaderHandle; + +pub enum Uniform { + Float(CString, f32), + Vec3(CString, math::Vec3), + Vec4(CString, math::Vec4), + Mat4(CString, math::Mat4), + Int(CString, i32), + None, +} + +pub struct GlDrawCommand { + pub mesh: MeshHandle, + pub shader: ShaderHandle, + pub uniforms: [Uniform; 8], + pub textures: Option>, +} + pub fn gl_config_picker(configs: Box + '_>) -> Config { configs .reduce(|accum, config| { @@ -50,4 +72,3 @@ pub fn gl_create_context(window: &Window, gl_config: &Config) -> NotCurrentConte }) } } - diff --git a/src/backend/opengl/gl_backend.rs b/src/backend/opengl/gl_backend.rs new file mode 100644 index 0000000..97b3d36 --- /dev/null +++ b/src/backend/opengl/gl_backend.rs @@ -0,0 +1,163 @@ +use std::collections::HashMap; +use std::num::NonZero; +use glutin::config::GetGlConfig; +use glutin::display::GetGlDisplay; +use glutin::prelude::{GlDisplay, NotCurrentGlContext, PossiblyCurrentGlContext}; +use glutin::surface::{GlSurface, SwapInterval}; +use glutin_winit::GlWindow; +use winit::window::Window; +use crate::backend::gpu; + +use crate::{backend, handles, shaders}; +use crate::app::app::window_attributes; +use crate::backend::opengl::{self, GlRenderer}; + +enum GlDisplayCreationState { + Builder(glutin_winit::DisplayBuilder), + Init +} + +struct AppState { + gl_surface: glutin::surface::Surface, + window: Window, +} + +pub struct GlBackend { + gl_display: GlDisplayCreationState, + gl_context: Option, + gl: Option, + template: glutin::config::ConfigTemplateBuilder, + state: Option, + meshes: HashMap, + shaders: HashMap, +} + +impl backend::RenderBackend for GlBackend { + type DrawCommand = opengl::GlDrawCommand; + type ShaderSource = opengl::GlShaderSource; + + fn new() -> Self where Self: Sized { + let template = glutin::config::ConfigTemplateBuilder::new() + .with_alpha_size(8) + .with_transparency(cfg!(target_os = "macos")); + + let display_builder = glutin_winit::DisplayBuilder::new().with_window_attributes(Some(window_attributes())); + + Self { + gl_display: GlDisplayCreationState::Builder(display_builder), + gl_context: None, + state: None, + gl: None, + meshes: HashMap::new(), + shaders: HashMap::new(), + template + } + } + + fn resume(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + let (window, gl_config) = match &self.gl_display { + GlDisplayCreationState::Builder(builder) => { + let (window, gl_config) = match builder.clone().build( + event_loop, + self.template.clone(), + opengl::gl_config_picker + ) { + Ok((window, gl_config)) => (window.unwrap(), gl_config), + Err(e) => { + eprintln!("Error: {:#?}", e); + return; + } + }; + + self.gl_display = GlDisplayCreationState::Init; + self.gl_context = Some(opengl::gl_create_context(&window, &gl_config).treat_as_possibly_current()); + + (window, gl_config) + }, + GlDisplayCreationState::Init => { + let gl_config = self.gl_context.as_ref().unwrap().config(); + match glutin_winit::finalize_window(event_loop, window_attributes(), &gl_config) { + Ok(window) => (window, gl_config), + Err(e) => { + eprintln!("Error: {:#?}", e); + return; + } + } + }, + }; + + let attrs = window + .build_surface_attributes(Default::default()) + .expect("Failed to build surface attributes"); + + let gl_surface = unsafe { + gl_config.display().create_window_surface(&gl_config, &attrs).unwrap() + }; + + let gl_context = self.gl_context.as_ref().unwrap(); + gl_context.make_current(&gl_surface).unwrap(); + + if self.gl.is_none() { + let renderer = opengl::GlRenderer::new( + &gl_context.display() + ); + + self.gl = Some(renderer); + } + + if let Err(res) = gl_surface.set_swap_interval( + gl_context, + SwapInterval::Wait(NonZero::new(1).unwrap()) + ) { + eprintln!("Error setting vsync: {:?}", res) + } + + assert!(self.state.replace(AppState { gl_surface, window }).is_none()); + } + + fn suspend(&mut self) { + self.state = None; + self.gl_context = Some( + self.gl_context.take().unwrap().make_not_current().unwrap().treat_as_possibly_current() + ); + } + + fn resize(&mut self, width: u32, height: u32) { + if let Some(AppState { gl_surface, window: _ }) = &self.state && self.gl.is_some() { + let gl_context = self.gl_context.as_ref().unwrap(); + + gl_surface.resize( + gl_context, + NonZero::new(width).unwrap(), + NonZero::new(height).unwrap() + ); + + unsafe { + self.gl + .as_ref() + .unwrap() + .Viewport(0, 0, width as i32, height as i32); + } + } + } + + fn create_shader(&mut self, desc: Self::ShaderSource) -> Result { + if let Some(gl) = &self.gl { + let shader = gl.create_shader(&desc); + let shader_handle = self.shaders.len() as u32; + self.shaders.insert(shader_handle, shader); + Ok(shader_handle) + } else { + todo!("implement error type for GlBackend") + } + } + + fn draw(&self, cmd: &Self::DrawCommand) { + let mesh = self.meshes.get(&cmd.mesh); + let shader = self.shaders.get(&cmd.shader); + + if let (Some(mesh), Some(shader)) = (mesh, shader) { + + } + } +} diff --git a/src/backend/opengl/gl_renderer.rs b/src/backend/opengl/gl_renderer.rs new file mode 100644 index 0000000..6db62d2 --- /dev/null +++ b/src/backend/opengl/gl_renderer.rs @@ -0,0 +1,240 @@ +use glutin::prelude::GlDisplay; +use crate::backend::opengl::{self, Uniform}; +use crate::math::{ Mat4, Vec3 }; +use crate::{geometry, backend::gpu::GpuMesh}; +use crate::shaders::Shader; +use std::ops::Deref; +use std::ffi::{ CStr, CString }; + +/// Render state containing camera and lighting information +pub struct RenderState { + pub projection: Mat4, + pub view: Mat4, + pub model: Mat4, + pub camera_pos: Vec3, + pub light_pos: Vec3, + pub light_color: Vec3, +} + +impl RenderState { + pub fn new(aspect_ratio: f32) -> Self { + let camera_pos = Vec3([0.0, 2.0, 5.0]); + Self { + projection: Mat4::perspective(45.0_f32.to_radians(), aspect_ratio, 0.1, 100.0), + view: Mat4::look_at( + camera_pos, + Vec3 ([0.0, 0.0, 0.0]), + Vec3 ([0.0, 1.0, 0.0]), + ), + model: Mat4::identity(), + camera_pos, + light_pos: Vec3([3.0, 5.0, 3.0]), + light_color: Vec3([1.0, 1.0, 1.0]), + } + } +} + +fn get_gl_string(gl: &opengl::Gl, variant: gl::types::GLenum) -> Option<&'static CStr>{ + unsafe { + let s = gl.GetString(variant); + (!s.is_null()).then(|| CStr::from_ptr(s.cast())) + } +} + +pub struct GlRenderer { + gl: std::rc::Rc, +} + +impl GlRenderer { + pub fn new(gl_display: &D) -> Self { + let gl = opengl::Gl::load_with(|symbol| { + let symbol = CString::new(symbol).unwrap(); + gl_display.get_proc_address(symbol.as_c_str()).cast() + }); + + if let Some(renderer) = get_gl_string(&gl, gl::RENDERER) { + println!("Running on {}", renderer.to_string_lossy()); + } + + if let Some(version) = get_gl_string(&gl, gl::VERSION) { + println!("OpenGL version {}", version.to_string_lossy()); + } + + if let Some(shaders_version) = get_gl_string(&gl, gl::SHADING_LANGUAGE_VERSION) { + println!("Shaders version on {}", shaders_version.to_string_lossy()); + } + + // Enable depth testing for 3D rendering + unsafe { + gl.Enable(gl::DEPTH_TEST); + gl.DepthFunc(gl::LESS); + } + + Self { + gl: std::rc::Rc::new(gl), + } + } + + pub fn upload_mesh(&self, mesh: &geometry::Mesh) -> GpuMesh { + unsafe { + let gl = &self.gl; + + let mut vao = std::mem::zeroed(); + gl.GenVertexArrays(1, &mut vao); + gl.BindVertexArray(vao); + + // Upload vertex data (position, normal, texcoord) + let mut vbo = std::mem::zeroed(); + gl.GenBuffers(1, &mut vbo); + gl.BindBuffer(gl::ARRAY_BUFFER, vbo); + + // Vertex layout: [pos.x, pos.y, pos.z, norm.x, norm.y, norm.z, tex.x, tex.y] + let vertex_data: Vec = mesh.vertices + .iter() + .flat_map(|v| [ + v.position.x(), v.position.y(), v.position.z(), + v.normal.x(), v.normal.y(), v.normal.z(), + v.tex_coord.x(), v.tex_coord.y(), + ]) + .collect(); + + gl.BufferData( + gl::ARRAY_BUFFER, + (vertex_data.len() * std::mem::size_of::()) as gl::types::GLsizeiptr, + vertex_data.as_ptr() as *const _, + gl::STATIC_DRAW, + ); + + let stride = 8 * std::mem::size_of::() as gl::types::GLsizei; + + // Position attribute (location = 0) + gl.VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, stride, std::ptr::null()); + gl.EnableVertexAttribArray(0); + + // Normal attribute (location = 1) + gl.VertexAttribPointer(1, 3, gl::FLOAT, gl::FALSE, stride, (3 * std::mem::size_of::()) as *const _); + gl.EnableVertexAttribArray(1); + + // Texcoord attribute (location = 2) + gl.VertexAttribPointer(2, 2, gl::FLOAT, gl::FALSE, stride, (6 * std::mem::size_of::()) as *const _); + gl.EnableVertexAttribArray(2); + + // Upload index data if available + let (ebo, index_count) = if !mesh.indices.is_empty() { + let mut ebo = std::mem::zeroed(); + gl.GenBuffers(1, &mut ebo); + gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); + gl.BufferData( + gl::ELEMENT_ARRAY_BUFFER, + (mesh.indices.len() * std::mem::size_of::()) as gl::types::GLsizeiptr, + mesh.indices.as_ptr() as *const _, + gl::STATIC_DRAW, + ); + (Some(ebo), Some(mesh.indices.len() as i32)) + } else { + (None, None) + }; + + gl.BindVertexArray(0); + + GpuMesh { + vao, + vbo, + ebo, + vertex_count: mesh.vertices.len() as i32, + index_count, + } + } + } + + fn compile_shader(gl: &opengl::Gl, shader: gl::types::GLenum, source: &[u8]) -> gl::types::GLuint { + unsafe { + let shader = gl.CreateShader(shader); + gl.ShaderSource(shader, 1, [source.as_ptr().cast()].as_ptr(), std::ptr::null()); + gl.CompileShader(shader); + shader + } + } + + pub fn create_shader(&self, shader_src: &opengl::GlShaderSource) -> Shader { + unsafe { + let vertex_shader = Self::compile_shader( + &self.gl, + gl::VERTEX_SHADER, + shader_src.vertex_src.as_bytes() + ); + let fragment_shader = Self::compile_shader( + &self.gl, + gl::FRAGMENT_SHADER, + shader_src.fragment_src.as_bytes() + ); + + let program = self.gl.CreateProgram(); + + self.gl.AttachShader(program, vertex_shader); + self.gl.AttachShader(program, fragment_shader); + + self.gl.LinkProgram(program); + + self.gl.DeleteShader(vertex_shader); + self.gl.DeleteShader(fragment_shader); + + Shader(program) + } + } + + pub fn apply_uniform(&self, shader: Shader, uniform: Uniform) { + unsafe { + match uniform { + Uniform::Float(name, value) => { + let location = self.GetUniformLocation(shader.0, name.as_ptr()); + self.Uniform1f(location, value); + } + Uniform::Int(name, value) => { + let location = self.GetUniformLocation(shader.0, name.as_ptr()); + self.Uniform1i(location, value); + } + Uniform::Vec3(name, value) => { + let location = self.GetUniformLocation(shader.0, name.as_ptr()); + self.Uniform3f(location, value[0], value[1], value[2]); + } + Uniform::Vec4(name, value) => { + let location = self.GetUniformLocation(shader.0, name.as_ptr()); + self.Uniform4f(location, value[0], value[1], value[2], value[3]); + } + Uniform::Mat4(name, value) => { + let location = self.GetUniformLocation(shader.0, name.as_ptr()); + self.UniformMatrix4fv(location, 1, gl::FALSE, value.as_ptr()); + } + Uniform::None => {} + } + } + } + + pub fn resize(&self, width: i32, height: i32) { + unsafe { + self.gl.Viewport(0, 0, width, height); + } + } + + pub fn draw_mesh(&self, mesh: &GpuMesh) { + unsafe { + self.BindVertexArray(mesh.vao); + + if let (Some(ebo), Some(count)) = (mesh.ebo, mesh.index_count) { + self.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); + self.DrawElements(gl::TRIANGLES, count, gl::UNSIGNED_INT, std::ptr::null()); + } else { + self.DrawArrays(gl::TRIANGLES, 0, mesh.vertex_count); + } + } + } +} + +impl Deref for GlRenderer { + type Target = opengl::Gl; + + fn deref(&self) -> &Self::Target { + &self.gl + } +} diff --git a/src/backend/opengl/gl_shader.rs b/src/backend/opengl/gl_shader.rs new file mode 100644 index 0000000..017ca68 --- /dev/null +++ b/src/backend/opengl/gl_shader.rs @@ -0,0 +1,4 @@ +pub struct GlShaderSource { + pub vertex_src: String, + pub fragment_src: String, +} diff --git a/src/models/mesh.rs b/src/geometry/mesh.rs similarity index 95% rename from src/models/mesh.rs rename to src/geometry/mesh.rs index 342e4ea..e2c9909 100644 --- a/src/models/mesh.rs +++ b/src/geometry/mesh.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::models::{VertexIndex, object::{ObjData, ObjMesh}}; +use crate::assets::types::{VertexIndex, ObjData, ObjMesh}; use super::Vertex; diff --git a/src/geometry/vertex.rs b/src/geometry/vertex.rs new file mode 100644 index 0000000..91f9b53 --- /dev/null +++ b/src/geometry/vertex.rs @@ -0,0 +1,8 @@ +use crate::math::{ Vec2, Vec3 }; + +#[derive(Debug)] +pub struct Vertex { + pub position: Vec3, + pub normal: Vec3, + pub tex_coord: Vec2, +} diff --git a/src/handles.rs b/src/handles.rs new file mode 100644 index 0000000..74c0187 --- /dev/null +++ b/src/handles.rs @@ -0,0 +1,5 @@ +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct MeshHandle(pub u32); + +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct MaterialHandle(pub u32); diff --git a/src/lib.rs b/src/lib.rs index 9c2b67d..258441f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,35 +1,78 @@ pub mod models { - pub mod mesh; - pub use mesh::*; - - pub mod vertex; - pub use vertex::*; pub mod model; pub use model::*; +} - pub mod scene; - pub use scene::*; +pub mod assets { + pub mod mtl; + pub mod obj; + pub mod types; + pub mod parser; - pub mod material; - pub use material::*; + pub mod error; +} - pub mod primitives; - pub use primitives::*; +pub mod geometry { + pub mod vertex; + pub use vertex::*; - pub mod object; + pub mod mesh; + pub use mesh::*; +} + +pub mod math { + pub use vector::*; + pub mod vector { + pub mod vec2; + pub mod vec3; + pub mod vec4; + + pub use vec2::*; + pub use vec3::*; + pub use vec4::*; + } + + pub use matrix::*; + pub mod matrix { + pub mod mat4; + pub use mat4::*; + } } pub mod app { - pub mod renderer; pub mod app; - pub mod gl; + pub mod light; + pub mod camera; + pub mod scene; } pub mod shaders { - pub mod shader; - pub use shader::*; + pub mod types; + pub use types::*; pub mod constants; pub use constants::*; } + +pub mod backend { + pub mod opengl { + pub mod gl_renderer; + pub use gl_renderer::*; + pub mod gl_backend; + pub use gl_backend::*; + pub mod gl_shader; + pub use gl_shader::*; + pub mod gl; + pub use gl::*; + } + pub mod gpu { + pub mod mesh; + pub use mesh::*; + } + + pub mod backend; + pub use backend::*; +} + +pub mod handles; diff --git a/src/main.rs b/src/main.rs index a3f1dc4..623b3f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ -use proj::{app::app::App, models::{ShaderSource, object::ObjData}, shaders::{FRAGMENT_SHADER_SOURCE, VERTEX_SHADER_SOURCE}}; +use proj::{ + app::app::App, assets, backend::opengl, shaders::{FRAGMENT_SHADER_SOURCE, VERTEX_SHADER_SOURCE} +}; use winit::event_loop::EventLoop; const HOUSE_OBJ_PATH: &str = "assets/models/small_house/model.obj"; @@ -6,31 +8,29 @@ const HOUSE_MAT_PATH: &str = "assets/models/small_house/materials.mtl"; fn main() { dotenvy::dotenv().ok(); + let event_loop = EventLoop::new() .expect("Should be able to have Event Loop"); - let shader_src = ShaderSource { - vertex_src: VERTEX_SHADER_SOURCE, - fragment_src: FRAGMENT_SHADER_SOURCE + let _shader_src = opengl::GlShaderSource { + vertex_src: VERTEX_SHADER_SOURCE.to_string(), + fragment_src: FRAGMENT_SHADER_SOURCE.to_string() }; - let obj_data = match ObjData::load_obj(HOUSE_OBJ_PATH) { + // Load OBJ and materials (CPU-side, before GL context) + let obj_data = match assets::obj::load_obj(HOUSE_OBJ_PATH) { Ok(o) => o, - Err(e) => return eprintln!("Error: {}", e), + Err(e) => return eprintln!("Error loading OBJ: {}", e), }; - println!("Done"); - - let materials = match ObjData::load_materials(HOUSE_MAT_PATH) { + let materials = match assets::mtl::load_materials(HOUSE_MAT_PATH) { Ok(m) => m, - Err(e) => return eprintln!("Error: {}", e), + Err(e) => return eprintln!("Error loading materials: {}", e), }; - dbg!(&materials); + println!("Loaded {} meshes, {} materials", obj_data.meshes.len(), materials.len()); - let mut app = App::new(); - - app.set_current_scene("home"); + let mut app = App::::new(); let event_res = event_loop.run_app(&mut app); diff --git a/src/models/primitives.rs b/src/math/matrix/mat4.rs similarity index 65% rename from src/models/primitives.rs rename to src/math/matrix/mat4.rs index ce98764..f80fb88 100644 --- a/src/models/primitives.rs +++ b/src/math/matrix/mat4.rs @@ -1,71 +1,4 @@ -use std::ops::Sub; - -#[derive(Clone, Copy, Debug, Default)] -#[repr(C)] -pub struct Vec2 { - pub x: f32, - pub y: f32, -} - -#[derive(Clone, Copy, Debug, Default)] -#[repr(C)] -pub struct Vec3 { - pub x: f32, - pub y: f32, - pub z: f32, -} - -impl Sub for Vec3 { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - Self { - x: self.x - rhs.x, - y: self.y - rhs.y, - z: self.z - rhs.z, - } - } -} - -impl Vec3 { - pub fn length(&self) -> f32 { - (self.x * self.x + self.y * self.y + self.z * self.z).sqrt() - } - - pub fn normalize(&mut self) -> Self { - let len = self.length(); - - if len == 0.0 { - return *self; - } - Self { - x: self.x / len, - y: self.y / len, - z: self.z / len, - } - } - - pub fn dot(&self, other: Vec3) -> f32 { - self.x * other.x + self.y * other.y + self.z * other.z - } - - pub fn cross(&self, other: Self) -> Self { - Self { - x: self.y * other.z - self.z * other.y, - y: self.z * other.x - self.x * other.z, - z: self.x * other.y - self.y * other.x, - } - } -} - -#[derive(Clone, Copy, Debug, Default)] -#[repr(C)] -pub struct Vec4 { - pub x: f32, - pub y: f32, - pub z: f32, - pub w: f32, -} +use crate::math::{ Vec3 }; #[repr(C)] #[derive(Clone, Copy, Debug)] @@ -161,14 +94,14 @@ impl Mat4 { pub fn look_at(eye: Vec3, target: Vec3, up: Vec3) -> Self { let f = (target - eye).normalize(); - let r = f.cross(up).normalize(); - let u = r.cross(f); + let r = f.cross(&up).normalize(); + let u = r.cross(&f); Self { data: [ - r.x, u.x, -f.x, 0.0, - r.y, u.y, -f.y, 0.0, - r.z, u.z, -f.z, 0.0, - -r.dot(eye), -u.dot(eye), f.dot(eye), 1.0, + r.x(), u.x(), -f.x(), 0.0, + r.y(), u.y(), -f.y(), 0.0, + r.z(), u.z(), -f.z(), 0.0, + -r.dot(&eye), -u.dot(&eye), f.dot(&eye), 1.0, ], } } diff --git a/src/math/vector/vec2.rs b/src/math/vector/vec2.rs new file mode 100644 index 0000000..4f4e277 --- /dev/null +++ b/src/math/vector/vec2.rs @@ -0,0 +1,155 @@ +use std::ops::{Add, Sub, Mul, Div, Neg, Index, IndexMut}; + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[repr(C)] +pub struct Vec2(pub [f32; 2]); + +impl Vec2 { + pub const fn new(x: f32, y: f32) -> Self { + Self([x, y]) + } + + pub const fn zero() -> Self { + Self([0.0, 0.0]) + } + + pub const fn one() -> Self { + Self([1.0, 1.0]) + } + + pub fn x(&self) -> f32 { + self.0[0] + } + + pub fn y(&self) -> f32 { + self.0[1] + } + + pub fn x_mut(&mut self) -> &mut f32 { + &mut self.0[0] + } + + pub fn y_mut(&mut self) -> &mut f32 { + &mut self.0[1] + } + + pub fn length_squared(&self) -> f32 { + self.0[0] * self.0[0] + self.0[1] * self.0[1] + } + + pub fn length(&self) -> f32 { + self.length_squared().sqrt() + } + + pub fn normalize(&self) -> Self { + let len = self.length(); + if len == 0.0 { + return *self; + } + Self([self.0[0] / len, self.0[1] / len]) + } + + pub fn dot(&self, other: &Self) -> f32 { + self.0[0] * other.0[0] + self.0[1] * other.0[1] + } + + pub fn as_ptr(&self) -> *const f32 { + self.0.as_ptr() + } + + pub fn as_mut_ptr(&mut self) -> *mut f32 { + self.0.as_mut_ptr() + } +} + +// Add operations +impl Add for Vec2 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self([self.0[0] + rhs.0[0], self.0[1] + rhs.0[1]]) + } +} + +impl Add for Vec2 { + type Output = Self; + + fn add(self, rhs: f32) -> Self::Output { + Self([self.0[0] + rhs, self.0[1] + rhs]) + } +} + +// Subtraction operations +impl Sub for Vec2 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self([self.0[0] - rhs.0[0], self.0[1] - rhs.0[1]]) + } +} + +impl Sub for Vec2 { + type Output = Self; + + fn sub(self, rhs: f32) -> Self::Output { + Self([self.0[0] - rhs, self.0[1] - rhs]) + } +} + +// Multiplication operations +impl Mul for Vec2 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + Self([self.0[0] * rhs.0[0], self.0[1] * rhs.0[1]]) + } +} + +impl Mul for Vec2 { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + Self([self.0[0] * rhs, self.0[1] * rhs]) + } +} + +// Division operations +impl Div for Vec2 { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output { + Self([self.0[0] / rhs.0[0], self.0[1] / rhs.0[1]]) + } +} + +impl Div for Vec2 { + type Output = Self; + + fn div(self, rhs: f32) -> Self::Output { + Self([self.0[0] / rhs, self.0[1] / rhs]) + } +} + +// Negation +impl Neg for Vec2 { + type Output = Self; + + fn neg(self) -> Self::Output { + Self([-self.0[0], -self.0[1]]) + } +} + +// Index operations +impl Index for Vec2 { + type Output = f32; + + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} + +impl IndexMut for Vec2 { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] + } +} diff --git a/src/math/vector/vec3.rs b/src/math/vector/vec3.rs new file mode 100644 index 0000000..55153a4 --- /dev/null +++ b/src/math/vector/vec3.rs @@ -0,0 +1,171 @@ +use std::ops::{Add, Sub, Mul, Div, Neg, Index, IndexMut}; + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[repr(C)] +pub struct Vec3(pub [f32; 3]); + +impl Vec3 { + pub const fn new(x: f32, y: f32, z: f32) -> Self { + Self([x, y, z]) + } + + pub const fn zero() -> Self { + Self([0.0, 0.0, 0.0]) + } + + pub const fn one() -> Self { + Self([1.0, 1.0, 1.0]) + } + + pub fn x(&self) -> f32 { + self.0[0] + } + + pub fn y(&self) -> f32 { + self.0[1] + } + + pub fn z(&self) -> f32 { + self.0[2] + } + + pub fn x_mut(&mut self) -> &mut f32 { + &mut self.0[0] + } + + pub fn y_mut(&mut self) -> &mut f32 { + &mut self.0[1] + } + + pub fn z_mut(&mut self) -> &mut f32 { + &mut self.0[2] + } + + pub fn length_squared(&self) -> f32 { + self.0[0] * self.0[0] + self.0[1] * self.0[1] + self.0[2] * self.0[2] + } + + pub fn length(&self) -> f32 { + self.length_squared().sqrt() + } + + pub fn normalize(&self) -> Self { + let len = self.length(); + if len == 0.0 { + return *self; + } + Self([self.0[0] / len, self.0[1] / len, self.0[2] / len]) + } + + pub fn dot(&self, other: &Self) -> f32 { + self.0[0] * other.0[0] + self.0[1] * other.0[1] + self.0[2] * other.0[2] + } + + pub fn cross(&self, other: &Self) -> Self { + Self([ + self.0[1] * other.0[2] - self.0[2] * other.0[1], + self.0[2] * other.0[0] - self.0[0] * other.0[2], + self.0[0] * other.0[1] - self.0[1] * other.0[0], + ]) + } + + pub fn as_ptr(&self) -> *const f32 { + self.0.as_ptr() + } + + pub fn as_mut_ptr(&mut self) -> *mut f32 { + self.0.as_mut_ptr() + } +} + +// Add operations +impl Add for Vec3 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self([self.0[0] + rhs.0[0], self.0[1] + rhs.0[1], self.0[2] + rhs.0[2]]) + } +} + +impl Add for Vec3 { + type Output = Self; + + fn add(self, rhs: f32) -> Self::Output { + Self([self.0[0] + rhs, self.0[1] + rhs, self.0[2] + rhs]) + } +} + +// Subtraction operations +impl Sub for Vec3 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self([self.0[0] - rhs.0[0], self.0[1] - rhs.0[1], self.0[2] - rhs.0[2]]) + } +} + +impl Sub for Vec3 { + type Output = Self; + + fn sub(self, rhs: f32) -> Self::Output { + Self([self.0[0] - rhs, self.0[1] - rhs, self.0[2] - rhs]) + } +} + +// Multiplication operations +impl Mul for Vec3 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + Self([self.0[0] * rhs.0[0], self.0[1] * rhs.0[1], self.0[2] * rhs.0[2]]) + } +} + +impl Mul for Vec3 { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + Self([self.0[0] * rhs, self.0[1] * rhs, self.0[2] * rhs]) + } +} + +// Division operations +impl Div for Vec3 { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output { + Self([self.0[0] / rhs.0[0], self.0[1] / rhs.0[1], self.0[2] / rhs.0[2]]) + } +} + +impl Div for Vec3 { + type Output = Self; + + fn div(self, rhs: f32) -> Self::Output { + Self([self.0[0] / rhs, self.0[1] / rhs, self.0[2] / rhs]) + } +} + +// Negation +impl Neg for Vec3 { + type Output = Self; + + fn neg(self) -> Self::Output { + Self([-self.0[0], -self.0[1], -self.0[2]]) + } +} + +// Index operations +impl Index for Vec3 { + type Output = f32; + + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} + +impl IndexMut for Vec3 { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] + } +} diff --git a/src/math/vector/vec4.rs b/src/math/vector/vec4.rs new file mode 100644 index 0000000..3dafca9 --- /dev/null +++ b/src/math/vector/vec4.rs @@ -0,0 +1,171 @@ +use std::ops::{Add, Sub, Mul, Div, Neg, Index, IndexMut}; + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[repr(C)] +pub struct Vec4(pub [f32; 4]); + +impl Vec4 { + pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self { + Self([x, y, z, w]) + } + + pub const fn zero() -> Self { + Self([0.0, 0.0, 0.0, 0.0]) + } + + pub const fn one() -> Self { + Self([1.0, 1.0, 1.0, 1.0]) + } + + pub fn x(&self) -> f32 { + self.0[0] + } + + pub fn y(&self) -> f32 { + self.0[1] + } + + pub fn z(&self) -> f32 { + self.0[2] + } + + pub fn w(&self) -> f32 { + self.0[3] + } + + pub fn x_mut(&mut self) -> &mut f32 { + &mut self.0[0] + } + + pub fn y_mut(&mut self) -> &mut f32 { + &mut self.0[1] + } + + pub fn z_mut(&mut self) -> &mut f32 { + &mut self.0[2] + } + + pub fn w_mut(&mut self) -> &mut f32 { + &mut self.0[3] + } + + pub fn length_squared(&self) -> f32 { + self.0[0] * self.0[0] + self.0[1] * self.0[1] + self.0[2] * self.0[2] + self.0[3] * self.0[3] + } + + pub fn length(&self) -> f32 { + self.length_squared().sqrt() + } + + pub fn normalize(&self) -> Self { + let len = self.length(); + if len == 0.0 { + return *self; + } + Self([self.0[0] / len, self.0[1] / len, self.0[2] / len, self.0[3] / len]) + } + + pub fn dot(&self, other: &Self) -> f32 { + self.0[0] * other.0[0] + self.0[1] * other.0[1] + self.0[2] * other.0[2] + self.0[3] * other.0[3] + } + + pub fn as_ptr(&self) -> *const f32 { + self.0.as_ptr() + } + + pub fn as_mut_ptr(&mut self) -> *mut f32 { + self.0.as_mut_ptr() + } +} + +// Add operations +impl Add for Vec4 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self([self.0[0] + rhs.0[0], self.0[1] + rhs.0[1], self.0[2] + rhs.0[2], self.0[3] + rhs.0[3]]) + } +} + +impl Add for Vec4 { + type Output = Self; + + fn add(self, rhs: f32) -> Self::Output { + Self([self.0[0] + rhs, self.0[1] + rhs, self.0[2] + rhs, self.0[3] + rhs]) + } +} + +// Subtraction operations +impl Sub for Vec4 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self([self.0[0] - rhs.0[0], self.0[1] - rhs.0[1], self.0[2] - rhs.0[2], self.0[3] - rhs.0[3]]) + } +} + +impl Sub for Vec4 { + type Output = Self; + + fn sub(self, rhs: f32) -> Self::Output { + Self([self.0[0] - rhs, self.0[1] - rhs, self.0[2] - rhs, self.0[3] - rhs]) + } +} + +// Multiplication operations +impl Mul for Vec4 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + Self([self.0[0] * rhs.0[0], self.0[1] * rhs.0[1], self.0[2] * rhs.0[2], self.0[3] * rhs.0[3]]) + } +} + +impl Mul for Vec4 { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + Self([self.0[0] * rhs, self.0[1] * rhs, self.0[2] * rhs, self.0[3] * rhs]) + } +} + +// Division operations +impl Div for Vec4 { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output { + Self([self.0[0] / rhs.0[0], self.0[1] / rhs.0[1], self.0[2] / rhs.0[2], self.0[3] / rhs.0[3]]) + } +} + +impl Div for Vec4 { + type Output = Self; + + fn div(self, rhs: f32) -> Self::Output { + Self([self.0[0] / rhs, self.0[1] / rhs, self.0[2] / rhs, self.0[3] / rhs]) + } +} + +// Negation +impl Neg for Vec4 { + type Output = Self; + + fn neg(self) -> Self::Output { + Self([-self.0[0], -self.0[1], -self.0[2], -self.0[3]]) + } +} + +// Index operations +impl Index for Vec4 { + type Output = f32; + + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} + +impl IndexMut for Vec4 { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] + } +} diff --git a/src/math/vector/vector.rs b/src/math/vector/vector.rs new file mode 100644 index 0000000..d47bb2c --- /dev/null +++ b/src/math/vector/vector.rs @@ -0,0 +1,68 @@ +use std::ops::Sub; + +#[derive(Clone, Copy, Debug, Default)] +#[repr(C)] +pub struct Vec2 { + pub x: f32, + pub y: f32, +} + +#[derive(Clone, Copy, Debug, Default)] +#[repr(C)] +pub struct Vec3 { + pub x: f32, + pub y: f32, + pub z: f32, +} + +impl Sub for Vec3 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self { + x: self.x - rhs.x, + y: self.y - rhs.y, + z: self.z - rhs.z, + } + } +} + +impl Vec3 { + pub fn length(&self) -> f32 { + (self.x * self.x + self.y * self.y + self.z * self.z).sqrt() + } + + pub fn normalize(&mut self) -> Self { + let len = self.length(); + + if len == 0.0 { + return *self; + } + Self { + x: self.x / len, + y: self.y / len, + z: self.z / len, + } + } + + pub fn dot(&self, other: Vec3) -> f32 { + self.x * other.x + self.y * other.y + self.z * other.z + } + + pub fn cross(&self, other: Self) -> Self { + Self { + x: self.y * other.z - self.z * other.y, + y: self.z * other.x - self.x * other.z, + z: self.x * other.y - self.y * other.x, + } + } +} + +#[derive(Clone, Copy, Debug, Default)] +#[repr(C)] +pub struct Vec4 { + pub x: f32, + pub y: f32, + pub z: f32, + pub w: f32, +} diff --git a/src/models/material.rs b/src/models/material.rs deleted file mode 100644 index 47185df..0000000 --- a/src/models/material.rs +++ /dev/null @@ -1,26 +0,0 @@ -use super::Vec3; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct TextureId(pub u32); - -#[derive(Clone, Debug, Default)] -pub struct Material { - pub name: String, - - pub ambient: Vec3, - pub diffuse: Vec3, - pub specular: Vec3, - pub emissive: Vec3, - pub shininess: f32, - pub opacity: f32, - - pub diffuse_texture: Option, - pub normal_texture: Option, - pub specular_texture: Option, -} - -impl Material { - pub fn new() -> Self { - Default::default() - } -} diff --git a/src/models/model.rs b/src/models/model.rs index 8a118fa..755f4d8 100644 --- a/src/models/model.rs +++ b/src/models/model.rs @@ -1,29 +1,29 @@ -use crate::models::{Material, Mesh}; - -pub struct ShaderSource { - pub vertex_src: &'static [u8], - pub fragment_src: &'static [u8], -} - -pub struct Model { - pub objects: Vec, - pub materials: Vec, -} - -pub struct GpuMesh { - pub vao: gl::types::GLuint, - pub vbo: gl::types::GLuint, - pub _ebo: Option, - pub vertex_count: i32, - pub _index_count: Option, -} +use crate::geometry::Mesh; +use crate::assets::mtl::Material; +use crate::handles::{ MeshHandle, MaterialHandle }; +use crate::assets::types::ObjData; pub struct ModelData { pub name: String, pub meshes: Vec, + pub materials: Vec, } -impl From<&ObjData> for Model { - fn from(value: &ObjData) -> Self { +impl From<&ObjData> for ModelData { + fn from(obj: &ObjData) -> Self { + let meshes = obj.meshes.iter() + .map(|obj_mesh| Mesh::new(obj, obj_mesh)) + .collect(); + + ModelData { + name: String::new(), + meshes, + materials: Vec::new(), + } } } + +pub struct Model { + pub name: String, + pub parts: Vec<(MeshHandle, MaterialHandle)>, +} diff --git a/src/models/object.rs b/src/models/object.rs deleted file mode 100644 index c41386c..0000000 --- a/src/models/object.rs +++ /dev/null @@ -1,200 +0,0 @@ -use crate::models::{Material, Vec2, VertexIndex, primitives::Vec3}; - -#[derive(Debug)] -pub struct ObjData { - pub normals: Vec, - pub vertices: Vec, - pub tex_coords: Vec, - pub meshes: Vec, - pub material_lib: Option -} - -#[derive(Debug)] -pub struct ObjMesh { - pub name: String, - pub material_name: Option, - pub faces: Vec, -} - -#[derive(Debug)] -pub struct ObjFace { - pub indices: Vec, -} - -const NEW_MATERIAL_DELIMITER: &str = "newmtl "; - -fn parse_rgb(values: &str) -> Result<[f32; 3], Box> { - let mut rgb = [0.0; 3]; - let mut i = 0; - - let values = values.split_whitespace(); - - for v in values { - if i > 3 { - todo!("Implement custom error handling: too many values"); - } - let parsed = v.parse::()?; - rgb[i] = parsed; - i += 1; - }; - - if i != 3 { - todo!("Implement custom error handling: too few values"); - } - - Ok(rgb) -} - -impl ObjData { - pub fn new() -> Self { - Self { - normals: vec![], - vertices: vec![], - tex_coords: vec![], - meshes: vec![], - material_lib: None - } - } - - pub fn load_materials(path: &str) -> Result, Box> { - let content = std::fs::read_to_string(path)?; - let mut lines = content.lines().peekable(); - let mut materials = vec![]; - - while let Some(line) = lines.next() { - println!("{line}"); - let line = line.trim(); - if line.is_empty() { - continue ; - } - - if line.starts_with(NEW_MATERIAL_DELIMITER) { - let mat_name = line.strip_prefix(NEW_MATERIAL_DELIMITER).unwrap(); - println!("mat_name {mat_name}"); - - while let Some(&next_line) = lines.peek() { - let next = next_line.trim(); - - println!("{next_line}"); - lines.next(); - - let mut material = Material::new(); - material.name = mat_name.to_string(); - - if let Some((key, values)) = next.split_once(' ') { - match key { - "Ka" => { - match parse_rgb(values) { - Ok(v) => material.ambient = Vec3 { x: v[0], y: v[1], z: v[2] }, - Err(e) => eprintln!("Error parsing Ka values: {}", e.to_string()) - } - }, - "Kd" => { - match parse_rgb(values) { - Ok(v) => material.diffuse = Vec3 { x: v[0], y: v[1], z: v[2] }, - Err(e) => eprintln!("Error parsing Kd values: {}", e.to_string()) - } - } - "Ks" => { - match parse_rgb(values) { - Ok(v) => material.specular = Vec3 { x: v[0], y: v[1], z: v[2] }, - Err(e) => eprintln!("Error parsing Ks values: {}", e.to_string()) - } - } - _ => { eprintln!("Unknown key: {key}") } - } - } - materials.push(material); - } - } - } - - Ok(materials) - } - - pub fn load_obj(path: &str) -> Result> { - let mut obj_data = Self::new(); - let content = std::fs::read_to_string(path)?; - let mut current_name = String::new(); - - let mut current_mash = ObjMesh { - name: String::new(), - material_name: None, - faces: vec![] - }; - - for line in content.lines() { - let line = line.trim(); - let mut line_split = line.split_whitespace(); - - while let Some(split) = line_split.next() { - match split { - "v" => { - let x = line_split.next().unwrap().parse::()?; - let y = line_split.next().unwrap().parse::()?; - let z = line_split.next().unwrap().parse::()?; - - obj_data.vertices.push(Vec3 { x, y, z }); - }, - "vt" => { - let x = line_split.next().unwrap().parse::()?; - let y = line_split.next().unwrap().parse::()?; - - obj_data.tex_coords.push(Vec2 { x, y }) - }, - "vn" => { - let x = line_split.next().unwrap().parse::()?; - let y = line_split.next().unwrap().parse::()?; - let z = line_split.next().unwrap().parse::()?; - - obj_data.normals.push(Vec3 { x, y, z }); - }, - "o" | "g" => { - current_name = line_split.next().unwrap_or("").to_string(); - current_mash.name = current_name.clone(); - } - "usemtl" => { - if !current_mash.faces.is_empty() { - obj_data.meshes.push(current_mash); - } - - let material_name = line_split.next().and_then(|l| Some(l.to_string())); - current_mash = ObjMesh { - name: current_name.clone(), - material_name, - faces: vec![] - }; - }, - "f" => { - let mut current_face = ObjFace { indices: vec![] }; - - while let Some(val) = line_split.next() { - let values_split = val.split('/'); - let mut face_index: [u32; 3] = [0; 3]; - - for (i, value) in values_split.enumerate() { - if !value.is_empty() { - face_index[i] = value.parse::()?; - } - } - - let vertex_index = VertexIndex { - position: face_index[0], - tex_coord: if face_index[1] != 0 { Some(face_index[1]) } else { None }, - normal: if face_index[2] != 0 { Some(face_index[2]) } else { None }, - }; - - current_face.indices.push(vertex_index); - } - current_mash.faces.push(current_face); - } - _ => {} - } - } - } - if !current_mash.faces.is_empty() { - obj_data.meshes.push(current_mash); - } - Ok(obj_data) - } -} diff --git a/src/models/scene.rs b/src/models/scene.rs deleted file mode 100644 index e7bd996..0000000 --- a/src/models/scene.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::collections::HashMap; - -use crate::models::{Material, Model}; - -pub struct Scene { - pub models: Vec, - pub materials: HashMap, -} diff --git a/src/models/vertex.rs b/src/models/vertex.rs deleted file mode 100644 index 5e7925c..0000000 --- a/src/models/vertex.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::models::{ Vec2, Vec3 }; - -#[derive(Debug)] -pub struct Vertex { - pub position: Vec3, - pub normal: Vec3, - pub tex_coord: Vec2, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct VertexIndex { - pub position: u32, - pub tex_coord: Option, - pub normal: Option -} diff --git a/src/shaders/constants.rs b/src/shaders/constants.rs index 86c71dc..c2cbed1 100644 --- a/src/shaders/constants.rs +++ b/src/shaders/constants.rs @@ -1,26 +1,61 @@ +pub const VERTEX_SHADER_SOURCE: &str = r#" +#version 330 core -pub const VERTEX_SHADER_SOURCE: &[u8] = b" -#version 100 -precision mediump float; +layout(location = 0) in vec3 a_position; +layout(location = 1) in vec3 a_normal; +layout(location = 2) in vec2 a_texcoord; -attribute vec3 position; -attribute vec3 color; +uniform mat4 u_model; +uniform mat4 u_view; +uniform mat4 u_projection; -varying vec3 v_color; +out vec3 v_position; +out vec3 v_normal; +out vec2 v_texcoord; void main() { - gl_Position = vec4(position, 1.0); - v_color = color; -} -\0"; + vec4 world_pos = u_model * vec4(a_position, 1.0); + v_position = world_pos.xyz; + v_normal = mat3(transpose(inverse(u_model))) * a_normal; + v_texcoord = a_texcoord; + gl_Position = u_projection * u_view * world_pos; +}"#; -pub const FRAGMENT_SHADER_SOURCE: &[u8] = b" -#version 100 -precision mediump float; +pub const FRAGMENT_SHADER_SOURCE: &str = r#" +#version 330 core -varying vec3 v_color; +in vec3 v_position; +in vec3 v_normal; +in vec2 v_texcoord; + +uniform vec3 u_light_pos; +uniform vec3 u_light_color; +uniform vec3 u_view_pos; + +uniform vec3 u_ambient; +uniform vec3 u_diffuse; +uniform vec3 u_specular; +uniform float u_shininess; + +out vec4 frag_color; void main() { - gl_FragColor = vec4(v_color, 1.0); -} -\0"; + vec3 normal = normalize(v_normal); + vec3 light_dir = normalize(u_light_pos - v_position); + vec3 view_dir = normalize(u_view_pos - v_position); + vec3 halfway = normalize(light_dir + view_dir); + + // Ambient + vec3 ambient = u_ambient * 0.1; + + // Diffuse + float diff = max(dot(normal, light_dir), 0.0); + vec3 diffuse = diff * u_diffuse * u_light_color; + + // Specular (Blinn-Phong) + float spec = pow(max(dot(normal, halfway), 0.0), u_shininess); + vec3 specular = spec * u_specular * u_light_color; + + vec3 result = ambient + diffuse + specular; + frag_color = vec4(result, 1.0); +}"#; diff --git a/src/shaders/shader.rs b/src/shaders/shader.rs deleted file mode 100644 index a78b1e3..0000000 --- a/src/shaders/shader.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::{app::gl, models::{Mat4, Vec3}}; - -pub struct Shader { - pub program: gl::types::GLuint, - pub gl: std::rc::Rc, -} - -impl Shader { - pub fn use_program(&self) { - unsafe { self.gl.UseProgram(self.program) }; - } - - pub fn set_uniform_mat4(&self, name: &str, mat: &Mat4) { - unsafe { - let loc = self.gl.GetUniformLocation( - self.program, - name.as_ptr() as *const i8 - ); - self.gl.UniformMatrix4fv(loc, 1, gl::FALSE, mat.as_ptr()); - } - } - - pub fn set_uniform_vec3(&self, name: &str, v: &Vec3) { - unsafe { - let loc = self.gl.GetUniformLocation(self.program, name.as_ptr() as *const i8); - self.gl.Uniform3f(loc, v.x, v.y, v.z); - } - } - - pub fn set_uniform_f32(&self, name: &str, v: f32) { - unsafe { - let loc = self.gl.GetUniformLocation(self.program, name.as_ptr() as *const i8); - self.gl.Uniform1f(loc, v); - } - } - - pub fn set_uniform_i32(&self, name: &str, v: i32) { - unsafe { - let loc = self.gl.GetUniformLocation(self.program, name.as_ptr() as *const i8); - self.gl.Uniform1i(loc, v); // used for texture slots - } - } -} diff --git a/src/shaders/types.rs b/src/shaders/types.rs new file mode 100644 index 0000000..16bdd83 --- /dev/null +++ b/src/shaders/types.rs @@ -0,0 +1,5 @@ +pub struct ShaderSource {} + +pub struct Shader(pub u32); + +pub type ShaderHandle = u32;