first commit

This commit is contained in:
Yehowshua Immanuel 2025-04-29 09:15:10 -04:00
commit 145d5f3d5f
5 changed files with 383 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

63
Cargo.lock generated Normal file
View file

@ -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"

7
Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[package]
name = "rust_tiny_rast"
version = "0.1.0"
edition = "2024"
[dependencies]
sdl2 = "0.35"

5
README.md Normal file
View file

@ -0,0 +1,5 @@
# Some Rasterization Experiments in Rust
```
cargo run
```

307
src/main.rs Normal file
View file

@ -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<Vec<Pixel>>,
}
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<Window>, 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(())
}