renders the diablo model

This commit is contained in:
Joe Ardent 2025-09-06 12:54:20 -07:00
parent 85f945aef7
commit 346e9aa85d
4 changed files with 165 additions and 32 deletions

View file

@ -37,14 +37,23 @@ fn main() {
let mut framebuffer = TGAImage::new(w, h, TGAFormat::RGB); let mut framebuffer = TGAImage::new(w, h, TGAFormat::RGB);
let model = Model::from_obj("diablo3_pose.obj"); 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 for f in model.faces.iter() {
.write_file("framebuffer.tga", true, true) for &f in f.iter() {
.unwrap(); 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 ax = 7;
let ay = 3; let ay = 3;
let bx = 12; let bx = 12;
@ -56,9 +65,9 @@ fn baseline(framebuffer: &mut TGAImage) {
framebuffer.set(bx, by, WHITE); framebuffer.set(bx, by, WHITE);
framebuffer.set(cx, cy, WHITE); framebuffer.set(cx, cy, WHITE);
let a = Point::new(ax, ay); let a = Point2i::newu(ax, ay);
let b = Point::new(bx, by); let b = Point2i::newu(bx, by);
let c = Point::new(cx, cy); let c = Point2i::newu(cx, cy);
line(a, b, framebuffer, BLUE); line(a, b, framebuffer, BLUE);
line(c, b, framebuffer, GREEN); line(c, b, framebuffer, GREEN);
@ -66,17 +75,17 @@ fn baseline(framebuffer: &mut TGAImage) {
line(a, c, framebuffer, RED); line(a, c, framebuffer, RED);
} }
fn bench(fb: &mut TGAImage) { fn _bench(fb: &mut TGAImage) {
let mut rng = rand::rng(); let mut rng = rand::rng();
for _ in 0..1 << 24 { for _ in 0..1 << 24 {
let ax = rng.random_range(0..fb.width); let ax = rng.random_range(0..fb.width);
let ay = rng.random_range(0..fb.height); 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 bx = rng.random_range(0..fb.width);
let by = rng.random_range(0..fb.height); let by = rng.random_range(0..fb.height);
let b = Point::new(bx, by); let b = Point2i::newu(bx, by);
line( line(
a, 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(); let is_steep = (a.x - b.x).abs() < (a.y - b.y).abs();
if is_steep { if is_steep {
std::mem::swap(&mut a.x, &mut a.y); std::mem::swap(&mut a.x, &mut a.y);

View file

@ -1,13 +1,10 @@
#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)] use std::ops::{Deref, DerefMut};
pub struct Point3 {
store: [f32; 3],
}
impl From<[f32; 3]> for Point3 { use crate::{
fn from(value: [f32; 3]) -> Self { RED, line,
Self { store: value } point::{Point2i, Point3f},
} tga::TGAImage,
} };
#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)] #[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Face { 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)] #[derive(Default, Debug, Clone, PartialEq, PartialOrd)]
pub struct Model { pub struct Model {
pub verts: Vec<Point3>, pub verts: Vec<Point3f>,
pub faces: Vec<Face>, pub faces: Vec<Face>,
} }
@ -49,7 +60,15 @@ impl Model {
if *t == "f" { if *t == "f" {
let f: Vec<usize> = line[1..4] let f: Vec<usize> = line[1..4]
.iter() .iter()
.map(|l| l.split('/').take(1).next().unwrap().parse().unwrap()) .map(|l| {
l.split('/')
.take(1)
.next()
.unwrap()
.parse::<usize>()
.unwrap()
- 1
})
.collect(); .collect();
let f: [usize; 3] = f.try_into().unwrap(); let f: [usize; 3] = f.try_into().unwrap();
faces.push(f.into()); faces.push(f.into());
@ -59,4 +78,25 @@ impl Model {
Self { verts, faces } 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)
} }

View file

@ -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)] #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Point { pub struct Point2i {
pub x: i32, pub x: i32,
pub y: i32, pub y: i32,
} }
impl Point { impl Point2i {
pub fn new(x: u32, y: u32) -> Self { pub fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
pub fn newu(x: u32, y: u32) -> Self {
Self { Self {
x: x as i32, x: x as i32,
y: y as i32, y: y as i32,
@ -15,7 +18,7 @@ impl Point {
} }
} }
impl Add for Point { impl Add for Point2i {
type Output = Self; type Output = Self;
fn add(self, rhs: Self) -> Self::Output { 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; type Output = Self;
fn sub(self, rhs: Self) -> Self::Output { 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; type Output = Self;
fn mul(self, rhs: Self) -> Self::Output { 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<f32> 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<f32> 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<f32> for Point3f {
type Output = Self;
fn div(self, rhs: f32) -> Self::Output {
self * (1.0 / rhs)
}
}
impl From<Point3f> for Point2i {
fn from(p: Point3f) -> Self {
Self {
x: p.x().round_ties_even() as i32,
y: p.y().round_ties_even() as i32,
}
}
}

View file

@ -236,12 +236,12 @@ impl TGAImage {
} }
pub fn set(&mut self, x: u32, y: u32, color: TGAColor) { 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; return;
} }
let bpp = self.bytes_per_pixel() as usize; 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; let end = start + bpp;
self.data[start..end].copy_from_slice(&color[0..bpp]); self.data[start..end].copy_from_slice(&color[0..bpp]);
} }