From 145d5f3d5fafbdbfb0d25801eb24eef818f93dfc Mon Sep 17 00:00:00 2001 From: Yehowshua Immanuel Date: Tue, 29 Apr 2025 09:15:10 -0400 Subject: [PATCH] first commit --- .gitignore | 1 + Cargo.lock | 63 +++++++++++ Cargo.toml | 7 ++ README.md | 5 + src/main.rs | 307 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 383 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7eaf3a0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,63 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "rust_tiny_rast" +version = "0.1.0" +dependencies = [ + "sdl2", +] + +[[package]] +name = "sdl2" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a" +dependencies = [ + "bitflags", + "lazy_static", + "libc", + "sdl2-sys", +] + +[[package]] +name = "sdl2-sys" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0" +dependencies = [ + "cfg-if", + "libc", + "version-compare", +] + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2f8bdba --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "rust_tiny_rast" +version = "0.1.0" +edition = "2024" + +[dependencies] +sdl2 = "0.35" diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f728a0 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Some Rasterization Experiments in Rust + +``` +cargo run +``` \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d75b56a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,307 @@ +use sdl2::pixels::Color; +use sdl2::event::Event; +use sdl2::rect::Point; +use sdl2::render::Canvas; +use sdl2::video::Window; +use std::cmp::{min, max}; + +// 2D integer vector +#[derive(Clone, Copy)] +struct Vector2i { + x: i32, + y: i32, +} + +// 3D integer vector (for cross product in point_in_triangle) +#[derive(Clone, Copy)] +struct Vector3i { + x: i32, + y: i32, + z: i32, +} + +// 2D triangle in screen space +#[derive(Clone, Copy)] +struct Triangle { + a: Vector2i, + b: Vector2i, + c: Vector2i, +} + +// RGB pixel +#[derive(Clone, Copy)] +struct Pixel { + r: u8, + g: u8, + b: u8, +} + +impl Pixel { + fn to_sdl_color(&self) -> Color { + Color::RGB(self.r, self.g, self.b) + } +} + +// Framebuffer +struct FrameBuffer { + width: usize, + height: usize, + pixels: Vec>, +} + +impl FrameBuffer { + fn new(width: usize, height: usize) -> Self { + let pixels = vec![vec![Pixel { r: 0, g: 0, b: 0 }; width]; height]; + FrameBuffer { width, height, pixels } + } + + fn set_pixel(&mut self, x: i32, y: i32, pixel: Pixel) { + if x >= 0 && x < self.width as i32 && y >= 0 && y < self.height as i32 { + self.pixels[y as usize][x as usize] = pixel; + } + } +} + +// Vector operations +fn sub_vec(a: Vector2i, b: Vector2i) -> Vector2i { + Vector2i { x: a.x - b.x, y: a.y - b.y } +} + +// Cross product for Vector3i (translated from Nim's core.nim) +fn cross(a: Vector3i, b: Vector3i) -> Vector3i { + let a1 = a.x; + let a2 = a.y; + let a3 = a.z; + let b1 = b.x; + let b2 = b.y; + let b3 = b.z; + Vector3i { + x: a2 * b3 - a3 * b2, + y: -(a1 * b3 - a3 * b1), + z: a1 * b2 - a2 * b1, + } +} + +// Point-in-triangle test (verbatim translation from Nim's pointInTriangle) +fn point_in_triangle(triangle: Triangle, p: Vector2i) -> bool { + let mut A = triangle.a; + let mut B = triangle.b; + let mut C = triangle.c; + + let vAC = sub_vec(C, A); + let vAB = sub_vec(B, A); + let vPA = sub_vec(A, p); + + let cross_product = cross( + Vector3i { x: vAB.x, y: vAC.x, z: vPA.x }, + Vector3i { x: vAB.y, y: vAC.y, z: vPA.y }, + ); + + let x = cross_product.x; + let y = cross_product.y; + let z = cross_product.z; + + // degenerate triangle, AKA, line or point + if z == 0 { + return false; + } + + // non-degenerate cases + if z > 0 { + if (x + y - z) > 0 { + return false; + } + if y < 0 { + return false; + } + if x < 0 { + return false; + } + } + if z < 0 { + if (x + y - z) < 0 { + return false; + } + if y > 0 { + return false; + } + if x > 0 { + return false; + } + } + return true; +} + +// Get triangle bounding box +fn triangle_bounds(triangle: Triangle) -> (i32, i32, i32, i32) { + let x_coords = [triangle.a.x, triangle.b.x, triangle.c.x]; + let y_coords = [triangle.a.y, triangle.b.y, triangle.c.y]; + ( + x_coords.iter().min().unwrap().to_owned(), + x_coords.iter().max().unwrap().to_owned(), + y_coords.iter().min().unwrap().to_owned(), + y_coords.iter().max().unwrap().to_owned(), + ) +} + +// Bresenham's line algorithm (low slope) +fn line_low(fb: &mut FrameBuffer, p1: Vector2i, p2: Vector2i, color: Pixel) { + let mut dx = p2.x - p1.x; + let mut dy = p2.y - p1.y; + let mut yi = 1; + if dy < 0 { + yi = -1; + dy = -dy; + } + let mut d = 2 * dy - dx; + let mut y = p1.y; + + for x in p1.x..=p2.x { + fb.set_pixel(x, y, color); + if d > 0 { + y += yi; + d += 2 * (dy - dx); + } else { + d += 2 * dy; + } + } +} + +// Bresenham's line algorithm (high slope) +fn line_high(fb: &mut FrameBuffer, p1: Vector2i, p2: Vector2i, color: Pixel) { + let mut dx = p2.x - p1.x; + let mut dy = p2.y - p1.y; + let mut xi = 1; + if dx < 0 { + xi = -1; + dx = -dx; + } + let mut d = 2 * dx - dy; + let mut x = p1.x; + + for y in p1.y..=p2.y { + fb.set_pixel(x, y, color); + if d > 0 { + x += xi; + d += 2 * (dx - dy); + } else { + d += 2 * dx; + } + } +} + +// Draw line between two points +fn draw_line(fb: &mut FrameBuffer, p1: Vector2i, p2: Vector2i, color: Pixel) { + if (p2.y - p1.y).abs() < (p2.x - p1.x).abs() { + if p1.x > p2.x { + line_low(fb, p2, p1, color); + } else { + line_low(fb, p1, p2, color); + } + } else { + if p1.y > p2.y { + line_high(fb, p2, p1, color); + } else { + line_high(fb, p1, p2, color); + } + } +} + +// Draw wireframe triangle +fn draw_triangle_wire(fb: &mut FrameBuffer, triangle: Triangle, color: Pixel) { + draw_line(fb, triangle.a, triangle.b, color); + draw_line(fb, triangle.b, triangle.c, color); + draw_line(fb, triangle.c, triangle.a, color); +} + +// Draw filled triangle +fn draw_triangle_fill(fb: &mut FrameBuffer, triangle: Triangle, color: Pixel) { + let (min_x, max_x, min_y, max_y) = triangle_bounds(triangle); + for y in min_y..=max_y { + for x in min_x..=max_x { + let point = Vector2i { x, y }; + if point_in_triangle(triangle, point) { + fb.set_pixel(x, y, color); + } + } + } +} + +// Render framebuffer to SDL canvas +fn render_to_canvas(canvas: &mut Canvas, fb: &FrameBuffer) { + for y in 0..fb.height { + for x in 0..fb.width { + let pixel = fb.pixels[y][x]; + canvas.set_draw_color(pixel.to_sdl_color()); + canvas.draw_point(Point::new(x as i32, y as i32)).unwrap(); + } + } + canvas.present(); +} + +fn main() -> Result<(), String> { + // Initialize SDL2 + let sdl_context = sdl2::init()?; + let video_subsystem = sdl_context.video()?; + + const WIDTH: u32 = 800; + const HEIGHT: u32 = 600; + + let window = video_subsystem + .window("Rust Rasterizer", WIDTH, HEIGHT) + .position_centered() + .build() + .map_err(|e| e.to_string())?; + + let mut canvas = window + .into_canvas() + .accelerated() + .present_vsync() + .build() + .map_err(|e| e.to_string())?; + + canvas.set_draw_color(Color::RGB(0, 0, 0)); + canvas.clear(); + + // Create framebuffer + let mut fb = FrameBuffer::new(WIDTH as usize, HEIGHT as usize); + + // Example triangles + let triangles = vec![ + Triangle { + a: Vector2i { x: 100, y: 100 }, + b: Vector2i { x: 200, y: 300 }, + c: Vector2i { x: 300, y: 150 }, + }, + Triangle { + a: Vector2i { x: 400, y: 200 }, + b: Vector2i { x: 500, y: 400 }, + c: Vector2i { x: 600, y: 250 }, + }, + ]; + + let red = Pixel { r: 255, g: 0, b: 0 }; + let blue = Pixel { r: 0, g: 0, b: 255 }; + + // Rasterize triangles + for (i, triangle) in triangles.iter().enumerate() { + let color = if i % 2 == 0 { red } else { blue }; + draw_triangle_fill(&mut fb, *triangle, color); + draw_triangle_wire(&mut fb, *triangle, Pixel { r: 255, g: 255, b: 255 }); // White outline + } + + // Main loop + let mut event_pump = sdl_context.event_pump()?; + 'running: loop { + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } => break 'running, + _ => {} + } + } + + render_to_canvas(&mut canvas, &fb); + } + + Ok(()) +} \ No newline at end of file