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