diff --git a/src/main.rs b/src/main.rs index a613db9..3403224 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,8 @@ extern crate justerror; mod tga; +use std::collections::BTreeMap; + use rand::Rng; use tga::*; @@ -32,25 +34,39 @@ const YELLOW: TGAColor = TGAColor { }; fn main() { - let w = 800; - let h = 800; - let mut framebuffer = TGAImage::new(w, h, TGAFormat::RGB); - let model = Model::from_obj("diablo3_pose.obj"); + let w = 128; + let h = 128; + let mut fb = TGAImage::new(w, h, TGAFormat::RGB); + fill_triangles(&mut fb); + fb.write_file("triangles.tga", true, true).unwrap(); +} - dbg!(model.verts.len()); - let mut max_face = 0usize; - let mut min_face = usize::MAX; +fn fill_triangles(fb: &mut TGAImage) { + /* + triangle( 7, 45, 35, 100, 45, 60, framebuffer, red); + triangle(120, 35, 90, 5, 45, 110, framebuffer, white); + triangle(115, 83, 80, 90, 85, 120, framebuffer, green); + */ - for f in model.faces.iter() { - for &f in f.iter() { - max_face = f.max(max_face); - min_face = f.min(min_face); - } - } + let t1a = Point2i::new(7, 45); + let t1b = Point2i::new(35, 100); + let t1c = Point2i::new(45, 60); + triangle_filled(t1a, t1b, t1c, RED, fb); - model.render_wireframe(&mut framebuffer); + let t2a = Point2i::new(120, 35); + let t2b = Point2i::new(90, 5); + let t2c = Point2i::new(45, 110); + triangle_filled(t2a, t2b, t2c, WHITE, fb); - framebuffer.write_file("diablo.tga", true, true).unwrap(); + let a = Point2i::new(115, 83); + let b = Point2i::new(80, 90); + let c = Point2i::new(85, 120); + triangle_filled(a, b, c, GREEN, fb); +} + +fn _render_diablo(model: Model, fb: &mut TGAImage) { + model.render_wireframe(fb); + fb.write_file("diablo.tga", true, true).unwrap(); } fn _baseline(framebuffer: &mut TGAImage) { @@ -69,10 +85,10 @@ fn _baseline(framebuffer: &mut TGAImage) { let b = Point2i::newu(bx, by); let c = Point2i::newu(cx, cy); - line(a, b, framebuffer, BLUE); - line(c, b, framebuffer, GREEN); - line(c, a, framebuffer, YELLOW); - line(a, c, framebuffer, RED); + line(a, b, BLUE, framebuffer); + line(c, b, GREEN, framebuffer); + line(c, a, YELLOW, framebuffer); + line(a, c, RED, framebuffer); } fn _bench(fb: &mut TGAImage) { @@ -90,13 +106,20 @@ fn _bench(fb: &mut TGAImage) { line( a, b, - fb, [rng.random(), rng.random(), rng.random(), rng.random()].into(), + fb, ); } } -fn line(mut a: Point2i, mut b: Point2i, fb: &mut TGAImage, color: TGAColor) { +fn line(a: Point2i, b: Point2i, color: TGAColor, fb: &mut TGAImage) { + let verts = line_verts(a, b); + for v in verts.into_iter() { + fb.set(v.x as u32, v.y as u32, color); + } +} + +fn line_verts(mut a: Point2i, mut b: Point2i) -> Vec { 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); @@ -107,14 +130,63 @@ fn line(mut a: Point2i, mut b: Point2i, fb: &mut TGAImage, color: TGAColor) { std::mem::swap(&mut a.y, &mut b.y); } - let mut y = a.y as f32; + let mut verts: Vec = Vec::new(); + let step = (b.y - a.y) as f32 / (b.x - a.x) as f32; + let sign = step.signum() as i32; + let mut y = a.y as f32; for x in (a.x)..b.x { - if is_steep { - fb.set(y as u32, x as u32, color); + let p = if is_steep { + Point2i::new(y.round_ties_even() as i32, x) } else { - fb.set(x as u32, y as u32, color); - } + Point2i::new(x, y.round_ties_even() as i32) + }; + verts.push(p); y += step; } + + verts +} + +fn triangle_lines(a: Point2i, b: Point2i, c: Point2i, color: TGAColor, fb: &mut TGAImage) { + line(a, b, color, fb); + line(b, c, color, fb); + line(c, a, color, fb); +} + +fn triangle_filled(a: Point2i, b: Point2i, c: Point2i, color: TGAColor, fb: &mut TGAImage) { + let mut verts = line_verts(a, b); + verts.extend(line_verts(b, c)); + verts.extend(line_verts(c, a)); + + let mut lines: BTreeMap> = BTreeMap::new(); + for vert in verts.into_iter() { + lines + .entry(vert.y) + .and_modify(|e| { + e.push(vert.x); + e.sort_unstable(); + }) + .or_insert(vec![vert.x]); + } + + let mut prev_y: Option = None; + for (y, xs) in lines.iter_mut() { + let len = xs.len(); + match len { + 0 => {} + 1 => fb.set(xs[0] as u32, *y as u32, color), + _ => { + let start = xs[0]; + let end = xs[len - 1]; + line(Point2i::new(start, *y), Point2i::new(end, *y), color, fb); + } + } + if let Some(prev) = prev_y { + if (y - prev).abs() > 1 { + dbg!(y); + } + } + prev_y = Some(*y) + } } diff --git a/src/model.rs b/src/model.rs index 423098c..2bb51ae 100644 --- a/src/model.rs +++ b/src/model.rs @@ -87,9 +87,9 @@ impl Model { 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); + line(a, b, RED, framebuffer); + line(b, c, RED, framebuffer); + line(c, a, RED, framebuffer); } } } diff --git a/src/point.rs b/src/point.rs index 60a8f30..76fe6ac 100644 --- a/src/point.rs +++ b/src/point.rs @@ -10,6 +10,7 @@ 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,