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 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);

View file

@ -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<Point3>,
pub verts: Vec<Point3f>,
pub faces: Vec<Face>,
}
@ -49,7 +60,15 @@ impl Model {
if *t == "f" {
let f: Vec<usize> = line[1..4]
.iter()
.map(|l| l.split('/').take(1).next().unwrap().parse().unwrap())
.map(|l| {
l.split('/')
.take(1)
.next()
.unwrap()
.parse::<usize>()
.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)
}

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)]
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<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) {
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]);
}