broken state - now its complicated

This commit is contained in:
Victor Vobis 2026-01-13 21:55:13 +01:00
parent 0cf7d5eb43
commit 1775cf90bc
12 changed files with 549 additions and 168 deletions

View File

@ -13,7 +13,7 @@ use winit::{event, window};
use crate::app::gl::{gl_config_picker, gl_create_context};
use crate::app::renderer::Renderer;
use crate::models::ModelData;
use crate::models::Scene;
enum GlDisplayCreationState {
Builder(Box<DisplayBuilder>),
@ -32,13 +32,12 @@ pub struct App {
renderer: Option<Renderer>,
current_scene: Option<String>,
scene_data: ModelData,
state: Option<AppState>,
}
impl App {
pub fn new(scene_data: ModelData) -> Self {
pub fn new() -> Self {
let template = ConfigTemplateBuilder::new()
.with_alpha_size(8)
.with_transparency(cfg!(target_os = "macos"));
@ -51,7 +50,6 @@ impl App {
gl_context: None,
renderer: None,
state: None,
scene_data,
current_scene: None,
}
}
@ -112,7 +110,7 @@ impl ApplicationHandler for App {
if self.renderer.is_none() {
let mut renderer = Renderer::new(&gl_context.display());
renderer.upload_scene_data(&self.scene_data);
// renderer.upload_scene_data(&self.scene_data);
println!("Built Renderer");

View File

@ -1,6 +1,7 @@
use glutin::prelude::GlDisplay;
use crate::app::gl;
use crate::models::{self, Model, ModelData, RenderObject, Scene, ShaderSource};
use crate::models::{self, GpuMesh, Scene, ShaderSource};
use crate::shaders::Shader;
use std::collections::HashMap;
use std::ops::Deref;
use std::ffi::{ CStr, CString };
@ -13,7 +14,7 @@ fn get_gl_string(gl: &gl::Gl, variant: gl::types::GLenum) -> Option<&'static CSt
}
pub struct Renderer {
gl: gl::Gl,
gl: std::rc::Rc<gl::Gl>,
shaders: HashMap<String, gl::types::GLuint>,
scenes: HashMap<String, Scene>
}
@ -38,26 +39,13 @@ impl Renderer {
}
Self {
gl,
gl: std::rc::Rc::new(gl),
shaders: HashMap::new(),
scenes: HashMap::new()
}
}
pub fn upload_scene_data(&mut self, scene_data: &ModelData) {
let objects = scene_data.objects_data
.iter()
.map(|(mesh, shader_src)| {
let shader = self.create_shader(shader_src);
let render_object = self.upload_mesh(mesh);
(render_object, shader)
})
.collect::<Vec<(RenderObject, u32)>>();
println!("Uploading Scene {}", scene_data.name);
self.scenes.insert(scene_data.name.clone(), Model { objects });
}
pub fn upload_mesh(&self, mesh: &models::Mesh) -> RenderObject {
pub fn upload_mesh(&self, mesh: &models::Mesh) -> GpuMesh {
unsafe {
let gl = &self.gl;
@ -71,7 +59,7 @@ impl Renderer {
let vertex_data = mesh.vertices
.iter()
.map(|v| {
[v.x, v.y, v.z, 1.0, 0.1, 0.1]
[v.position.x, v.position.y, v.position.z, 1.0, 0.1, 0.1]
})
.collect::<Vec<[f32; 6]>>();
@ -106,7 +94,7 @@ impl Renderer {
gl.EnableVertexAttribArray(pos_attrib as gl::types::GLuint);
gl.EnableVertexAttribArray(color_attrib as gl::types::GLuint);
RenderObject {
GpuMesh {
vao,
vbo,
_ebo: None,
@ -125,7 +113,7 @@ impl Renderer {
}
}
pub fn create_shader(&self, shader_src: &ShaderSource) -> gl::types::GLuint {
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);
@ -140,7 +128,10 @@ impl Renderer {
self.gl.DeleteShader(vertex_shader);
self.gl.DeleteShader(fragment_shader);
program
Shader {
program,
gl: self.gl.clone()
}
}
}
@ -164,7 +155,7 @@ impl Renderer {
}
}
pub fn draw_object(&self, obj: &RenderObject, shader: gl::types::GLuint) {
pub fn draw_object(&self, obj: &GpuMesh, shader: gl::types::GLuint) {
unsafe {
self.UseProgram(shader);
@ -182,9 +173,9 @@ impl Renderer {
}
if let Some(scene) = self.scenes.get(current_scene) {
for model in scene.models {
scene.models.iter().for_each(|(obj, shader)| {
self.draw_object(obj, *shader);
}
});
} else {
eprintln!("Unknown Scene Requested: {}", current_scene);
}

View File

@ -13,6 +13,11 @@ pub mod models {
pub mod material;
pub use material::*;
pub mod primitives;
pub use primitives::*;
pub mod object;
}
pub mod app {
@ -22,6 +27,9 @@ pub mod app {
}
pub mod shaders {
pub mod shaders;
pub use shaders::*;
pub mod shader;
pub use shader::*;
pub mod constants;
pub use constants::*;
}

View File

@ -1,29 +1,34 @@
use proj::{app::app::App, models::{Mesh, ModelData, ShaderSource}, shaders::{FRAGMENT_SHADER_SOURCE, VERTEX_SHADER_SOURCE}};
use proj::{app::app::App, models::{ShaderSource, object::ObjData}, shaders::{FRAGMENT_SHADER_SOURCE, VERTEX_SHADER_SOURCE}};
use winit::event_loop::EventLoop;
const TRIANGLE_OBJ_PATH: &str = "assets/models/small_house/model.obj";
const HOUSE_OBJ_PATH: &str = "assets/models/small_house/model.obj";
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 mut mesh = Mesh::new();
if let Err(e) = mesh.load_obj(TRIANGLE_OBJ_PATH) {
panic!("{}", e);
}
let shader_src = ShaderSource {
vertex_src: VERTEX_SHADER_SOURCE,
fragment_src: FRAGMENT_SHADER_SOURCE
};
let scene_data = ModelData {
name: "home".to_string(),
objects_data: vec![(mesh, shader_src)]
let obj_data = match ObjData::load_obj(HOUSE_OBJ_PATH) {
Ok(o) => o,
Err(e) => return eprintln!("Error: {}", e),
};
let mut app = App::new(scene_data);
println!("Done");
let materials = match ObjData::load_materials(HOUSE_MAT_PATH) {
Ok(m) => m,
Err(e) => return eprintln!("Error: {}", e),
};
dbg!(&materials);
let mut app = App::new();
app.set_current_scene("home");

View File

@ -1,18 +1,26 @@
#[derive(Debug)]
use super::Vec3;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TextureId(pub u32);
#[derive(Clone, Debug, Default)]
pub struct Material {
pub ambient: [f32; 3],
pub diffuse: [f32; 3],
pub specular: [f32; 3],
pub shader_name: String,
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 {
Self {
ambient: Default::default(),
diffuse: Default::default(),
specular: Default::default(),
shader_name: String::new(),
}
Default::default()
}
}

View File

@ -1,124 +1,44 @@
use std::collections::HashMap;
use crate::models::Material;
use crate::models::{VertexIndex, object::{ObjData, ObjMesh}};
use super::Vertex;
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct Mesh {
pub name: String,
pub vertices: Vec<Vertex>,
pub material_name: String,
}
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 {
let parsed = v.parse::<f32>()?;
rgb[i] = parsed;
i += 1;
if i > 2 {
todo!("Implement custom error handling: too many values");
}
};
if i != 3 {
todo!("Implement custom error handling: too few values");
}
Ok(rgb)
pub indices: Vec<u32>,
pub material_name: Option<String>,
}
impl Mesh {
pub fn new() -> Self {
Self {
vertices: vec![],
material_name: String::new(),
}
}
pub fn new(obj: &ObjData, obj_mash: &ObjMesh) -> Self {
let mut vertices: Vec<Vertex> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
let mut vertex_map: HashMap<VertexIndex, u32> = HashMap::new();
pub fn load_obj(&mut self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
for line in content.lines() {
if line.starts_with("v ") {
let coords: Result<Vec<f32>, _> = line
.split_whitespace()
.skip(1)
.take(3)
.map(|s| s.parse::<f32>())
.collect();
match coords {
Ok(v) if v.len() == 3 => {
self.vertices.push(Vertex { x: v[0], y: v[1], z: v[2] })
},
_ => eprintln!("Warning: Invalid vertex line: {}", line)
for face in &obj_mash.faces {
for i in 1..(face.indices.len() - 1) {
for &idx in &[face.indices[0], face.indices[i], face.indices[i +1]] {
let index = *vertex_map.entry(idx).or_insert_with(|| {
let v = Vertex {
position: obj.vertices[idx.position as usize],
normal: idx.normal.map(|n| obj.normals[n as usize]).unwrap_or_default(),
tex_coord: idx.tex_coord.map(|t| obj.tex_coords[t as usize]).unwrap_or_default(),
};
vertices.push(v);
(vertices.len() - 1) as u32
});
indices.push(index);
}
}
}
println!("{:#?}", self.vertices);
Ok(())
}
pub fn load_material(&mut self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
let mut lines = content.lines().peekable();
while let Some(line) = lines.next() {
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();
while let Some(&next_line) = lines.peek() {
let next = next_line.trim();
if next.starts_with(NEW_MATERIAL_DELIMITER) || next.is_empty() {
lines.next();
let mut material = Material::new();
if let Some((key, values)) = next.split_once(' ') {
match key {
"Ka" => {
match parse_rgb(values) {
Ok(v) => material.ka = v,
Err(e) => eprintln!("Error parsing Ka values: {}", e.to_string())
}
},
"Kd" => {
match parse_rgb(values) {
Ok(v) => material.kd = v,
Err(e) => eprintln!("Error parsing Kd values: {}", e.to_string())
}
}
"Ks" => {
match parse_rgb(values) {
Ok(v) => material.ks = v,
Err(e) => eprintln!("Error parsing Ks values: {}", e.to_string())
}
}
_ => { eprintln!("Unknown key: {key}") }
}
}
self.materials.insert(mat_name.to_string(), material);
}
}
}
Mesh {
vertices,
indices,
name: obj_mash.name.clone(),
material_name: obj_mash.material_name.clone(),
}
println!("{:#?}", self.vertices);
Ok(())
}
}

View File

@ -1,4 +1,4 @@
use crate::models::Mesh;
use crate::models::{Material, Mesh};
pub struct ShaderSource {
pub vertex_src: &'static [u8],
@ -6,10 +6,11 @@ pub struct ShaderSource {
}
pub struct Model {
pub objects: Vec<RenderObject>
pub objects: Vec<GpuMesh>,
pub materials: Vec<Material>,
}
pub struct RenderObject {
pub struct GpuMesh {
pub vao: gl::types::GLuint,
pub vbo: gl::types::GLuint,
pub _ebo: Option<gl::types::GLuint>,
@ -19,5 +20,10 @@ pub struct RenderObject {
pub struct ModelData {
pub name: String,
pub meshes: Vec<Mesh>
pub meshes: Vec<Mesh>,
}
impl From<&ObjData> for Model {
fn from(value: &ObjData) -> Self {
}
}

200
src/models/object.rs Normal file
View File

@ -0,0 +1,200 @@
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)
}
}

194
src/models/primitives.rs Normal file
View File

@ -0,0 +1,194 @@
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,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct Mat4 {
pub data: [f32; 16],
}
impl Mat4 {
pub fn identity() -> Self {
Self {
data: [
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
],
}
}
pub fn translation(x: f32, y: f32, z: f32) -> Self {
Self {
data: [
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
x, y, z, 1.0,
],
}
}
pub fn scale(x: f32, y: f32, z: f32) -> Self {
Self {
data: [
x, 0.0, 0.0, 0.0,
0.0, y, 0.0, 0.0,
0.0, 0.0, z, 0.0,
0.0, 0.0, 0.0, 1.0,
],
}
}
pub fn rotation_x(angle: f32) -> Self {
let c = angle.cos();
let s = angle.sin();
Self {
data: [
1.0, 0.0, 0.0, 0.0,
0.0, c, s, 0.0,
0.0, -s, c, 0.0,
0.0, 0.0, 0.0, 1.0,
],
}
}
pub fn rotation_y(angle: f32) -> Self {
let c = angle.cos();
let s = angle.sin();
Self {
data: [
c, 0.0, -s, 0.0,
0.0, 1.0, 0.0, 0.0,
s, 0.0, c, 0.0,
0.0, 0.0, 0.0, 1.0,
],
}
}
pub fn rotation_z(angle: f32) -> Self {
let c = angle.cos();
let s = angle.sin();
Self {
data: [
c, s, 0.0, 0.0,
-s, c, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
],
}
}
pub fn perspective(fov: f32, aspect: f32, near: f32, far: f32) -> Self {
let f = 1.0 / (fov / 2.0).tan();
let nf = 1.0 / (near - far);
Self {
data: [
f / aspect, 0.0, 0.0, 0.0,
0.0, f, 0.0, 0.0,
0.0, 0.0, (far + near) * nf, -1.0,
0.0, 0.0, 2.0 * far * near * nf, 0.0,
],
}
}
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);
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,
],
}
}
pub fn as_ptr(&self) -> *const f32 {
self.data.as_ptr()
}
}
impl std::ops::Mul for Mat4 {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
let mut result = [0.0f32; 16];
for row in 0..4 {
for col in 0..4 {
for k in 0..4 {
result[col * 4 + row] += self.data[k * 4 + row] * rhs.data[col * 4 + k];
}
}
}
Self { data: result }
}
}

View File

@ -1,7 +1,15 @@
use crate::models::{ Vec2, Vec3 };
#[derive(Debug)]
pub struct Vertex {
pub x: f32,
pub y: f32,
pub z: f32,
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>
}

43
src/shaders/shader.rs Normal file
View File

@ -0,0 +1,43 @@
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
}
}
}