TODO: Finish draw function and scene rendering logic

This commit is contained in:
Victor Vobis 2026-01-14 12:55:56 +01:00
parent 1775cf90bc
commit 0f451c715c
36 changed files with 1677 additions and 785 deletions

33
Cargo.lock generated
View File

@ -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"

View File

@ -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]

View File

@ -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<DisplayBuilder>),
Init
pub struct App<B: RenderBackend> {
backend: B,
}
struct AppState {
gl_surface: Surface<WindowSurface>,
window: Window,
}
pub struct App {
gl_display: GlDisplayCreationState,
gl_context: Option<PossiblyCurrentContext>,
template: ConfigTemplateBuilder,
renderer: Option<Renderer>,
current_scene: Option<String>,
state: Option<AppState>,
}
impl App {
impl<B: RenderBackend> App<B> {
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<B: RenderBackend> ApplicationHandler for App<B> {
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();
}
}
}

3
src/app/camera.rs Normal file
View File

@ -0,0 +1,3 @@
pub struct Camera {
}

3
src/app/light.rs Normal file
View File

@ -0,0 +1,3 @@
pub struct Light {
}

View File

@ -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<gl::Gl>,
shaders: HashMap<String, gl::types::GLuint>,
scenes: HashMap<String, Scene>
}
impl Renderer {
pub fn new<D: GlDisplay>(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::<Vec<[f32; 6]>>();
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::<f32>() as gl::types::GLsizei,
std::ptr::null()
);
gl.VertexAttribPointer(
color_attrib as gl::types::GLuint,
3,
gl::FLOAT,
0,
6 * std::mem::size_of::<f32>() as gl::types::GLsizei,
(3 * std::mem::size_of::<f32>()) 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));
}
}
}

36
src/app/scene.rs Normal file
View File

@ -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<Model>,
pub materials: HashMap<handles::MaterialHandle, assets::mtl::Material>
}
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,
};
}
}
}
}

33
src/assets/error.rs Normal file
View File

@ -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<dyn std::error::Error + Send + Sync>),
#[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),
}

143
src/assets/mtl.rs Normal file
View File

@ -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<TextureId>,
pub normal_texture: Option<TextureId>,
pub specular_texture: Option<TextureId>,
}
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<Vec<Material>, AssetError> {
let content = std::fs::read_to_string(path)?;
let mut materials = vec![];
let mut current_material: Option<Material> = 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)
}

105
src/assets/obj.rs Normal file
View File

@ -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<types::ObjData, AssetError> {
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)
}

62
src/assets/parser.rs Normal file
View File

@ -0,0 +1,62 @@
use std::str::FromStr;
use crate::assets::error::ParseError;
pub fn parse_args<const N: usize, T>(
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::<T>().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<T>(
values: &str,
delim: &str
) -> Result<Vec<T>, ParseError>
where
T: FromStr + Copy + Default,
T::Err: std::error::Error + 'static + Send + Sync
{
let mut result: Vec<T> = Vec::new();
let mut i = 0;
let values = values.split(delim);
for v in values {
let parsed = if !v.is_empty() {
v.parse::<T>().map_err(|e| {ParseError::InvalidValue(Box::new(e))})?
} else {
Default::default()
};
result[i] = parsed;
i += 1;
}
Ok(result)
}

41
src/assets/types.rs Normal file
View File

@ -0,0 +1,41 @@
use crate::math::{ Vec2, Vec3 };
#[derive(Debug)]
pub struct ObjData {
pub normals: Vec<Vec3>,
pub vertices: Vec<Vec3>,
pub tex_coords: Vec<Vec2>,
pub meshes: Vec<ObjMesh>,
pub material_lib: Option<String>
}
#[derive(Debug)]
pub struct ObjMesh {
pub name: String,
pub material_name: Option<String>,
pub faces: Vec<ObjFace>,
}
#[derive(Debug)]
pub struct ObjFace {
pub indices: Vec<VertexIndex>,
}
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<u32>,
pub normal: Option<u32>
}

33
src/backend/backend.rs Normal file
View File

@ -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<shaders::ShaderHandle, String>;
// 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);
// }

7
src/backend/gpu/mesh.rs Normal file
View File

@ -0,0 +1,7 @@
pub struct GpuMesh {
pub vao: gl::types::GLuint,
pub vbo: gl::types::GLuint,
pub ebo: Option<gl::types::GLuint>,
pub vertex_count: i32,
pub index_count: Option<i32>,
}

View File

@ -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<Vec<(u32, u32)>>,
}
pub fn gl_config_picker(configs: Box<dyn Iterator<Item = Config> + '_>) -> Config {
configs
.reduce(|accum, config| {
@ -50,4 +72,3 @@ pub fn gl_create_context(window: &Window, gl_config: &Config) -> NotCurrentConte
})
}
}

View File

@ -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<glutin::surface::WindowSurface>,
window: Window,
}
pub struct GlBackend {
gl_display: GlDisplayCreationState,
gl_context: Option<glutin::context::PossiblyCurrentContext>,
gl: Option<GlRenderer>,
template: glutin::config::ConfigTemplateBuilder,
state: Option<AppState>,
meshes: HashMap<handles::MeshHandle, gpu::GpuMesh>,
shaders: HashMap<shaders::ShaderHandle, shaders::Shader>,
}
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<shaders::ShaderHandle, String> {
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) {
}
}
}

View File

@ -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<opengl::Gl>,
}
impl GlRenderer {
pub fn new<D: GlDisplay>(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<f32> = 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::<f32>()) as gl::types::GLsizeiptr,
vertex_data.as_ptr() as *const _,
gl::STATIC_DRAW,
);
let stride = 8 * std::mem::size_of::<f32>() 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::<f32>()) as *const _);
gl.EnableVertexAttribArray(1);
// Texcoord attribute (location = 2)
gl.VertexAttribPointer(2, 2, gl::FLOAT, gl::FALSE, stride, (6 * std::mem::size_of::<f32>()) 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::<u32>()) 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
}
}

View File

@ -0,0 +1,4 @@
pub struct GlShaderSource {
pub vertex_src: String,
pub fragment_src: String,
}

View File

@ -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;

8
src/geometry/vertex.rs Normal file
View File

@ -0,0 +1,8 @@
use crate::math::{ Vec2, Vec3 };
#[derive(Debug)]
pub struct Vertex {
pub position: Vec3,
pub normal: Vec3,
pub tex_coord: Vec2,
}

5
src/handles.rs Normal file
View File

@ -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);

View File

@ -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;

View File

@ -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::<opengl::GlBackend>::new();
let event_res = event_loop.run_app(&mut app);

View File

@ -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,
],
}
}

155
src/math/vector/vec2.rs Normal file
View File

@ -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<f32> 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<f32> 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<f32> 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<f32> 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<usize> for Vec2 {
type Output = f32;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl IndexMut<usize> for Vec2 {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.0[index]
}
}

171
src/math/vector/vec3.rs Normal file
View File

@ -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<f32> 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<f32> 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<f32> 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<f32> 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<usize> for Vec3 {
type Output = f32;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl IndexMut<usize> for Vec3 {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.0[index]
}
}

171
src/math/vector/vec4.rs Normal file
View File

@ -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<f32> 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<f32> 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<f32> 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<f32> 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<usize> for Vec4 {
type Output = f32;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl IndexMut<usize> for Vec4 {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.0[index]
}
}

68
src/math/vector/vector.rs Normal file
View File

@ -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,
}

View File

@ -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<TextureId>,
pub normal_texture: Option<TextureId>,
pub specular_texture: Option<TextureId>,
}
impl Material {
pub fn new() -> Self {
Default::default()
}
}

View File

@ -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<GpuMesh>,
pub materials: Vec<Material>,
}
pub struct GpuMesh {
pub vao: gl::types::GLuint,
pub vbo: gl::types::GLuint,
pub _ebo: Option<gl::types::GLuint>,
pub vertex_count: i32,
pub _index_count: Option<i32>,
}
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<Mesh>,
pub materials: Vec<Material>,
}
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)>,
}

View File

@ -1,200 +0,0 @@
use crate::models::{Material, Vec2, VertexIndex, primitives::Vec3};
#[derive(Debug)]
pub struct ObjData {
pub normals: Vec<Vec3>,
pub vertices: Vec<Vec3>,
pub tex_coords: Vec<Vec2>,
pub meshes: Vec<ObjMesh>,
pub material_lib: Option<String>
}
#[derive(Debug)]
pub struct ObjMesh {
pub name: String,
pub material_name: Option<String>,
pub faces: Vec<ObjFace>,
}
#[derive(Debug)]
pub struct ObjFace {
pub indices: Vec<VertexIndex>,
}
const NEW_MATERIAL_DELIMITER: &str = "newmtl ";
fn parse_rgb(values: &str) -> Result<[f32; 3], Box<dyn std::error::Error>> {
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::<f32>()?;
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<Vec<Material>, Box<dyn std::error::Error>> {
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<Self, Box<dyn std::error::Error>> {
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::<f32>()?;
let y = line_split.next().unwrap().parse::<f32>()?;
let z = line_split.next().unwrap().parse::<f32>()?;
obj_data.vertices.push(Vec3 { x, y, z });
},
"vt" => {
let x = line_split.next().unwrap().parse::<f32>()?;
let y = line_split.next().unwrap().parse::<f32>()?;
obj_data.tex_coords.push(Vec2 { x, y })
},
"vn" => {
let x = line_split.next().unwrap().parse::<f32>()?;
let y = line_split.next().unwrap().parse::<f32>()?;
let z = line_split.next().unwrap().parse::<f32>()?;
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::<u32>()?;
}
}
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)
}
}

View File

@ -1,8 +0,0 @@
use std::collections::HashMap;
use crate::models::{Material, Model};
pub struct Scene {
pub models: Vec<Model>,
pub materials: HashMap<String, Material>,
}

View File

@ -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<u32>,
pub normal: Option<u32>
}

View File

@ -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);
}"#;

View File

@ -1,43 +0,0 @@
use crate::{app::gl, models::{Mat4, Vec3}};
pub struct Shader {
pub program: gl::types::GLuint,
pub gl: std::rc::Rc<gl::Gl>,
}
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
}
}
}

5
src/shaders/types.rs Normal file
View File

@ -0,0 +1,5 @@
pub struct ShaderSource {}
pub struct Shader(pub u32);
pub type ShaderHandle = u32;