first commit
This commit is contained in:
commit
145d5f3d5f
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
63
Cargo.lock
generated
Normal file
63
Cargo.lock
generated
Normal 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
7
Cargo.toml
Normal 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
5
README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Some Rasterization Experiments in Rust
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run
|
||||||
|
```
|
307
src/main.rs
Normal file
307
src/main.rs
Normal 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(())
|
||||||
|
}
|
Loading…
Reference in a new issue