From 346e9aa85de45062a55a22b552969292145a210f Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Sat, 6 Sep 2025 12:54:20 -0700 Subject: [PATCH] renders the diablo model --- src/main.rs | 33 +++++++++++------- src/model.rs | 62 +++++++++++++++++++++++++++------ src/point.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++++++---- src/tga.rs | 4 +-- 4 files changed, 165 insertions(+), 32 deletions(-) diff --git a/src/main.rs b/src/main.rs index 348ace8..a613db9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,14 +37,23 @@ fn main() { let mut framebuffer = TGAImage::new(w, h, TGAFormat::RGB); let model = Model::from_obj("diablo3_pose.obj"); - dbg!(model); + dbg!(model.verts.len()); + let mut max_face = 0usize; + let mut min_face = usize::MAX; - framebuffer - .write_file("framebuffer.tga", true, true) - .unwrap(); + for f in model.faces.iter() { + for &f in f.iter() { + max_face = f.max(max_face); + min_face = f.min(min_face); + } + } + + model.render_wireframe(&mut framebuffer); + + framebuffer.write_file("diablo.tga", true, true).unwrap(); } -fn baseline(framebuffer: &mut TGAImage) { +fn _baseline(framebuffer: &mut TGAImage) { let ax = 7; let ay = 3; let bx = 12; @@ -56,9 +65,9 @@ fn baseline(framebuffer: &mut TGAImage) { framebuffer.set(bx, by, WHITE); framebuffer.set(cx, cy, WHITE); - let a = Point::new(ax, ay); - let b = Point::new(bx, by); - let c = Point::new(cx, cy); + let a = Point2i::newu(ax, ay); + let b = Point2i::newu(bx, by); + let c = Point2i::newu(cx, cy); line(a, b, framebuffer, BLUE); line(c, b, framebuffer, GREEN); @@ -66,17 +75,17 @@ fn baseline(framebuffer: &mut TGAImage) { line(a, c, framebuffer, RED); } -fn bench(fb: &mut TGAImage) { +fn _bench(fb: &mut TGAImage) { let mut rng = rand::rng(); for _ in 0..1 << 24 { let ax = rng.random_range(0..fb.width); let ay = rng.random_range(0..fb.height); - let a = Point::new(ax, ay); + let a = Point2i::newu(ax, ay); let bx = rng.random_range(0..fb.width); let by = rng.random_range(0..fb.height); - let b = Point::new(bx, by); + let b = Point2i::newu(bx, by); line( a, @@ -87,7 +96,7 @@ fn bench(fb: &mut TGAImage) { } } -fn line(mut a: Point, mut b: Point, fb: &mut TGAImage, color: TGAColor) { +fn line(mut a: Point2i, mut b: Point2i, fb: &mut TGAImage, color: TGAColor) { let is_steep = (a.x - b.x).abs() < (a.y - b.y).abs(); if is_steep { std::mem::swap(&mut a.x, &mut a.y); diff --git a/src/model.rs b/src/model.rs index f4bf407..363d256 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,13 +1,10 @@ -#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)] -pub struct Point3 { - store: [f32; 3], -} +use std::ops::{Deref, DerefMut}; -impl From<[f32; 3]> for Point3 { - fn from(value: [f32; 3]) -> Self { - Self { store: value } - } -} +use crate::{ + RED, line, + point::{Point2i, Point3f}, + tga::TGAImage, +}; #[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)] pub struct Face { @@ -20,9 +17,23 @@ impl From<[usize; 3]> for Face { } } +impl Deref for Face { + type Target = [usize; 3]; + + fn deref(&self) -> &Self::Target { + &self.store + } +} + +impl DerefMut for Face { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.store + } +} + #[derive(Default, Debug, Clone, PartialEq, PartialOrd)] pub struct Model { - pub verts: Vec, + pub verts: Vec, pub faces: Vec, } @@ -49,7 +60,15 @@ impl Model { if *t == "f" { let f: Vec = line[1..4] .iter() - .map(|l| l.split('/').take(1).next().unwrap().parse().unwrap()) + .map(|l| { + l.split('/') + .take(1) + .next() + .unwrap() + .parse::() + .unwrap() + - 1 + }) .collect(); let f: [usize; 3] = f.try_into().unwrap(); faces.push(f.into()); @@ -59,4 +78,25 @@ impl Model { Self { verts, faces } } + + pub fn render_wireframe(&self, framebuffer: &mut TGAImage) { + let width = framebuffer.width; + let height = framebuffer.height; + + for face in self.faces.iter() { + let a = world2view(self.verts[face[0]], width, height); + let b = world2view(self.verts[face[1]], width, height); + let c = world2view(self.verts[face[2]], width, height); + line(a, b, framebuffer, RED); + line(b, c, framebuffer, RED); + line(c, a, framebuffer, RED); + } + } +} + +fn world2view(point: Point3f, width: u32, height: u32) -> Point2i { + let point = (point + 1.0) * 0.5; + let x = (point.x().min(1.0) * width as f32).round_ties_even() as i32; + let y = (point.y().min(1.0) * height as f32).round_ties_even() as i32; + Point2i::new(x, y) } diff --git a/src/point.rs b/src/point.rs index fbcc517..60a8f30 100644 --- a/src/point.rs +++ b/src/point.rs @@ -1,13 +1,16 @@ -use std::ops::{Add, Mul, Sub}; +use std::ops::{Add, Deref, DerefMut, Div, Mul, Sub}; #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Point { +pub struct Point2i { pub x: i32, pub y: i32, } -impl Point { - pub fn new(x: u32, y: u32) -> Self { +impl Point2i { + pub fn new(x: i32, y: i32) -> Self { + Self { x, y } + } + pub fn newu(x: u32, y: u32) -> Self { Self { x: x as i32, y: y as i32, @@ -15,7 +18,7 @@ impl Point { } } -impl Add for Point { +impl Add for Point2i { type Output = Self; fn add(self, rhs: Self) -> Self::Output { @@ -26,7 +29,7 @@ impl Add for Point { } } -impl Sub for Point { +impl Sub for Point2i { type Output = Self; fn sub(self, rhs: Self) -> Self::Output { @@ -37,7 +40,7 @@ impl Sub for Point { } } -impl Mul for Point { +impl Mul for Point2i { type Output = Self; fn mul(self, rhs: Self) -> Self::Output { @@ -47,3 +50,84 @@ impl Mul for Point { } } } + +#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct Point3f { + store: [f32; 3], +} + +impl From<[f32; 3]> for Point3f { + fn from(value: [f32; 3]) -> Self { + Self { store: value } + } +} + +impl Deref for Point3f { + type Target = [f32; 3]; + + fn deref(&self) -> &Self::Target { + &self.store + } +} + +impl DerefMut for Point3f { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.store + } +} + +impl Point3f { + pub fn new(x: f32, y: f32, z: f32) -> Self { + Self { store: [x, y, z] } + } + + pub fn x(&self) -> f32 { + self.store[0] + } + + pub fn y(&self) -> f32 { + self.store[1] + } + + pub fn z(&self) -> f32 { + self.store[2] + } +} + +impl Add for Point3f { + type Output = Self; + + fn add(self, rhs: f32) -> Self::Output { + Self { + store: [self[0] + rhs, self[1] + rhs, self[2] + rhs], + } + } +} + +impl Mul for Point3f { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + Self { + store: [self[0] * rhs, self[1] * rhs, self[2] * rhs], + } + } +} + +impl Div for Point3f { + type Output = Self; + + fn div(self, rhs: f32) -> Self::Output { + self * (1.0 / rhs) + } +} + +impl From for Point2i { + fn from(p: Point3f) -> Self { + Self { + x: p.x().round_ties_even() as i32, + y: p.y().round_ties_even() as i32, + } + } +} diff --git a/src/tga.rs b/src/tga.rs index b73ea36..cd09164 100644 --- a/src/tga.rs +++ b/src/tga.rs @@ -236,12 +236,12 @@ impl TGAImage { } pub fn set(&mut self, x: u32, y: u32, color: TGAColor) { - if self.data.is_empty() || x > self.width || y > self.height { + if self.data.is_empty() || x >= self.width || y >= self.height { return; } let bpp = self.bytes_per_pixel() as usize; - let start = (x + y * self.width) as usize * bpp; + let start = (x + (y * self.width)) as usize * bpp; let end = start + bpp; self.data[start..end].copy_from_slice(&color[0..bpp]); }