diff --git a/simul/.vscode/launch.json b/simul/.vscode/launch.json new file mode 100644 index 0000000..5ddde67 --- /dev/null +++ b/simul/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Debug executable 'simul'", + "type": "lldb", + "request": "launch", + "cargo": { + "args": [ + "run", + "--bin=simul" + ] + }, + "args": [] + }, + { + "name": "Debug unit tests in executable 'simul'", + "type": "lldb", + "request": "launch", + "cargo": { + "args": [ + "test", + "--bin=simul" + ] + } + } + ] +} \ No newline at end of file diff --git a/simul/Cargo.lock b/simul/Cargo.lock index 017a1b2..bd24979 100644 --- a/simul/Cargo.lock +++ b/simul/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "bitflags" version = "2.11.0" @@ -20,6 +26,43 @@ version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "obj-rs" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8247fd8f66340ad9c969f96e63a80a2f74b04e5403d92584b75e046006f4c7e1" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + [[package]] name = "sdl3" version = "0.17.3" @@ -58,9 +101,57 @@ dependencies = [ "sdl3-sys", ] +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "simul" version = "0.1.0" dependencies = [ + "obj-rs", "sdl3", ] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/simul/Cargo.toml b/simul/Cargo.toml index 5666a20..b082c1a 100644 --- a/simul/Cargo.toml +++ b/simul/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +obj-rs = "0.7.4" sdl3 = { version = "0.17.3", features = ["ttf", "image"] } diff --git a/simul/assets/cube.mtl b/simul/assets/cube.mtl new file mode 100644 index 0000000..31c5ac7 --- /dev/null +++ b/simul/assets/cube.mtl @@ -0,0 +1,12 @@ +# Blender 5.1.0 MTL File: 'None' +# www.blender.org + +newmtl Material +Ns 250.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.800000 0.800000 0.800000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.500000 +d 1.000000 +illum 2 diff --git a/simul/assets/cube.obj b/simul/assets/cube.obj new file mode 100644 index 0000000..b9b58a5 --- /dev/null +++ b/simul/assets/cube.obj @@ -0,0 +1,46 @@ +# Blender 5.1.0 +# www.blender.org +mtllib cube.mtl +o Cube +v 1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 1.000000 +v -1.000000 1.000000 -1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 1.000000 +vn -0.0000 1.0000 -0.0000 +vn -0.0000 -0.0000 1.0000 +vn -1.0000 -0.0000 -0.0000 +vn -0.0000 -1.0000 -0.0000 +vn 1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 -1.0000 +vt 0.875000 0.500000 +vt 0.625000 0.750000 +vt 0.625000 0.500000 +vt 0.375000 1.000000 +vt 0.375000 0.750000 +vt 0.625000 0.000000 +vt 0.375000 0.250000 +vt 0.375000 0.000000 +vt 0.375000 0.500000 +vt 0.125000 0.750000 +vt 0.125000 0.500000 +vt 0.625000 0.250000 +vt 0.875000 0.750000 +vt 0.625000 1.000000 +s 0 +usemtl Material +f 5/1/1 3/2/1 1/3/1 +f 3/2/2 8/4/2 4/5/2 +f 7/6/3 6/7/3 8/8/3 +f 2/9/4 8/10/4 6/11/4 +f 1/3/5 4/5/5 2/9/5 +f 5/12/6 2/9/6 6/7/6 +f 5/1/1 7/13/1 3/2/1 +f 3/2/2 7/14/2 8/4/2 +f 7/6/3 5/12/3 6/7/3 +f 2/9/4 4/5/4 8/10/4 +f 1/3/5 3/2/5 4/5/5 +f 5/12/6 1/3/6 2/9/6 diff --git a/simul/src/m3x3.rs b/simul/src/m3x3.rs index 2737220..0ea5c8a 100644 --- a/simul/src/m3x3.rs +++ b/simul/src/m3x3.rs @@ -2,43 +2,69 @@ use std::ops::Mul; use crate::v3::V3; -pub struct M3x3([f64; 9]); +#[derive(Clone, Copy, Debug)] +pub struct M3x3([[f64; 3]; 3]); impl M3x3 { + pub fn init(v: f64) -> Self { + Self([[v; 3]; 3]) + } + + pub fn from_v3_rot(rot: V3) -> Self { + M3x3::new_rotate_x(rot.0) * M3x3::new_rotate_y(rot.1) * M3x3::new_rotate_z(rot.2) + } + #[rustfmt::skip] pub fn new_rotate_x(angle: f64) -> Self { Self([ - 1.0, 0.0, 0.0, - 0.0, f64::cos(angle), -f64::sin(angle), - 0.0, f64::sin(angle), f64::cos(angle), + [1.0, 0.0, 0.0], + [0.0, f64::cos(angle), -f64::sin(angle)], + [0.0, f64::sin(angle), f64::cos(angle)], ]) } #[rustfmt::skip] pub fn new_rotate_y(angle: f64) -> Self { Self([ - f64::cos(angle), 0.0, f64::sin(angle), - 0.0, 1.0, 0.0, - -f64::sin(angle), 0.0, f64::cos(angle), + [ f64::cos(angle), 0.0, f64::sin(angle)], + [ 0.0, 1.0, 0.0], + [-f64::sin(angle), 0.0, f64::cos(angle)], ]) } #[rustfmt::skip] pub fn new_rotate_z(angle: f64) -> Self { Self([ - f64::cos(angle), -f64::sin(angle), 0.0, - f64::sin(angle), f64::cos(angle), 0.0, - 0.0, 0.0, 1.0, + [f64::cos(angle), -f64::sin(angle), 0.0], + [f64::sin(angle), f64::cos(angle), 0.0], + [ 0.0, 0.0, 1.0], ]) } } +impl Mul for M3x3 { + type Output = M3x3; + + fn mul(self, rhs: M3x3) -> Self::Output { + let mut output = M3x3([[0.0; 3]; 3]); + for i in 0..3 { + for j in 0..3 { + let v = &mut output.0[i][j]; + for k in 0..3 { + *v += self.0[i][k] * rhs.0[k][j]; + } + } + } + output + } +} + impl Mul for M3x3 { type Output = V3; fn mul(self, rhs: V3) -> Self::Output { let V3(x, y, z) = rhs; - let [xx, xy, xz, yx, yy, yz, zx, zy, zz] = self.0; + let [[xx, xy, xz], [yx, yy, yz], [zx, zy, zz]] = self.0; V3( x * xx + y * xy + z * xz, x * yx + y * yy + z * yz, diff --git a/simul/src/main.rs b/simul/src/main.rs index f0378fc..e60e8e7 100644 --- a/simul/src/main.rs +++ b/simul/src/main.rs @@ -1,7 +1,12 @@ #![allow(unused)] +use std::{collections::HashMap, f64::consts::PI, fs::File, io::BufReader}; + +use obj::{Obj, load_obj}; + use crate::{ - scene::Scene, + m3x3::M3x3, + scene::{Model, Scene}, tri2::Tri2, tri3::Tri3, v2::V2, @@ -17,23 +22,89 @@ mod v2; mod v3; mod window; -pub struct App {} +struct AssetStore { + assets: HashMap, +} + +impl AssetStore { + fn new() -> Self { + Self { + assets: HashMap::new(), + } + } + + fn load>(&mut self, path: S) -> Obj { + let path = path.into(); + if self.assets.contains_key(&path) { + return self.assets.get(&path).unwrap().clone(); + } + let asset: Obj = load_obj(BufReader::new(File::open(&path).unwrap())).unwrap(); + self.assets.insert(path, asset.clone()); + asset + } + + fn get>(&self, path: S) -> Option { + let path = path.into(); + self.assets.get(&path).cloned() + } +} + +pub struct App { + assets: AssetStore, + rot: V3, +} + +impl App { + pub fn new() -> Self { + Self { + assets: AssetStore::new(), + rot: V3::init(0.0), + } + } +} impl window::App for App { - fn update(&mut self, delta_time: std::time::Duration) {} + fn update(&mut self, delta_time: std::time::Duration) { + self.rot.0 += PI * 2.0 * delta_time.as_secs_f64() * 0.1; + self.rot.1 += PI * 2.0 * delta_time.as_secs_f64() * 0.1; + } fn render(&self, r: &mut R) { - r.draw_line(V2(0.0, 0.0), V2(0.6, 0.2), Color::RED); - let mut scene = Scene::new(); - scene.draw_triangle( - Tri3(V3(0.0, 0.0, 0.0), V3(0.3, 0.3, 0.0), V3(0.3, 0.0, 0.0)), - Color::GREEN, - Color::BLACK, + // let mut model1 = Model::new(); + // model1 + // .add_tri( + // Tri3(V3(0.0, 0.0, 0.0), V3(0.3, 0.3, 0.0), V3(0.3, 0.0, 0.0)), + // Color::RGB(200, 200, 200), + // ) + // .rotate_by_m3x3(M3x3::from_v3_rot(self.rot)) + // .translate(V3(1.0, 0.0, 2.0)) + // .scale(V3(1.0, 1.0, 1.0)); + // + // scene.draw_model(model1); + + let mut model2 = Model::new(); + + let cube_obj = self.assets.get("assets/cube.obj").unwrap(); + model2 + .add_obj(&cube_obj, Color::RGB(165, 125, 165)) + .rotate_by_m3x3(M3x3::from_v3_rot(self.rot)) + .scale(V3(0.2, 0.2, 0.2)) + .translate(V3(0.0, 0.0, 1.0)); + + scene.draw_model(model2); + + // scene.draw_obj(&cube_obj, V3(0.1, 0.1, 0.1), Color::RGB(125, 125, 125)); + + scene.render( + r, + V3(0.0, 0.0, 0.0), + M3x3::from_v3_rot(V3(0.0, 0.0, 0.0)), + V3(0.0, 0.0, 1.0), ); - scene.render(r, V3(0.0, 0.0, -1.0)); + // r.draw_line(V2(0.0, 0.0), V2(0.6, 0.2), Color::RED); } fn event(&mut self, event: Event) {} @@ -41,7 +112,8 @@ impl window::App for App { fn main() { let mut window = Window::new(); - let mut app = App {}; + let mut app = App::new(); + app.assets.load("assets/cube.obj"); window.run(&mut app); } diff --git a/simul/src/scene.rs b/simul/src/scene.rs index a425405..bd01c56 100644 --- a/simul/src/scene.rs +++ b/simul/src/scene.rs @@ -1,4 +1,7 @@ +use obj::Obj; + use crate::{ + m3x3::M3x3, tri2::Tri2, tri3::Tri3, v3::V3, @@ -6,7 +9,7 @@ use crate::{ }; pub struct Scene { - tris: Vec<(Tri3, Color, Color)>, + tris: Vec<(Tri3, V3, Color)>, } impl Scene { @@ -14,7 +17,13 @@ impl Scene { Self { tris: Vec::new() } } - pub fn render(&self, r: &mut impl Renderer, camera_pos: V3) { + pub fn render( + &self, + r: &mut impl Renderer, + camera_pos: V3, + camera_rot: M3x3, + screen_rel_pos: V3, + ) { let mut indices_with_scores = self .tris .iter() @@ -31,29 +40,102 @@ impl Scene { indices_with_scores.sort_by(|a, b| b.1.total_cmp(&a.1)); for (i, _) in indices_with_scores { - let (tri3, outline_color, fill_color) = &self.tris[i]; + let (tri3, normal, color) = &self.tris[i]; // if !(tri3.0.2 >= -1.0 && tri3.1.2 >= -1.0 && tri3.2.2 >= -1.0) { // continue; // } - // if tri3.normal().dot(tri3.0 - camera_pos) >= 0.0 { + // if normal.dot(camera_pos - tri3.0) < 0.0 { // continue; // } - // - println!("{tri3:?}"); - let tri2 = tri3.project_2d(camera_pos); + let tri2 = tri3.project_2d(camera_pos, camera_rot, screen_rel_pos); - println!("{tri2:?}"); - - r.draw_triangles(&[tri2], *fill_color); - r.draw_line(tri2.0, tri2.1, *outline_color); - r.draw_line(tri2.0, tri2.2, *outline_color); + r.draw_triangles(&[tri2], *color); + r.draw_line(tri2.0, tri2.1, Color::BLACK); + r.draw_line(tri2.1, tri2.2, Color::BLACK); + r.draw_line(tri2.2, tri2.0, Color::BLACK); } } - pub fn draw_triangle(&mut self, tri: Tri3, outline_color: Color, fill_color: Color) { - self.tris.push((tri, outline_color, fill_color)); + pub fn draw_model(&mut self, model: Model) { + self.tris.extend(model.tris()); + } + + pub fn draw_triangle(&mut self, tri: Tri3, color: Color) { + self.tris.push((tri, tri.normal(), color)); + } +} + +pub struct Model { + tris: Vec<(Tri3, V3, Color)>, +} + +impl Model { + pub fn new() -> Self { + Self { tris: Vec::new() } + } + + pub fn tris(&self) -> impl Iterator { + self.tris.iter().copied() + } + + pub fn translate(&mut self, offset: V3) -> &mut Self { + for tri in &mut self.tris { + tri.0 = tri.0.translate(offset); + } + self + } + + pub fn rotate_by_m3x3(&mut self, rot: M3x3) -> &mut Self { + for tri in &mut self.tris { + tri.0 = tri.0.rotate_by_m3x3(rot); + } + self + } + + pub fn scale(&mut self, scale: V3) -> &mut Self { + for tri in &mut self.tris { + tri.0 = tri.0.scale(scale); + } + self + } + + pub fn add_tri(&mut self, tri: Tri3, color: Color) -> &mut Self { + self.tris.push((tri, tri.normal(), color)); + self + } + + pub fn add_obj(&mut self, obj: &Obj, color: Color) -> &mut Self { + let count = obj.indices.len() / 3; + for i in 0..count { + let v0 = obj.vertices[obj.indices[i * 3] as usize]; + let v1 = obj.vertices[obj.indices[i * 3 + 1] as usize]; + let v2 = obj.vertices[obj.indices[i * 3 + 2] as usize]; + + self.tris.push(( + Tri3( + V3( + v0.position[0] as _, + v0.position[1] as _, + v0.position[2] as _, + ), + V3( + v1.position[0] as _, + v1.position[1] as _, + v1.position[2] as _, + ), + V3( + v2.position[0] as _, + v2.position[1] as _, + v2.position[2] as _, + ), + ), + V3(v0.normal[0] as _, v0.normal[1] as _, v0.normal[2] as _), + color, + )); + } + self } } diff --git a/simul/src/tri3.rs b/simul/src/tri3.rs index cc159fd..c0af797 100644 --- a/simul/src/tri3.rs +++ b/simul/src/tri3.rs @@ -1,4 +1,4 @@ -use crate::{tri2::Tri2, v3::V3}; +use crate::{m3x3::M3x3, tri2::Tri2, v3::V3}; #[derive(Clone, Copy, Debug)] pub struct Tri3(pub V3, pub V3, pub V3); @@ -20,20 +20,27 @@ impl Tri3 { ) } - pub fn rotate(&self, rot: V3) -> Self { - self.map(|v| v.rotate(rot)) + pub fn rotate_by_v3(&self, rot: V3) -> Self { + self.map(|v| v.rotate_by_v3(rot)) + } + pub fn rotate_by_m3x3(&self, rot: M3x3) -> Self { + self.map(|v| v.rotate_by_m3x3(rot)) } - pub fn project_2d(&self, camera_pos: V3) -> Tri2 { + pub fn project_2d(&self, camera_pos: V3, camera_rot: M3x3, screen_rel_pos: V3) -> Tri2 { Tri2( - self.0.project_2d(camera_pos), - self.1.project_2d(camera_pos), - self.2.project_2d(camera_pos), + self.0.project_2d(camera_pos, camera_rot, screen_rel_pos), + self.1.project_2d(camera_pos, camera_rot, screen_rel_pos), + self.2.project_2d(camera_pos, camera_rot, screen_rel_pos), ) } pub fn translate(&self, offset: V3) -> Self { - self.map(|v| v + offset) + self.map(|v| v.translate(offset)) + } + + pub fn scale(&self, scale: V3) -> Self { + self.map(|v| v.scale(scale)) } pub fn points(&self) -> [V3; 3] { diff --git a/simul/src/v2.rs b/simul/src/v2.rs index 57ff770..1c34a80 100644 --- a/simul/src/v2.rs +++ b/simul/src/v2.rs @@ -19,8 +19,7 @@ impl V2 { pub fn reduce f64>(&self, initial: f64, func: F) -> f64 { let acc = initial; let acc = func(acc, self.0); - let acc = func(acc, self.1); - acc + func(acc, self.1) } pub fn normal(&self) -> V2 { diff --git a/simul/src/v3.rs b/simul/src/v3.rs index d46ed89..f059eda 100644 --- a/simul/src/v3.rs +++ b/simul/src/v3.rs @@ -26,25 +26,31 @@ impl V3 { let acc = initial; let acc = func(acc, self.0); let acc = func(acc, self.1); - let acc = func(acc, self.0); - acc + func(acc, self.0) } - pub fn cross(&self, rhs: Self) -> Self { + pub fn cross(&self, rhs: V3) -> Self { let V3(ax, ay, az) = self; let V3(bx, by, bz) = rhs; V3(ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx) } - pub fn rotate(&self, rot: Self) -> Self { + pub fn rotate_by_v3(&self, rot: V3) -> Self { M3x3::new_rotate_z(rot.2) * (M3x3::new_rotate_y(rot.1) * (M3x3::new_rotate_x(rot.0) * *self)) } - pub fn project_2d(&self, camera_pos: V3) -> V2 { + pub fn rotate_by_m3x3(&self, rot: M3x3) -> Self { + rot * *self + } + + /// See https://en.wikipedia.org/wiki/3D_projection#Mathematical_formula + /// for details on the implementation. + pub fn project_2d(&self, camera_pos: V3, camera_rot: M3x3, screen_rel_pos: V3) -> V2 { + let a = *self; let c = camera_pos; - let d = *self - c; - let e = V3(0.0, 0.0, 0.0) - c; + let d = (a - c).rotate_by_m3x3(camera_rot); + let e = screen_rel_pos - c; V2(e.2 / d.2 * d.0 + e.0, e.2 / d.2 * d.1 + e.1) } @@ -52,8 +58,12 @@ impl V3 { self.zip(rhs, |a, b| a * b).reduce(0.0, |acc, v| acc + v) } - pub fn translate(&self, trans: V3) -> V3 { - *self + trans + pub fn translate(&self, offset: Self) -> Self { + *self + offset + } + + pub fn scale(&self, scale: Self) -> Self { + self.zip(scale, |a, b| a * b) } pub fn len(&self) -> f64 { diff --git a/simul/src/window.rs b/simul/src/window.rs index b69f7fc..99daf7d 100644 --- a/simul/src/window.rs +++ b/simul/src/window.rs @@ -88,7 +88,7 @@ impl Window { time_before = time_now; app.update(delta_time); - self.canvas.set_draw_color(Color::RGB(20, 20, 20)); + self.canvas.set_draw_color(Color::RGB(30, 30, 30)); self.canvas.clear(); app.render(self); self.canvas.present();