From d738478f54fd557113e423fe0b0143726a146bc2 Mon Sep 17 00:00:00 2001 From: sfja Date: Thu, 26 Mar 2026 16:29:08 +0100 Subject: [PATCH] fix 3d wireframe render --- game/src/engine/game.rs | 15 ++-- game/src/engine/math.rs | 83 +++++++++++--------- game/src/engine/mod.rs | 17 +++-- game/src/engine/r3d.rs | 68 +++++++++++++++++ game/src/engine/sdl_io.rs | 157 ++++++++++++++------------------------ game/src/engine/shapes.rs | 102 ++++++++++++++++--------- game/src/main.rs | 30 ++++---- 7 files changed, 276 insertions(+), 196 deletions(-) create mode 100644 game/src/engine/r3d.rs diff --git a/game/src/engine/game.rs b/game/src/engine/game.rs index 7183d76..e48a079 100644 --- a/game/src/engine/game.rs +++ b/game/src/engine/game.rs @@ -1,6 +1,4 @@ -use sdl3::pixels::Color; - -use crate::engine::math::{Vertex, V2, V3}; +use crate::engine::math::{V2, V3}; use std::{marker::PhantomData, time::Duration}; use super::error::Error; @@ -16,10 +14,15 @@ pub trait Io> { pub trait Renderer { fn draw_rect(&mut self, pos: V2, size: V2, color: Color); - fn point(&mut self, pos: V2, color: Color); + fn draw_point(&mut self, pos: V2, color: Color); fn draw_line(&mut self, from: V2, to: V2, color: Color); - fn draw_cube(&mut self, pos: V3, size: V3, outline_colors: Color, fill_color: Color); - fn draw_triangle(&mut self, triangle: Vertex, color: Color); +} + +#[derive(Clone, Copy)] +pub enum Color { + WHITE, + GREEN, + WED, } pub enum Event { diff --git a/game/src/engine/math.rs b/game/src/engine/math.rs index 20415b6..d667d72 100644 --- a/game/src/engine/math.rs +++ b/game/src/engine/math.rs @@ -1,4 +1,4 @@ -use std::ops::{Add, AddAssign, Mul, MulAssign}; +use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; #[derive(Clone, Copy, PartialEq, Debug)] pub struct V2(pub f64, pub f64); @@ -6,17 +6,34 @@ pub struct V2(pub f64, pub f64); #[derive(Clone, Copy, PartialEq, Debug)] pub struct V3(pub f64, pub f64, pub f64); +impl V3 { + pub fn project_2d(&self) -> V2 { + // V2(self.0 / self.2, self.1 / self.2) + let c = V3(0.0, 0.0, -1.0); + let d = *self - c; + let e = V3(0.0, 0.0, 0.0) - c; + V2(e.2 / d.2 * d.0 + e.0, e.2 / d.2 * d.1 + e.1) + } + + pub fn cross(&self, rhs: Self) -> Self { + let V3(ax, ay, az) = self; + let V3(bx, by, bz) = rhs; + Self(ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx) + } +} + impl Add for V3 { - type Output = V3; + type Output = Self; fn add(self, rhs: Self) -> Self::Output { Self(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2) } } +impl Sub for V3 { + type Output = Self; -impl AddAssign for V3 { - fn add_assign(&mut self, rhs: Self) { - *self = *self + rhs; + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2) } } @@ -28,55 +45,53 @@ impl Mul for V3 { } } +impl Add<&Self> for V3 { + type Output = Self; + + fn add(self, rhs: &Self) -> Self::Output { + Self(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2) + } +} +impl Sub<&Self> for V3 { + type Output = Self; + + fn sub(self, rhs: &Self) -> Self::Output { + Self(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2) + } +} + +impl AddAssign for V3 { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} +impl SubAssign for V3 { + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} impl MulAssign for V3 { fn mul_assign(&mut self, rhs: f64) { *self = *self * rhs; } } -impl V3 { - pub fn project(&self) -> V2 { - V2(self.0 / self.2, self.1 / self.2) - } - - pub fn delta(&self, v: V3) -> V3 { - V3(v.0 - self.0, v.1 - self.1, v.2 - self.2) - } -} - -#[derive(Clone, PartialEq, Debug)] -pub struct Vertex(pub V3, pub V3, pub V3); - -impl Vertex { - pub fn normal_vector(&self) -> V3 { - let vector_a = self.1.delta(self.0); - let vector_b = self.1.delta(self.2); - - V3( - vector_a.1 * vector_b.2 - vector_a.2 * vector_b.1, - vector_a.2 * vector_b.0 - vector_a.0 * vector_b.2, - vector_a.0 * vector_b.1 - vector_a.1 * vector_b.0, - ) - } -} - #[cfg(test)] mod test { use super::*; #[test] fn v3_ops() { - let v2 = |v: f64| V2(v, v); let v3 = |v: f64| V3(v, v, v); assert_eq!(v3(1.0) + v3(2.0), v3(3.0)); + assert_eq!(v3(3.0) - v3(2.0), v3(1.0)); + assert_eq!(v3(2.0) * 2.0, v3(4.0)); let mut a = v3(1.0); a += v3(2.0); assert_eq!(a, v3(3.0)); - assert_eq!(v3(2.0) * 2.0, v3(4.0)); - let mut a = v3(2.0); a *= 2.0; assert_eq!(a, v3(4.0)); diff --git a/game/src/engine/mod.rs b/game/src/engine/mod.rs index be969fd..2884b52 100644 --- a/game/src/engine/mod.rs +++ b/game/src/engine/mod.rs @@ -1,10 +1,17 @@ +#![allow(unused_imports)] + mod error; mod game; -pub mod math; +mod math; +mod r3d; mod sdl_io; -pub mod shapes; +mod shapes; mod system; -pub use game::{Event, Game, Io, Object, Renderer}; -pub use math::V3; -pub use sdl_io::SdlIo; +pub use error::*; +pub use game::*; +pub use math::*; +pub use r3d::*; +pub use sdl_io::*; +pub use shapes::*; +pub use system::*; diff --git a/game/src/engine/r3d.rs b/game/src/engine/r3d.rs new file mode 100644 index 0000000..da34530 --- /dev/null +++ b/game/src/engine/r3d.rs @@ -0,0 +1,68 @@ +use crate::engine::{game::Renderer, Color, Shape, V2, V3}; + +#[derive(Clone, PartialEq, Debug)] +pub struct Triangle2(pub V2, pub V2, pub V2); + +#[derive(Clone, PartialEq, Debug)] +pub struct Triangle3(pub V3, pub V3, pub V3); + +impl Triangle3 { + pub fn normal_vector(&self) -> V3 { + (self.1 - self.0).cross(self.2 - self.1) + } + + pub fn translate(&self, offset: V3) -> Self { + Self(self.0 + offset, self.1 + offset, self.2 + offset) + } + + pub fn project_2d(&self) -> Triangle2 { + Triangle2( + self.0.project_2d(), + self.1.project_2d(), + self.2.project_2d(), + ) + } +} + +pub struct R3d<'r, R: Renderer> { + r: &'r mut R, +} + +static CAMERA_POS: V3 = V3(0.0, 0.0, -1.0); + +impl<'r, R: Renderer> R3d<'r, R> { + pub fn new(r: &'r mut R) -> Self { + Self { r } + } + + pub fn draw_line(&mut self, from: V3, to: V3, color: Color) { + self.r + .draw_line((from).project_2d(), (to).project_2d(), color); + } + + pub fn draw_triangle(&mut self, triangle: Triangle3, color: Color) { + let triangle = triangle.translate(CAMERA_POS).project_2d(); + + self.r.draw_line(triangle.0, triangle.1, color); + self.r.draw_line(triangle.1, triangle.2, color); + self.r.draw_line(triangle.2, triangle.0, color); + } + + pub fn draw_cube(&mut self, pos: V3, size: V3, outline_color: Color, fill_color: Color) { + let shape = Shape::new_cube(size); + + for face in shape.faces() { + let normal_vector = face.normal_vector(); + let pxyz = face.0 + normal_vector; + self.draw_line(face.0 + pos, pxyz + pos, Color::WED); + if normal_vector.2 < 0.0 { + self.draw_triangle(face.translate(pos - CAMERA_POS), outline_color); + } + } + + for vertex in shape.vertices() { + self.r + .draw_point((vertex + pos).project_2d(), outline_color); + } + } +} diff --git a/game/src/engine/sdl_io.rs b/game/src/engine/sdl_io.rs index 0754428..2dbe32c 100644 --- a/game/src/engine/sdl_io.rs +++ b/game/src/engine/sdl_io.rs @@ -1,9 +1,9 @@ -use std::time::Instant; +use std::time::{Duration, Instant}; use sdl3::{ event::Event, keyboard::Keycode, - pixels::{Color, PixelFormat}, + pixels::{Color as SdlColor, PixelFormat}, rect::Point, render::{Canvas, FPoint, FRect}, video::Window, @@ -13,8 +13,8 @@ use sdl3::{ use crate::engine::{ error::Error, game, - math::{Vertex, V2, V3}, - Object, + math::{V2, V3}, + Color, Renderer, }; pub static WIDTH: f64 = 1280.0; @@ -37,7 +37,7 @@ impl SdlIo { .unwrap(); let mut canvas = window.into_canvas(); - canvas.set_draw_color(Color::BLACK); + canvas.set_draw_color(SdlColor::BLACK); canvas.clear(); canvas.present(); @@ -52,7 +52,9 @@ impl SdlIo { let mut event_pump = self.sdl_context.event_pump().unwrap(); let mut time_before = Instant::now(); - let time_per_frame = 1_000_000_000 / 60; + let time_per_frame = Duration::from_secs_f64(1.0 / 60.0); + let mut print_fps_timer = Instant::now(); + let mut fps_count = 0; 'running: loop { for event in event_pump.poll_iter() { @@ -71,42 +73,60 @@ impl SdlIo { time_before = time_now; game.update(delta_time); - self.canvas.set_draw_color(Color::BLACK); + self.canvas.set_draw_color(SdlColor::BLACK); self.canvas.clear(); game.render(self); self.canvas.present(); + + fps_count += 1; + if (time_now - print_fps_timer).as_secs_f64() > 1.0 { + println!("fps: {fps_count}"); + fps_count = 0; + print_fps_timer = time_now; + } + if delta_time < time_per_frame { + std::thread::sleep(time_per_frame - delta_time); + } } } - fn world_to_screen(&self, v: V2) -> V2 { - V2(v.0 + WIDTH / 2.0, HEIGHT / 2.0 - v.1) + /// world space to screen space + fn scale_w2s(&self, v: V2) -> V2 { + let factor = WIDTH / 2.0; + V2(v.0 * factor, v.1 * factor) + } + /// world space to screen space. + fn translate_w2s(&self, v: V2) -> V2 { + let middle = V2(WIDTH / 2.0, HEIGHT / 2.0); + V2(v.0 + middle.0, middle.1 - v.1) + } + + /// scale -> translate + fn point_w2s(&self, v: V2) -> V2 { + self.translate_w2s(self.scale_w2s(v)) } } -impl game::Renderer for SdlIo { +impl Renderer for SdlIo { fn draw_rect(&mut self, pos: V2, size: V2, color: Color) { - let pos = self.world_to_screen(pos); + let pos = self.point_w2s(pos); + let size = self.scale_w2s(size); self.canvas.set_draw_color(color); self.canvas - .fill_rect(FRect::new( - (pos.0 - size.0 / 2.0) as _, - (pos.1 - size.1 / 2.0) as _, - size.0 as _, - size.1 as _, - )) + .fill_rect(FRect::new(pos.0 as _, pos.1 as _, size.0 as _, size.1 as _)) .unwrap(); } - fn point(&mut self, pos: V2, color: Color) { - let pos = self.world_to_screen(pos); - let size = V2(10.0, 10.0); + fn draw_point(&mut self, pos: V2, color: Color) { + let pos = self.point_w2s(pos); + let size = 4.0; self.canvas.set_draw_color(color); self.canvas .fill_rect(FRect::new( - (pos.0 - size.0 / 2.0) as _, - (pos.1 - size.1 / 2.0) as _, - 10.0, - 10.0, + (pos.0 - size / 2.0) as _, + (pos.1 - size / 2.0) as _, + size as _, + size as _, )) .unwrap(); } @@ -114,88 +134,23 @@ impl game::Renderer for SdlIo { fn draw_line(&mut self, from: V2, to: V2, color: Color) { self.canvas.set_draw_color(color); self.canvas - .draw_line( - FPoint::new(from.0 as f32, from.1 as f32), - FPoint::new(to.0 as f32, to.1 as f32), - ) + .draw_line(self.point_w2s(from), self.point_w2s(to)) .unwrap(); } +} - fn draw_triangle(&mut self, triangle: Vertex, color: Color) { - self.draw_line( - self.world_to_screen(triangle.0.project()), - self.world_to_screen(triangle.1.project()), - color, - ); - self.draw_line( - self.world_to_screen(triangle.1.project()), - self.world_to_screen(triangle.2.project()), - color, - ); - self.draw_line( - self.world_to_screen(triangle.2.project()), - self.world_to_screen(triangle.0.project()), - color, - ); +impl From for FPoint { + fn from(value: V2) -> Self { + FPoint::new(value.0 as _, value.1 as _) } +} - fn draw_cube(&mut self, pos: V3, size: V3, outline_color: Color, fill_color: Color) { - let V3(x, y, z) = pos; - let V3(w, h, mut d) = size; - d /= 200.0; - let points = [ - pos, - V3(x + w, y, z), - V3(x, y + h, z), - V3(x + w, y + h, z), - V3(x, y, z + d), - V3(x + w, y, z + d), - V3(x, y + h, z + d), - V3(x + w, y + h, z + d), - ]; - - let vertices = [ - // south - Vertex(points[0], points[2], points[3]), - Vertex(points[0], points[3], points[1]), - // east - Vertex(points[1], points[3], points[7]), - Vertex(points[1], points[7], points[5]), - // north - Vertex(points[5], points[7], points[6]), - Vertex(points[5], points[6], points[4]), - // west - Vertex(points[4], points[6], points[2]), - Vertex(points[4], points[2], points[0]), - // top - Vertex(points[2], points[6], points[7]), - Vertex(points[2], points[7], points[3]), - // bottom - Vertex(points[5], points[4], points[0]), - Vertex(points[5], points[0], points[1]), - ]; - println!("current triangles"); - - for vertex in vertices { - let normal_vector = vertex.normal_vector(); - - // self.draw_triangle(vertex, outline_color); - println!("{}", normal_vector.2); - - if normal_vector.2 > 0.0 { - self.draw_triangle( - Vertex( - vertex.0, - vertex.1, - V3(vertex.2 .0, vertex.2 .1, vertex.2 .2), - ), - outline_color, - ); - } - } - - for point in points { - self.point(point.project(), outline_color); +impl From for SdlColor { + fn from(value: Color) -> Self { + match value { + Color::WHITE => SdlColor::WHITE, + Color::GREEN => SdlColor::GREEN, + Color::WED => SdlColor::RED, } } } diff --git a/game/src/engine/shapes.rs b/game/src/engine/shapes.rs index c4925e9..4979d16 100644 --- a/game/src/engine/shapes.rs +++ b/game/src/engine/shapes.rs @@ -1,54 +1,82 @@ -use crate::engine::math::{Vertex, V3}; +use crate::engine::{math::V3, Triangle3}; + +static CUBE_VERTICES: [(i8, i8, i8); 8] = [ + (0, 1, 0), // 0 front top left + (1, 1, 0), // 1 front top right + (0, 0, 0), // 2 front bottom left + (1, 0, 0), // 3 front bottom right + (0, 1, 1), // 4 back top left + (1, 1, 1), // 5 back top right + (0, 0, 1), // 6 back bottom left + (1, 0, 1), // 7 back bottom right +]; + +static CUBE_EDGES: [(usize, usize); 12] = [ + (0, 1), // ftl -> ftr + (2, 3), // fbl -> fbr + (4, 5), // btl -> btr + (6, 7), // bbl -> bbr + (0, 2), // ftl -> fbl + (1, 3), // ftr -> fbr + (4, 6), // btl -> bbl + (5, 7), // btr -> bbr + (0, 4), // ftl -> btl + (1, 5), // ftr -> btr + (2, 6), // fbl -> bbl + (3, 7), // fbr -> bbr +]; + +static CUBE_FACES: [(usize, usize, usize); 12] = [ + // front + (0, 1, 2), + (3, 2, 1), + // top + (0, 4, 1), + (5, 1, 4), + // right + (7, 3, 5), + (1, 5, 3), + // back + (5, 4, 7), + (6, 7, 4), + // bottom + (3, 7, 2), + (6, 2, 7), + // left + (4, 0, 6), + (2, 6, 0), +]; pub struct Shape { vertices: Vec, - fragments: Vec<(usize, usize, usize)>, + edges: Vec<(usize, usize)>, + faces: Vec<(usize, usize, usize)>, } impl Shape { - pub fn new_cube(V3(w, h, d): V3) -> Self { + pub fn new_cube(dim: V3) -> Self { Self { - vertices: vec![ - V3(0.0, 0.0, 0.0), // 0 front top left - V3(0.0 + w, 0.0, 0.0), // 1 front top right - V3(0.0, 0.0 + h, 0.0), // 2 front bottom left - V3(0.0 + w, 0.0 + h, 0.0), // 3 front bottom right - V3(0.0, 0.0, 0.0 + d), // 4 back top left - V3(0.0 + w, 0.0, 0.0 + d), // 5 back top right - V3(0.0, 0.0 + h, 0.0 + d), // 6 back bottom left - V3(0.0 + w, 0.0 + h, 0.0 + d), // 7 back bottom right - ], - fragments: vec![ - // back - (0, 1, 2), - (3, 2, 1), - // bottom - (4, 5, 0), - (1, 0, 5), - // left - (4, 0, 6), - (2, 6, 0), - // front - (5, 4, 7), - (6, 7, 4), - // top - (7, 6, 3), - (2, 3, 6), - // right - (1, 5, 3), - (7, 3, 5), - ], + vertices: CUBE_VERTICES + .iter() + .map(|p| scale_i8_vertex(*p, &dim)) + .collect(), + edges: Vec::from_iter(CUBE_EDGES), + faces: Vec::from_iter(CUBE_FACES), } } - pub fn points<'a>(&'a self) -> impl Iterator + 'a { + pub fn vertices<'a>(&'a self) -> impl Iterator + 'a { self.vertices.iter().cloned() } - pub fn vertices<'a>(&'a self) -> impl Iterator + 'a { + pub fn faces<'a>(&'a self) -> impl Iterator + 'a { let verts: &[V3] = &self.vertices; - self.fragments + self.faces .iter() - .map(|(a, b, c)| Vertex(verts[*a], verts[*b], verts[*c])) + .map(|(a, b, c)| Triangle3(verts[*a], verts[*b], verts[*c])) } } + +fn scale_i8_vertex(p: (i8, i8, i8), s: &V3) -> V3 { + V3(p.0 as f64 * s.0, p.1 as f64 * s.1, p.2 as f64 * s.2) +} diff --git a/game/src/main.rs b/game/src/main.rs index 1d36f1d..4f96463 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -2,20 +2,18 @@ use std::time::Duration; -use sdl3::pixels::Color; - -use crate::engine::math::{V2, V3}; +use crate::engine::{Color, R3d, V3}; mod engine; -enum Object { +enum MyObject { Player { pos: V3, vel: V3 }, } -impl engine::Object for Object { +impl engine::Object for MyObject { fn update(&mut self, delta_time: Duration) { match self { - Object::Player { pos, vel } => { + MyObject::Player { pos, vel } => { *pos += *vel * delta_time.as_secs_f64(); } } @@ -23,9 +21,15 @@ impl engine::Object for Object { fn render(&mut self, r: &mut R) { match self { - Object::Player { pos, .. } => { - // r.draw_rect(V2(pos.0, pos.1), V2(400.0, 400.0)); - r.draw_cube(*pos, V3(100.0, 100.0, 100.0), Color::GREEN, Color::WHITE); + MyObject::Player { pos, .. } => { + let mut r3d = R3d::new(r); + r3d.draw_cube(*pos, V3(0.2, 0.2, 0.2), Color::GREEN, Color::WHITE); + r3d.draw_cube( + *pos + V3(-0.4, -0.2, 0.0), + V3(0.2, 0.2, 0.2), + Color::GREEN, + Color::WHITE, + ); } } } @@ -33,11 +37,11 @@ impl engine::Object for Object { fn main() -> Result<(), Box> { let mut sdl_io = engine::SdlIo::new()?; - let mut game = engine::Game::::new()?; + let mut game = engine::Game::::new()?; - let player = Object::Player { - pos: V3(200.0, 100.0, 0.0), - vel: V3(0.0, 0.0, 0.1), + let player = MyObject::Player { + pos: V3(-0.1, -0.1, 0.0), + vel: V3(0.0, 0.1, 0.0), }; game.spawn(player);