From 20325e6a75b0824237b1f55c894501d16f1de247 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Mon, 8 Sep 2025 17:17:15 -0700 Subject: [PATCH] renders a 'wireframe' triangle --- src/main.rs | 89 +++++++++++++++++++++++++++++++++++-------- src/tga.rs | 106 +++++++++++++++++++++++----------------------------- 2 files changed, 119 insertions(+), 76 deletions(-) diff --git a/src/main.rs b/src/main.rs index 538867d..3ca3806 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,13 +46,10 @@ fn main() { let b = Point2i::new(550, 390).with_color(GREEN); let c = Point2i::new(230, 590).with_color(RED); - a.render(20, &mut fb); - b.render(20, &mut fb); - c.render(20, &mut fb); - let t = Triangle2i::new(a, b, c); - t.render_filled(BLACK, &mut fb); - fb.write_file("triangle.tga", true, true).unwrap(); + t.render_lines(20, BLACK, &mut fb); + + fb.write_file("triangle.tga", true).unwrap(); } fn line(mut a: Point2i, mut b: Point2i, color: TGAColor, mut fb: &mut TGAImage) { @@ -123,11 +120,7 @@ impl Triangle2i { let beta = Triangle2i::new(p, self.c, self.a).signed_area() * it; let gamma = Triangle2i::new(p, self.a, self.b).signed_area() * it; - if alpha.is_sign_positive() - && beta.is_sign_positive() - && gamma.is_sign_positive() - && let Ok(mut fb) = fb.lock() - { + 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 @@ -136,16 +129,80 @@ impl Triangle2i { } else { color }; - fb.set(x as u32, y as u32, color); + + if let Ok(mut fb) = fb.lock() { + fb.set(x as u32, y as u32, color); + } + }; + }); + }); + } + + pub fn render_lines(&self, thickness: i32, color: TGAColor, fb: &mut TGAImage) { + let fb = Arc::new(Mutex::new(fb)); + 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, self.c).signed_area() * it; + let beta = Triangle2i::new(p, self.c, self.a).signed_area() * it; + let gamma = Triangle2i::new(p, self.a, self.b).signed_area() * it; + + if alpha.is_sign_positive() && beta.is_sign_positive() && gamma.is_sign_positive() { + let ad = ((self.b.y - self.a.y) * x - (self.b.x - self.a.x) * y + + self.b.x * self.a.y + - self.b.y * self.a.x) + .abs() + / ((self.b.y - self.a.y).pow(2) + (self.b.x - self.a.x).pow(2)).isqrt(); + let bd = ((self.c.y - self.b.y) * x - (self.c.x - self.b.x) * y + + self.c.x * self.b.y + - self.c.y * self.b.x) + .abs() + / ((self.c.y - self.b.y).pow(2) + (self.c.x - self.b.x).pow(2)).isqrt(); + let cd = ((self.a.y - self.c.y) * x - (self.a.x - self.c.x) * y + + self.a.x * self.c.y + - self.a.y * self.c.x) + .abs() + / ((self.a.y - self.c.y).pow(2) + (self.a.x - self.c.x).pow(2)).isqrt(); + + if ad <= thickness || bd <= thickness || cd <= thickness { + 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 + }; + + if let Ok(mut fb) = fb.lock() { + fb.set(x as u32, y as u32, color); + } + } } }); }); } - pub fn render_lines(&self, color: TGAColor, fb: &mut TGAImage) { - line(self.a, self.b, color, fb); - line(self.b, self.c, color, fb); - line(self.c, self.a, color, fb); + pub fn centroid(&self) -> Point2i { + 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 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 + }; + Point2i { x, y, color } } } diff --git a/src/tga.rs b/src/tga.rs index 2a28a7e..ae5c5d1 100644 --- a/src/tga.rs +++ b/src/tga.rs @@ -222,7 +222,7 @@ impl TGAImage { Ok(()) } - pub fn write_file(&mut self, file: &str, do_vflip: bool, do_rle: bool) -> IoResult { + pub fn write_file(&mut self, file: &str, do_vflip: bool) -> IoResult { let mut file = std::fs::File::create(file)?; let mut header = TGAHeader::default(); @@ -231,10 +231,8 @@ impl TGAImage { header.height = self.height as u16; header.datatype_code = match self.format { - TGAFormat::Grayscale if do_rle => 11, - TGAFormat::Grayscale => 3, - _ if do_rle => 10, - _ => 2, + TGAFormat::Grayscale => 11, + _ => 10, }; header.image_descriptor = if do_vflip { 0x00 } else { 0x20 }; @@ -243,10 +241,49 @@ impl TGAImage { file.write_all(&mut header)?; - if do_rle { - self.write_rle_file(&mut file)?; - } else { - file.write_all(&mut self.data)?; + let max_chunk_len = 128; + let num_pixels = (self.width * self.height) as usize; + let mut current_pixel = 0; + + let bpp = self.bytes_per_pixel() as usize; + + while current_pixel < num_pixels { + let chunk_start = current_pixel * bpp; + let mut current_byte = current_pixel * bpp; + + let mut run_length = 1; + let mut is_raw = true; + + while (current_pixel + run_length) < num_pixels && run_length < max_chunk_len { + let mut succ_eq = true; + let mut t = 0; + while succ_eq && t < bpp { + succ_eq = self.data[current_byte + t] == self.data[current_byte + t + bpp]; + t += 1; + } + current_byte += bpp; + if 1 == run_length { + is_raw = !succ_eq; + } + if is_raw && succ_eq { + run_length -= 1; + break; + } + if !(is_raw || succ_eq) { + break; + } + run_length += 1; + } + current_pixel += run_length; + let mut out = if is_raw { + [run_length as u8 - 1] + } else { + [run_length as u8 + 127] + }; + + file.write_all(&mut out)?; + let chunk_end = chunk_start + if is_raw { run_length * bpp } else { bpp }; + file.write_all(&mut self.data[chunk_start..chunk_end])?; } file.flush()?; @@ -352,55 +389,4 @@ impl TGAImage { Ok(()) } - - fn write_rle_file(&mut self, file: &mut File) -> IoResult { - let max_chunk_len = 128; - let num_pixels = (self.width * self.height) as usize; - let mut current_pixel = 0; - - let bpp = self.bytes_per_pixel() as usize; - - while current_pixel < num_pixels { - let chunk_start = current_pixel * bpp; - let mut current_byte = current_pixel * bpp; - - let mut run_length = 1; - let mut is_raw = true; - - while (current_pixel + run_length) < num_pixels && run_length < max_chunk_len { - let mut succ_eq = true; - let mut t = 0; - while succ_eq && t < bpp { - succ_eq = self.data[current_byte + t] == self.data[current_byte + t + bpp]; - t += 1; - } - current_byte += bpp; - if 1 == run_length { - is_raw = !succ_eq; - } - if is_raw && succ_eq { - run_length -= 1; - break; - } - if !(is_raw || succ_eq) { - break; - } - run_length += 1; - } - current_pixel += run_length; - let mut out = if is_raw { - [run_length as u8 - 1] - } else { - [run_length as u8 + 127] - }; - - file.write_all(&mut out)?; - let chunk_end = chunk_start + if is_raw { run_length * bpp } else { bpp }; - file.write_all(&mut self.data[chunk_start..chunk_end])?; - } - - file.flush()?; - - Ok(()) - } }