diff --git a/src/main.rs b/src/main.rs index 8b55e6d..322b756 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,8 @@ mod model; mod triangle; use triangle::*; +use crate::model::Model; + const BLACK: TGAColor = TGAColor { bgra: [0u8; 4] }; const WHITE: TGAColor = TGAColor { @@ -35,18 +37,13 @@ const YELLOW: TGAColor = TGAColor { }; fn main() { - let w = 640; - let h = 640; + let w = 800; + let h = 800; let mut fb = TGAImage::new(w, h, TGAFormat::RGB); + let model = Model::from_obj("diablo3_pose.obj"); + model.render_triangles(&mut fb); - let a = Point2i::new(170, 40).with_color(BLUE); - let b = Point2i::new(550, 390).with_color(GREEN); - let c = Point2i::new(230, 590).with_color(RED); - - let t = Triangle2i::new(a, b, c); - t.render_lines(20, BLACK, &mut fb); - - fb.write_file("triangle.tga", true).unwrap(); + //fb.write_file("triangle.tga", true).unwrap(); } fn line(mut a: Point2i, mut b: Point2i, color: TGAColor, mut fb: &mut TGAImage) { diff --git a/src/model.rs b/src/model.rs index cb9023f..8a85958 100644 --- a/src/model.rs +++ b/src/model.rs @@ -4,8 +4,9 @@ use rand::Rng; use crate::{ RED, Triangle2i, line, - point::{Point2i, Point3f}, + point::{Point2i, Point3f, Point3i}, tga::TGAImage, + triangle::Triangle3i, }; #[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)] @@ -98,12 +99,18 @@ impl Model { let width = framebuffer.width; let height = framebuffer.height; + let mut db = TGAImage::new( + framebuffer.width, + framebuffer.height, + crate::tga::TGAFormat::Grayscale, + ); + let mut rng = rand::rng(); 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); - let triangle = Triangle2i::new(a, b, c); + let a = world2iso(self.verts[face[0]], width, height); + let b = world2iso(self.verts[face[1]], width, height); + let c = world2iso(self.verts[face[2]], width, height); + let triangle = Triangle3i::new(a, b, c); let color = [ rng.random_range(0..=255u8), rng.random_range(0..=255u8), @@ -111,8 +118,9 @@ impl Model { 255, ] .into(); - triangle.render_filled(color, framebuffer); + triangle.render_filled(color, framebuffer, &mut db); } + //db.write_file("zbuffer.tga", true); } } @@ -122,3 +130,11 @@ fn world2view(point: Point3f, width: u32, height: u32) -> Point2i { let y = (point.y() * height as f32).round_ties_even() as i32; Point2i::new(x, y) } + +fn world2iso(point: Point3f, width: u32, height: u32) -> Point3i { + let point = (point + 1.0) * 0.5; + let x = (point.x() * width as f32).round_ties_even() as i32; + let y = (point.y() * height as f32).round_ties_even() as i32; + let z = (point.z() * 255.0).round_ties_even() as i32; + Point3i::new(x, y, z) +} diff --git a/src/point.rs b/src/point.rs index 35c785e..6102c40 100644 --- a/src/point.rs +++ b/src/point.rs @@ -184,3 +184,112 @@ impl From for Point2i { } } } + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Point3i { + pub x: i32, + pub y: i32, + pub z: i32, + pub color: Option, +} + +impl Point3i { + pub fn new(x: i32, y: i32, z: i32) -> Self { + Self { + x, + y, + z, + color: None, + } + } + + pub fn with_color(self, color: TGAColor) -> Self { + Self { + color: Some(color), + ..self + } + } +} + +impl From for Point3i { + fn from(p: Point3f) -> Self { + Self { + x: p.x().round_ties_even() as i32, + y: p.y().round_ties_even() as i32, + z: p.z().round_ties_even() as i32, + color: None, + } + } +} + +impl Add for Point3i { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + x: self.x + rhs.x, + y: self.y + rhs.y, + z: self.z + rhs.z, + color: if self.color.is_none() { + rhs.color + } else { + self.color + .map(|c| if let Some(oc) = rhs.color { c + oc } else { c }) + }, + } + } +} + +impl Sub for Point3i { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self { + x: self.x - rhs.x, + y: self.y - rhs.y, + z: self.z - rhs.z, + color: if self.color.is_none() { + rhs.color + } else { + self.color + .map(|c| if let Some(oc) = rhs.color { c - oc } else { c }) + }, + } + } +} + +impl Mul for Point3i { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + Self { + x: self.x * rhs.x, + y: self.y * rhs.y, + z: self.z * rhs.z, + color: None, + } + } +} + +impl Mul for Point3i { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + Self { + x: (self.x as f32 * rhs).round_ties_even() as i32, + y: (self.y as f32 * rhs).round_ties_even() as i32, + z: (self.z as f32 * rhs).round_ties_even() as i32, + color: self.color, + } + } +} + +impl From for Point2i { + fn from(p: Point3i) -> Self { + Self { + x: p.x, + y: p.y, + color: p.color, + } + } +} diff --git a/src/tga.rs b/src/tga.rs index ae5c5d1..e76d2df 100644 --- a/src/tga.rs +++ b/src/tga.rs @@ -73,6 +73,12 @@ impl From for TGAColor { } } +impl From for TGAColor { + fn from(value: u8) -> Self { + [value; 4].into() + } +} + impl Add for TGAColor { type Output = Self; @@ -145,7 +151,7 @@ pub struct TGAImage { pub format: TGAFormat, pub width: u32, pub height: u32, - data: Vec, + pub data: Vec, } pub type IoResult = std::result::Result<(), TGAError>; diff --git a/src/triangle.rs b/src/triangle.rs index a477b7e..39853c4 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -4,7 +4,7 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator}; use crate::{ AABB, BLACK, - point::Point2i, + point::{Point2i, Point3i}, tga::{TGAColor, TGAImage}, }; @@ -38,7 +38,7 @@ impl Triangle2i { } } - pub fn render_filled(&self, color: TGAColor, fb: &mut TGAImage) { + pub fn render_filled(&self, color: TGAColor, fb: &mut TGAImage, depth_buffer: &mut TGAImage) { let fb = Arc::new(Mutex::new(fb)); let bb = self.bb(); let total_area = self.signed_area(); @@ -139,3 +139,96 @@ impl Triangle2i { Point2i { x, y, color } } } + +pub struct Triangle3i { + a: Point3i, + b: Point3i, + c: Point3i, +} + +struct Wrapper(T); +unsafe impl Send for Wrapper {} + +impl Triangle3i { + pub fn new(a: Point3i, b: Point3i, c: Point3i) -> Self { + Self { a, b, c } + } + + pub fn signed_area(&self) -> f32 { + 0.5 * ((self.b.y - self.a.y) * (self.a.x + self.b.x) + + (self.c.y - self.b.y) * (self.c.x + self.b.x) + + (self.a.y - self.c.y) * (self.a.x + self.c.x)) as f32 + } + + pub fn bb(&self) -> AABB { + let xmax = self.a.x.max(self.b.x.max(self.c.x)); + let xmin = self.a.x.min(self.b.x.min(self.c.x)); + + let ymax = self.a.y.max(self.b.y.max(self.c.y)); + let ymin = self.a.y.min(self.b.y.min(self.c.y)); + AABB { + lower_left: Point2i::new(xmin, ymin), + upper_right: Point2i::new(xmax, ymax), + } + } + + pub fn render_filled(&self, color: TGAColor, fb: &mut TGAImage, depth_buffer: &mut TGAImage) { + let fb = Arc::new(Mutex::new(fb)); + let zb = Arc::new(Mutex::new(depth_buffer)); + let bb = self.bb(); + let total_area = self.signed_area(); + if total_area < 1.0 { + return; + } + let it = 1.0 / total_area; + (bb.xmin()..=bb.xmax()).into_par_iter().for_each(|x| { + (bb.ymin()..=bb.ymax()).into_par_iter().for_each(|y| { + let p = Point2i::new(x, y); + let alpha = Triangle2i::new(p, self.b.into(), self.c.into()).signed_area() * it; + let beta = Triangle2i::new(p, self.c.into(), self.a.into()).signed_area() * it; + let gamma = Triangle2i::new(p, self.a.into(), self.b.into()).signed_area() * it; + + if alpha.is_sign_positive() && beta.is_sign_positive() && gamma.is_sign_positive() { + let color = if let Some(ca) = self.a.color + && let Some(cb) = self.b.color + && let Some(cc) = self.c.color + { + color + (ca * alpha) + (cb * beta) + (cc * gamma) + } else { + color + }; + let z = (alpha * self.a.z as f32 + + beta * self.b.z as f32 + + gamma * self.c.z as f32) + .round_ties_even() as u8; + if let Ok(mut zb) = zb.lock() { + let oz = zb.get(x as u32, y as u32).unwrap_or_default().b(); + if oz <= z { + if let Ok(mut fb) = fb.lock() { + zb.set(x as u32, y as u32, z.into()); + fb.set(x as u32, y as u32, color); + } + } + } + }; + }); + }); + } + + pub fn centroid(&self) -> Point3i { + let x = self.a.x / 3 + self.b.x / 3 + self.c.x / 3; + let y = self.a.y / 3 + self.b.y / 3 + self.c.y / 3; + let z = self.a.z / 3 + self.b.z / 3 + self.c.z / 3; + let has_color = self.a.color.is_some() || self.b.color.is_some() || self.c.color.is_some(); + let denom = 1.0 / 3.0; + let color = if has_color { + let c = self.a.color.unwrap_or(BLACK) * denom + + self.b.color.unwrap_or(BLACK) * denom + + self.c.color.unwrap_or(BLACK) * denom; + Some(c) + } else { + None + }; + Point3i { x, y, z, color } + } +}