Compare commits
4 commits
4c7337a694
...
90420bdc72
Author | SHA1 | Date | |
---|---|---|---|
|
90420bdc72 | ||
|
10ac742f4f | ||
|
88a406f84d | ||
|
59d264a38f |
4 changed files with 184 additions and 41 deletions
52
Cargo.lock
generated
52
Cargo.lock
generated
|
@ -8,6 +8,37 @@ version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-deque"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-epoch"
|
||||||
|
version = "0.9.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
@ -99,6 +130,26 @@ dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"rayon-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon-core"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-deque",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.109"
|
version = "1.0.109"
|
||||||
|
@ -147,6 +198,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"justerror",
|
"justerror",
|
||||||
"rand",
|
"rand",
|
||||||
|
"rayon",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -6,4 +6,5 @@ edition = "2024"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
justerror = "1.1.0"
|
justerror = "1.1.0"
|
||||||
rand = "0.9.2"
|
rand = "0.9.2"
|
||||||
|
rayon = "1.11.0"
|
||||||
thiserror = "2.0.16"
|
thiserror = "2.0.16"
|
||||||
|
|
148
src/main.rs
148
src/main.rs
|
@ -1,10 +1,12 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate justerror;
|
extern crate justerror;
|
||||||
|
|
||||||
mod tga;
|
use std::sync::{Arc, Mutex};
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
|
||||||
|
mod tga;
|
||||||
use tga::*;
|
use tga::*;
|
||||||
|
|
||||||
mod point;
|
mod point;
|
||||||
|
@ -34,41 +36,46 @@ const YELLOW: TGAColor = TGAColor {
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let w = 128;
|
let w = 800;
|
||||||
let h = 128;
|
let h = 800;
|
||||||
let mut fb = TGAImage::new(w, h, TGAFormat::RGB);
|
let mut fb = TGAImage::new(w, h, TGAFormat::RGB);
|
||||||
fill_triangles(&mut fb);
|
// fill_triangles(&mut fb);
|
||||||
fb.write_file("triangles.tga", true, true).unwrap();
|
// fb.write_file("triangles.tga", true, true).unwrap();
|
||||||
|
|
||||||
|
_render_head(&mut fb);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_triangles(fb: &mut TGAImage) {
|
fn fill_triangles(fb: &mut TGAImage) {
|
||||||
/*
|
|
||||||
triangle( 7, 45, 35, 100, 45, 60, framebuffer, red);
|
|
||||||
triangle(120, 35, 90, 5, 45, 110, framebuffer, white);
|
|
||||||
triangle(115, 83, 80, 90, 85, 120, framebuffer, green);
|
|
||||||
*/
|
|
||||||
|
|
||||||
let t1a = Point2i::new(7, 45);
|
let t1a = Point2i::new(7, 45);
|
||||||
let t1b = Point2i::new(35, 100);
|
let t1b = Point2i::new(35, 100);
|
||||||
let t1c = Point2i::new(45, 60);
|
let t1c = Point2i::new(45, 60);
|
||||||
triangle_filled(t1a, t1b, t1c, RED, fb);
|
let t1 = Triangle::new(t1a, t1b, t1c);
|
||||||
|
t1.render_filled(RED, fb);
|
||||||
|
|
||||||
let t2a = Point2i::new(120, 35);
|
let t2a = Point2i::new(120, 35);
|
||||||
let t2b = Point2i::new(90, 5);
|
let t2b = Point2i::new(90, 5);
|
||||||
let t2c = Point2i::new(45, 110);
|
let t2c = Point2i::new(45, 110);
|
||||||
triangle_filled(t2a, t2b, t2c, WHITE, fb);
|
let t2 = Triangle::new(t2a, t2b, t2c);
|
||||||
|
t2.render_filled(WHITE, fb);
|
||||||
|
|
||||||
let a = Point2i::new(115, 83);
|
let a = Point2i::new(115, 83);
|
||||||
let b = Point2i::new(80, 90);
|
let b = Point2i::new(80, 90);
|
||||||
let c = Point2i::new(85, 120);
|
let c = Point2i::new(85, 120);
|
||||||
triangle_filled(a, b, c, GREEN, fb);
|
let t3 = Triangle::new(a, b, c);
|
||||||
|
t3.render_filled(GREEN, fb);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _render_diablo(model: Model, fb: &mut TGAImage) {
|
fn _render_diablo(fb: &mut TGAImage) {
|
||||||
|
let model = Model::from_obj("diablo3_pose.obj");
|
||||||
model.render_wireframe(fb);
|
model.render_wireframe(fb);
|
||||||
fb.write_file("diablo.tga", true, true).unwrap();
|
fb.write_file("diablo.tga", true, true).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn _render_head(fb: &mut TGAImage) {
|
||||||
|
let model = Model::from_obj("head.obj");
|
||||||
|
model.render_triangles(fb);
|
||||||
|
fb.write_file("head.tga", true, true).unwrap();
|
||||||
|
}
|
||||||
fn _baseline(framebuffer: &mut TGAImage) {
|
fn _baseline(framebuffer: &mut TGAImage) {
|
||||||
let ax = 7;
|
let ax = 7;
|
||||||
let ay = 3;
|
let ay = 3;
|
||||||
|
@ -112,7 +119,7 @@ fn _bench(fb: &mut TGAImage) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line(mut a: Point2i, mut b: Point2i, color: TGAColor, fb: &mut TGAImage) {
|
fn line(mut a: Point2i, mut b: Point2i, color: TGAColor, mut fb: &mut TGAImage) {
|
||||||
let is_steep = (a.x - b.x).abs() < (a.y - b.y).abs();
|
let is_steep = (a.x - b.x).abs() < (a.y - b.y).abs();
|
||||||
if is_steep {
|
if is_steep {
|
||||||
std::mem::swap(&mut a.x, &mut a.y);
|
std::mem::swap(&mut a.x, &mut a.y);
|
||||||
|
@ -124,7 +131,7 @@ fn line(mut a: Point2i, mut b: Point2i, color: TGAColor, fb: &mut TGAImage) {
|
||||||
|
|
||||||
let step = (b.y - a.y) as f32 / (b.x - a.x) as f32;
|
let step = (b.y - a.y) as f32 / (b.x - a.x) as f32;
|
||||||
let mut y = a.y as f32;
|
let mut y = a.y as f32;
|
||||||
for x in (a.x)..b.x {
|
for x in a.x..b.x {
|
||||||
let (px, py) = if is_steep {
|
let (px, py) = if is_steep {
|
||||||
(y.round_ties_even() as i32, x)
|
(y.round_ties_even() as i32, x)
|
||||||
} else {
|
} else {
|
||||||
|
@ -135,31 +142,92 @@ fn line(mut a: Point2i, mut b: Point2i, color: TGAColor, fb: &mut TGAImage) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn triangle_lines(a: Point2i, b: Point2i, c: Point2i, color: TGAColor, fb: &mut TGAImage) {
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
line(a, b, color, fb);
|
struct Triangle {
|
||||||
line(b, c, color, fb);
|
a: Point2i,
|
||||||
line(c, a, color, fb);
|
b: Point2i,
|
||||||
|
c: Point2i,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn triangle_filled(a: Point2i, b: Point2i, c: Point2i, color: TGAColor, fb: &mut TGAImage) {
|
impl Triangle {
|
||||||
let mut sorted = [a, b, c];
|
pub fn new(a: Point2i, b: Point2i, c: Point2i) -> Self {
|
||||||
sorted.sort_unstable_by(|a, b| a.y.cmp(&b.y));
|
Self { a, b, c }
|
||||||
let [a, b, c] = sorted;
|
|
||||||
|
|
||||||
let total_height = c.y - a.y;
|
|
||||||
|
|
||||||
let segment_height = b.y - a.y;
|
|
||||||
for y in a.y..=b.y {
|
|
||||||
let dy = y - a.y;
|
|
||||||
let x1 = a.x + ((c.x - a.x) * dy) / total_height;
|
|
||||||
let x2 = a.x + ((b.x - a.x) * dy) / segment_height;
|
|
||||||
line(Point2i::new(x1, y), Point2i::new(x2, y), color, fb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let segment_height = c.y - b.y;
|
pub fn signed_area(&self) -> f32 {
|
||||||
for y in b.y..=c.y {
|
0.5 * ((self.b.y - self.a.y) * (self.a.x + self.b.x)
|
||||||
let x1 = a.x + ((c.x - a.x) * (y - a.y)) / total_height;
|
+ (self.c.y - self.b.y) * (self.c.x + self.b.x)
|
||||||
let x2 = b.x + ((c.x - b.x) * (y - b.y)) / segment_height;
|
+ (self.a.y - self.c.y) * (self.a.x + self.c.x)) as f32
|
||||||
line(Point2i::new(x1, y), Point2i::new(x2, y), color, fb);
|
}
|
||||||
|
|
||||||
|
pub fn bb(&self) -> AABB {
|
||||||
|
let xmax = self.a.x.max(self.b.x.max(self.c.x));
|
||||||
|
let xmin = self.a.x.min(self.b.x.min(self.c.x));
|
||||||
|
|
||||||
|
let ymax = self.a.y.max(self.b.y.max(self.c.y));
|
||||||
|
let ymin = self.a.y.min(self.b.y.min(self.c.y));
|
||||||
|
AABB {
|
||||||
|
lower_left: Point2i::new(xmin, ymin),
|
||||||
|
upper_right: Point2i::new(xmax, ymax),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_filled(&self, color: TGAColor, fb: &mut TGAImage) {
|
||||||
|
let fb = Arc::new(Mutex::new(fb));
|
||||||
|
let bb = self.bb();
|
||||||
|
let total_area_sign = self.signed_area().signum();
|
||||||
|
(bb.ymin()..=bb.ymax()).into_par_iter().for_each(|y| {
|
||||||
|
(bb.xmin()..bb.xmax()).into_par_iter().for_each(|x| {
|
||||||
|
let p = Point2i::new(x, y);
|
||||||
|
let a = Triangle::new(p, self.a, self.b).signed_area().signum() * total_area_sign;
|
||||||
|
let b = Triangle::new(p, self.b, self.c).signed_area().signum() * total_area_sign;
|
||||||
|
let c = Triangle::new(p, self.c, self.a).signed_area().signum() * total_area_sign;
|
||||||
|
|
||||||
|
if a.is_sign_positive()
|
||||||
|
&& b.is_sign_positive()
|
||||||
|
&& c.is_sign_positive()
|
||||||
|
&& 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct AABB {
|
||||||
|
lower_left: Point2i,
|
||||||
|
upper_right: Point2i,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AABB {
|
||||||
|
pub fn new(lower_left: Point2i, upper_right: Point2i) -> Self {
|
||||||
|
Self {
|
||||||
|
lower_left,
|
||||||
|
upper_right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn xmin(&self) -> i32 {
|
||||||
|
self.lower_left.x
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn xmax(&self) -> i32 {
|
||||||
|
self.upper_right.x
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ymin(&self) -> i32 {
|
||||||
|
self.lower_left.y
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ymax(&self) -> i32 {
|
||||||
|
self.upper_right.y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
24
src/model.rs
24
src/model.rs
|
@ -1,7 +1,9 @@
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
RED, line,
|
RED, Triangle, line,
|
||||||
point::{Point2i, Point3f},
|
point::{Point2i, Point3f},
|
||||||
tga::TGAImage,
|
tga::TGAImage,
|
||||||
};
|
};
|
||||||
|
@ -92,6 +94,26 @@ impl Model {
|
||||||
line(c, a, RED, framebuffer);
|
line(c, a, RED, framebuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn render_triangles(&self, framebuffer: &mut TGAImage) {
|
||||||
|
let width = framebuffer.width;
|
||||||
|
let height = framebuffer.height;
|
||||||
|
|
||||||
|
let mut rng = rand::rng();
|
||||||
|
for face in self.faces.iter() {
|
||||||
|
let a = world2view(self.verts[face[0]], width, height);
|
||||||
|
let b = world2view(self.verts[face[1]], width, height);
|
||||||
|
let c = world2view(self.verts[face[2]], width, height);
|
||||||
|
let triangle = Triangle::new(a, b, c);
|
||||||
|
let color = [
|
||||||
|
rng.random_range(0..=255u8),
|
||||||
|
rng.random_range(0..=255u8),
|
||||||
|
rng.random_range(0..=255u8),
|
||||||
|
255,
|
||||||
|
]
|
||||||
|
.into();
|
||||||
|
triangle.render_filled(color, framebuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn world2view(point: Point3f, width: u32, height: u32) -> Point2i {
|
fn world2view(point: Point3f, width: u32, height: u32) -> Point2i {
|
||||||
|
|
Loading…
Reference in a new issue