there's a missing scanline in the white triangle

This commit is contained in:
Joe Ardent 2025-09-07 11:07:19 -07:00
parent 6d335f43ea
commit 9047b0196b
3 changed files with 102 additions and 29 deletions

View file

@ -2,6 +2,8 @@
extern crate justerror; extern crate justerror;
mod tga; mod tga;
use std::collections::BTreeMap;
use rand::Rng; use rand::Rng;
use tga::*; use tga::*;
@ -32,25 +34,39 @@ const YELLOW: TGAColor = TGAColor {
}; };
fn main() { fn main() {
let w = 800; let w = 128;
let h = 800; let h = 128;
let mut framebuffer = TGAImage::new(w, h, TGAFormat::RGB); let mut fb = TGAImage::new(w, h, TGAFormat::RGB);
let model = Model::from_obj("diablo3_pose.obj"); 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;
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); 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);
*/
framebuffer.write_file("diablo.tga", true, true).unwrap(); 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);
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);
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) { fn _baseline(framebuffer: &mut TGAImage) {
@ -69,10 +85,10 @@ fn _baseline(framebuffer: &mut TGAImage) {
let b = Point2i::newu(bx, by); let b = Point2i::newu(bx, by);
let c = Point2i::newu(cx, cy); let c = Point2i::newu(cx, cy);
line(a, b, framebuffer, BLUE); line(a, b, BLUE, framebuffer);
line(c, b, framebuffer, GREEN); line(c, b, GREEN, framebuffer);
line(c, a, framebuffer, YELLOW); line(c, a, YELLOW, framebuffer);
line(a, c, framebuffer, RED); line(a, c, RED, framebuffer);
} }
fn _bench(fb: &mut TGAImage) { fn _bench(fb: &mut TGAImage) {
@ -90,13 +106,20 @@ fn _bench(fb: &mut TGAImage) {
line( line(
a, a,
b, b,
fb,
[rng.random(), rng.random(), rng.random(), rng.random()].into(), [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<Point2i> {
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);
@ -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); std::mem::swap(&mut a.y, &mut b.y);
} }
let mut y = a.y as f32; let mut verts: Vec<Point2i> = Vec::new();
let step = (b.y - a.y) as f32 / (b.x - a.x) as f32; 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 { for x in (a.x)..b.x {
if is_steep { let p = if is_steep {
fb.set(y as u32, x as u32, color); Point2i::new(y.round_ties_even() as i32, x)
} else { } else {
fb.set(x as u32, y as u32, color); Point2i::new(x, y.round_ties_even() as i32)
} };
verts.push(p);
y += step; 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<i32, Vec<i32>> = 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<i32> = 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)
}
} }

View file

@ -87,9 +87,9 @@ impl Model {
let a = world2view(self.verts[face[0]], width, height); let a = world2view(self.verts[face[0]], width, height);
let b = world2view(self.verts[face[1]], width, height); let b = world2view(self.verts[face[1]], width, height);
let c = world2view(self.verts[face[2]], width, height); let c = world2view(self.verts[face[2]], width, height);
line(a, b, framebuffer, RED); line(a, b, RED, framebuffer);
line(b, c, framebuffer, RED); line(b, c, RED, framebuffer);
line(c, a, framebuffer, RED); line(c, a, RED, framebuffer);
} }
} }
} }

View file

@ -10,6 +10,7 @@ impl Point2i {
pub fn new(x: i32, y: i32) -> Self { pub fn new(x: i32, y: i32) -> Self {
Self { x, y } Self { x, y }
} }
pub fn newu(x: u32, y: u32) -> Self { pub fn newu(x: u32, y: u32) -> Self {
Self { Self {
x: x as i32, x: x as i32,