Compare commits

..

22 Commits

Author SHA1 Message Date
2d696932e3 rtiow: add aabb tests and benchmark along with terrible SIMD impl. 2023-01-22 12:03:17 -08:00
27d6c1280b cargo upgrade -p criterion 2023-01-21 16:10:13 -08:00
4506418706 rtiow: remove need for right_child in BVHNode. 2023-01-21 15:59:33 -08:00
1d8aff7905 rtiow: using println and compute ETA in progress. 2023-01-19 21:19:05 -08:00
585ad4805c rtiow: implement triangle renderer that uses BVH internally. 2023-01-19 20:18:51 -08:00
b7f163c5a9 rtiow: minor cleanup. 2023-01-19 20:11:35 -08:00
4ab9425a97 rtiow/vec3: add min/max functions for building new Vec3 from 2 others. 2023-01-19 19:48:59 -08:00
468cba97b3 rtiow: remove unused ray/triangle intersection implementations. 2023-01-18 20:17:05 -08:00
b9ebc186fa rtiow: add new Scale tranformer. 2023-01-18 20:15:06 -08:00
3e9d900f1e Implement Vec3/Vec3 2023-01-18 20:14:49 -08:00
9e81acfda9 Working basic triangle intersection. 2023-01-17 21:32:28 -08:00
f8ec874d13 rtiow: change scene to aid in debugging. 2023-01-15 16:25:31 -08:00
a8756debb8 rtiow: precache some things in Triangles. 2023-01-15 15:29:52 -08:00
a0fb4637b5 rtiow: add ability to render single material triangle mesh. 2023-01-15 15:15:23 -08:00
6069bf9a65 rtiow: don't batch by line, improves parallelism in the long tail. 2023-01-15 12:48:25 -08:00
eeb7813243 rtiow: bump editions to 2021 2023-01-15 11:59:33 -08:00
c644299726 rtiow: update crate use statement in benches. 2023-01-15 11:57:56 -08:00
e6db61543b zigrtiow: commit example test w/ threads. 2023-01-15 11:55:59 -08:00
39eeb79409 rtiow: stub triangles shape created from STLs. 2023-01-15 11:55:11 -08:00
54e72cd81d vec3: helper to create a Vec3 from a single f32. 2023-01-15 11:54:48 -08:00
2d91f781f3 rtiow: remove rustfmt.toml, use systemwide settings. 2023-01-15 11:37:50 -08:00
24e8b4f9cf rtiow: move vec3 to separate crate so it can be used elsewhere. 2023-01-15 11:35:55 -08:00
23 changed files with 1682 additions and 569 deletions

654
rtiow/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,9 +5,8 @@ members = [
"noise_explorer_warp",
"renderer",
"tracer",
"vec3",
]
[profile]
[profile.release]
debug = true

View File

@ -2,19 +2,19 @@
name = "noise_explorer"
version = "0.1.0"
authors = ["Bill Thiede <git@xinu.tv>"]
edition = "2018"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "0.7.8"
askama = "0.7.1"
image = "0.22.3"
log = "0.4.5"
rand = "0.8.3"
actix-web = "0.7.19"
askama = "0.7.2"
image = "0.22.5"
log = "0.4.17"
rand = "0.8.5"
rand_xorshift = "0.3.0"
renderer = { path = "../renderer" }
serde = "1.0.79"
serde_derive = "1.0.79"
stderrlog = "0.4.1"
structopt = "0.2.10"
serde = "1.0.152"
serde_derive = "1.0.152"
stderrlog = "0.4.3"
structopt = "0.2.18"

View File

@ -2,18 +2,18 @@
name = "noise_explorer_warp"
version = "0.1.0"
authors = ["Bill Thiede <git@xinu.tv>"]
edition = "2018"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
askama = "0.7.1"
image = "0.22.3"
log = "0.4.5"
rand = "0.5.5"
askama = "0.7.2"
image = "0.22.5"
log = "0.4.17"
rand = "0.5.6"
renderer = { path = "../renderer" }
serde = "1.0.79"
serde_derive = "1.0.79"
stderrlog = "0.4.1"
structopt = "0.2.10"
warp = "0.1.20"
serde = "1.0.152"
serde_derive = "1.0.152"
stderrlog = "0.4.3"
structopt = "0.2.18"
warp = "0.1.23"

View File

@ -2,29 +2,35 @@
name = "renderer"
version = "0.1.0"
authors = ["Bill Thiede <git@xinu.tv>"]
edition = "2018"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bench]]
harness = false
name = "spheres"
[[bench]]
harness = false
name = "aabb"
[dependencies]
chrono = "*"
core_affinity = "0.5"
cpuprofiler = { version = "0.0.3", optional = true }
image = "0.22.3"
lazy_static = "1.1.0"
log = "0.4.5"
num_cpus = "1.8.0"
rand = "0.8.3"
serde = "1.0.79"
serde_derive = "1.0.79"
serde_json = "1.0.41"
structopt = "0.2.10"
image = "0.22.5"
lazy_static = "1.4.0"
log = "0.4.17"
num_cpus = "1.15.0"
rand = "0.8.5"
serde = "1.0.152"
serde_derive = "1.0.152"
serde_json = "1.0.91"
structopt = "0.2.18"
vec3 = {path = "../vec3"}
stl = {path = "../../../stl"}
#stl = {git = "https://git-private.z.xinu.tv/wathiede/stl"}
[dev-dependencies]
criterion = "0.2"
criterion = "0.4"
[features]
profile = ["cpuprofiler"]

View File

@ -0,0 +1,47 @@
use criterion::*;
use renderer::{aabb::AABB, ray::Ray};
fn bench(c: &mut Criterion) {
let bb = AABB::new([1., -1., -1.], [3., 1., 1.]);
let r_hit = Ray::new([0., 0., 0.], [1., 0., 0.], 0.);
let r_miss = Ray::new([0., 0., 0.], [-1., 0., 0.], 0.);
let t_min = 0.001;
let t_max = f32::MAX;
let mut group = c.benchmark_group("aabb");
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::new("hit_naive", "r_hit"), &r_hit, |b, r| {
b.iter(|| bb.hit_naive(*r, t_min, t_max))
});
group.bench_with_input(BenchmarkId::new("hit2", "r_hit"), &r_hit, |b, r| {
b.iter(|| bb.hit2(*r, t_min, t_max))
});
//group.bench_with_input(BenchmarkId::new("hit_precompute", "r_hit"), &r_hit, |b, r| { b.iter(|| bb.hit_precompute(*r, t_min, t_max)) });
group.bench_with_input(BenchmarkId::new("hit_fast", "r_hit"), &r_hit, |b, r| {
b.iter(|| bb.hit_fast(*r, t_min, t_max))
});
#[cfg(target_arch = "x86_64")]
group.bench_with_input(BenchmarkId::new("hit_simd", "r_hit"), &r_hit, |b, r| {
b.iter(|| bb.hit_simd(*r, t_min, t_max))
});
group.bench_with_input(BenchmarkId::new("hit_naive", "r_miss"), &r_miss, |b, r| {
b.iter(|| bb.hit_naive(*r, t_min, t_max))
});
group.bench_with_input(BenchmarkId::new("hit2", "r_miss"), &r_miss, |b, r| {
b.iter(|| bb.hit2(*r, t_min, t_max))
});
//group.bench_with_input(BenchmarkId::new("hit_precompute", "r_miss"), &r_miss, |b, r| { b.iter(|| bb.hit_precompute(*r, t_min, t_max)) });
group.bench_with_input(BenchmarkId::new("hit_fast", "r_miss"), &r_miss, |b, r| {
b.iter(|| bb.hit_fast(*r, t_min, t_max))
});
#[cfg(target_arch = "x86_64")]
group.bench_with_input(BenchmarkId::new("hit_simd", "r_miss"), &r_miss, |b, r| {
b.iter(|| bb.hit_simd(*r, t_min, t_max))
});
group.finish();
}
criterion_group!(benches, bench);
criterion_main!(benches);

View File

@ -1,9 +1,6 @@
#[macro_use]
extern crate criterion;
use criterion::*;
use criterion::{Criterion, ParameterizedBenchmark};
use rtiow::{
use renderer::{
hitable::Hit, material::Lambertian, ray::Ray, sphere::Sphere, texture::ConstantTexture,
vec3::Vec3,
};
@ -20,14 +17,15 @@ fn criterion_benchmark(c: &mut Criterion) {
// Miss
Ray::new([0., 0., -2.], [0., 0., -1.], 0.),
];
c.bench(
"sphere",
ParameterizedBenchmark::new(
"Sphere",
move |b, r| b.iter(|| sphere.hit(*r, 0., 1.)),
rays,
),
);
let mut group = c.benchmark_group("sphere");
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::new("Sphere", "hit"), &rays[0], |b, r| {
b.iter(|| sphere.hit(*r, 0., 1.))
});
group.bench_with_input(BenchmarkId::new("Sphere", "miss"), &rays[1], |b, r| {
b.iter(|| sphere.hit(*r, 0., 1.))
});
group.finish()
}
criterion_group!(benches, criterion_benchmark);

View File

@ -2,7 +2,7 @@ use std::fmt;
use crate::{ray::Ray, vec3::Vec3};
#[derive(Debug, Clone, PartialEq)]
#[derive(Default, Debug, Copy, Clone, PartialEq)]
pub struct AABB {
bounds: [Vec3; 2],
}
@ -30,7 +30,12 @@ fn max(x: f32, y: f32) -> f32 {
}
impl AABB {
pub fn new(min: Vec3, max: Vec3) -> AABB {
pub fn new<V: Into<Vec3>>(min: V, max: V) -> AABB {
let min: Vec3 = min.into();
let max: Vec3 = max.into();
assert!(min.x < max.x);
assert!(min.y < max.y);
assert!(min.z < max.z);
AABB { bounds: [min, max] }
}
@ -61,10 +66,33 @@ impl AABB {
pub fn min(&self) -> Vec3 {
self.bounds[0]
}
pub fn max(&self) -> Vec3 {
self.bounds[1]
}
pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
self.hit_simd(r, t_min, t_max)
//self.hit_naive(r, t_min, t_max)
}
pub fn hit_naive(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
let mut t_min = t_min;
let mut t_max = t_max;
for axis in 0..3 {
let t0 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
.min((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
let t1 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
.max((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
t_min = t0.max(t_min);
t_max = t1.min(t_max);
if t_max <= t_min {
return false;
}
}
true
}
pub fn hit2(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
let mut t_min = t_min;
let mut t_max = t_max;
@ -119,21 +147,26 @@ impl AABB {
t_min < t1 && t_max > t0
}
pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
let mut t_min = t_min;
let mut t_max = t_max;
for axis in 0..3 {
let t0 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
.min((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
let t1 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
.max((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
t_min = t0.max(t_min);
t_max = t1.min(t_max);
if t_max <= t_min {
return false;
}
pub fn hit_simd(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
#[cfg(target_arch = "x86_64")]
unsafe {
use std::arch::x86_64::*;
let o4 = _mm_set_ps(0., r.origin.z, r.origin.y, r.origin.x);
let d4 = _mm_set_ps(0., r.direction.z, r.direction.y, r.direction.x);
let bmin4 = _mm_set_ps(0., self.min().z, self.min().y, self.min().x);
let bmax4 = _mm_set_ps(0., self.max().z, self.max().y, self.max().x);
let mask4 = _mm_cmpeq_ps(_mm_setzero_ps(), _mm_set_ps(1., 0., 0., 0.));
let t1 = _mm_mul_ps(_mm_sub_ps(_mm_and_ps(bmin4, mask4), o4), d4);
let t2 = _mm_mul_ps(_mm_sub_ps(_mm_and_ps(bmax4, mask4), o4), d4);
let vmax4 = _mm_max_ps(t1, t2);
let vmin4 = _mm_min_ps(t1, t2);
let vmax4: (f32, f32, f32, f32) = std::mem::transmute(vmax4);
let vmin4: (f32, f32, f32, f32) = std::mem::transmute(vmin4);
let tmax = min(vmax4.0, min(vmax4.1, vmax4.2));
let tmin = max(vmin4.0, max(vmin4.1, vmin4.2));
//tmax >= tmin && tmin < r.time && tmax > t_min
t_min <= tmin && tmin <= t_max
}
true
}
pub fn hit_fast(&self, r: Ray, _t_min: f32, _t_max: f32) -> bool {
@ -169,3 +202,48 @@ pub fn surrounding_box(box0: &AABB, box1: &AABB) -> AABB {
);
AABB::new(min, max)
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! hit_test {
($($name:ident,)*) => {
mod hit {
use super::*;
$(
#[test]
fn $name() {
let t_min = 0.001;
let t_max = f32::MAX;
let bb = AABB::new([1., -1., -1.], [3., 1., 1.]);
// Hit
let r = Ray::new([0., 0., 0.], [1., 0., 0.], 0.5);
assert!(bb.$name(r, t_min, t_max));
}
)*
}
mod miss {
use super::*;
$(
#[test]
fn $name() {
let t_min = 0.001;
let t_max = f32::MAX;
let bb = AABB::new([1., -1., -1.], [3., 1., 1.]);
// Miss
let r = Ray::new([0., 0., 0.], [-1., 0., 0.], 0.5);
assert!(!bb.$name(r, t_min, t_max));
}
)*
}
}
}
hit_test! {
hit_naive,
hit2,
hit_fast,
hit_simd,
}
}

View File

@ -0,0 +1,289 @@
/// Implementation based on blog post @
/// https://jacco.ompf2.com/2022/04/13/how-to-build-a-bvh-part-1-basics/
use std::f32::EPSILON;
use stl::STL;
use crate::{
aabb::AABB,
hitable::{Hit, HitRecord},
material::Material,
ray::Ray,
vec3::{cross, dot, Vec3},
};
#[derive(Debug)]
struct BVHNode {
aabb: AABB,
left_child: usize,
first_prim: usize,
prim_count: usize,
}
impl BVHNode {
fn is_leaf(&self) -> bool {
self.prim_count > 0
}
}
#[derive(Debug)]
pub struct Triangle {
centroid: Vec3,
verts: [Vec3; 3],
}
#[derive(Debug)]
pub struct BVHTriangles<M>
where
M: Material,
{
pub triangles: Vec<Triangle>,
material: M,
bvh_nodes: Vec<BVHNode>,
}
const ROOT_NODE_IDX: usize = 0;
impl<M> BVHTriangles<M>
where
M: Material,
{
pub fn new(stl: &STL, material: M) -> BVHTriangles<M> {
let triangles: Vec<_> = stl
.triangles
.iter()
.map(|t| {
let v0 = t.verts[0];
let v1 = t.verts[1];
let v2 = t.verts[2];
let centroid = (v0 + v1 + v2) * 0.3333;
Triangle {
centroid,
verts: [v0, v1, v2],
}
})
.collect();
let n = 2 * triangles.len() - 2;
let bvh_nodes = Vec::with_capacity(n);
let mut bvh = BVHTriangles {
triangles,
bvh_nodes,
material,
};
bvh.build_bvh();
bvh
}
fn build_bvh(&mut self) {
// assign all triangles to root node
let root = BVHNode {
aabb: AABB::default(),
left_child: 0,
first_prim: 0,
prim_count: self.triangles.len() - 1,
};
self.bvh_nodes.push(root);
self.update_node_bounds(ROOT_NODE_IDX);
// subdivide recursively
self.subdivide(ROOT_NODE_IDX);
}
fn update_node_bounds(&mut self, node_idx: usize) {
let node = &mut self.bvh_nodes[node_idx];
let mut aabb_min: Vec3 = f32::MAX.into();
let mut aabb_max: Vec3 = f32::MIN.into();
for i in node.first_prim..node.prim_count {
let leaf_tri = &self.triangles[i];
aabb_min = vec3::min(aabb_min, leaf_tri.verts[0]);
aabb_min = vec3::min(aabb_min, leaf_tri.verts[1]);
aabb_min = vec3::min(aabb_min, leaf_tri.verts[2]);
aabb_max = vec3::max(aabb_max, leaf_tri.verts[0]);
aabb_max = vec3::max(aabb_max, leaf_tri.verts[1]);
aabb_max = vec3::max(aabb_max, leaf_tri.verts[2]);
}
node.aabb = AABB::new(aabb_min, aabb_max);
}
fn subdivide(&mut self, idx: usize) {
// Early out if we're down to just 2 or less triangles
if self.bvh_nodes[idx].prim_count <= 2 {
return;
}
let (first_prim, prim_count, left_count, i) = {
let node = &self.bvh_nodes[idx];
// Compute split plane and position.
let extent = node.aabb.min() - node.aabb.max();
let axis = node.aabb.longest_axis();
let split_pos = node.aabb.min()[axis] + extent[axis] * 0.5;
// Split the group in two halves.
let mut i = node.first_prim as isize;
let mut j = i + node.prim_count as isize - 1;
while i <= j {
if self.triangles[i as usize].centroid[axis] < split_pos {
i += 1;
} else {
self.triangles.swap(i as usize, j as usize);
j -= 1;
}
}
// Create child nodes for each half.
let left_count = i as usize - node.first_prim;
if left_count == 0 || left_count == node.prim_count {
return;
}
(node.first_prim, node.prim_count, left_count, i)
};
// create child nodes
let left_child_idx = self.bvh_nodes.len();
let right_child_idx = left_child_idx + 1;
let left = BVHNode {
aabb: AABB::default(),
left_child: 0,
first_prim: first_prim,
prim_count: left_count,
};
let right = BVHNode {
aabb: AABB::default(),
left_child: 0,
first_prim: i as usize,
prim_count: prim_count - left_count,
};
self.bvh_nodes.push(left);
self.bvh_nodes.push(right);
let node = &mut self.bvh_nodes[idx];
node.left_child = left_child_idx;
node.prim_count = 0;
// Recurse
self.update_node_bounds(left_child_idx);
self.update_node_bounds(right_child_idx);
self.subdivide(left_child_idx);
self.subdivide(right_child_idx);
}
fn intersect_bvh(&self, r: Ray, node_idx: usize, t_min: f32, t_max: f32) -> Option<HitRecord> {
let node = &self.bvh_nodes[node_idx];
if !node.aabb.hit(r, t_min, t_max) {
return None;
}
if node.is_leaf() {
for tri in &self.triangles {
if let Some(RayTriangleResult { t, p }) =
ray_triangle_intersect_moller_trumbore(r, tri)
{
//if let Some(RayTriangleResult { t, p }) = ray_triangle_intersect_geometric(r, tri) {
// We don't support UV (yet?).
let uv = (0.5, 0.5);
let v0 = tri.verts[0];
let v1 = tri.verts[1];
let v2 = tri.verts[2];
let v0v1 = v1 - v0;
let v0v2 = v2 - v0;
let normal = cross(v0v1, v0v2);
return Some(HitRecord {
t,
uv,
p,
normal,
material: &self.material,
});
}
}
} else {
let hr = self.intersect_bvh(r, node.left_child, t_min, t_max);
if hr.is_some() {
return hr;
}
let hr = self.intersect_bvh(r, node.left_child + 1, t_min, t_max);
if hr.is_some() {
return hr;
}
}
None
}
}
impl<M> Hit for BVHTriangles<M>
where
M: Material,
{
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
self.intersect_bvh(r, 0, t_min, t_max)
}
fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
Some(self.bvh_nodes[0].aabb)
}
}
struct RayTriangleResult {
t: f32,
p: Vec3,
}
///
/// Based on https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection.html
fn ray_triangle_intersect_moller_trumbore(r: Ray, tri: &Triangle) -> Option<RayTriangleResult> {
// #ifdef MOLLER_TRUMBORE
// Vec3f v0v1 = v1 - v0;
// Vec3f v0v2 = v2 - v0;
// Vec3f pvec = dir.crossProduct(v0v2);
// float det = v0v1.dotProduct(pvec);
// #ifdef CULLING
// // if the determinant is negative, the triangle is 'back facing'
// // if the determinant is close to 0, the ray misses the triangle
// if (det < kEpsilon) return false;
// #else
// // ray and triangle are parallel if det is close to 0
// if (fabs(det) < kEpsilon) return false;
// #endif
// float invDet = 1 / det;
//
// Vec3f tvec = orig - v0;
// u = tvec.dotProduct(pvec) * invDet;
// if (u < 0 || u > 1) return false;
//
// Vec3f qvec = tvec.crossProduct(v0v1);
// v = dir.dotProduct(qvec) * invDet;
// if (v < 0 || u + v > 1) return false;
//
// t = v0v2.dotProduct(qvec) * invDet;
//
let v0 = tri.verts[0];
let v1 = tri.verts[1];
let v2 = tri.verts[2];
let v0v1 = v1 - v0;
let v0v2 = v2 - v0;
let p = cross(r.direction, v0v2);
let det = dot(v0v1, p);
if det < EPSILON {
return None;
}
let inv_det = 1. / det;
let t = r.origin - v0;
let u = dot(t, p) * inv_det;
if u < 0. || u > 1. {
return None;
}
let q = cross(t, v0v1);
let v = dot(r.direction, q) * inv_det;
if v < 0. || u + v > 1. {
return None;
}
let t = dot(v0v2, q) * inv_det;
if t > EPSILON {
return Some(RayTriangleResult {
t,
p: r.origin + r.direction * t,
});
}
None
}

View File

@ -1,5 +1,6 @@
pub mod aabb;
pub mod bvh;
pub mod bvh_triangles;
pub mod camera;
pub mod constant_medium;
pub mod cuboid;
@ -17,8 +18,10 @@ pub mod ray;
pub mod rect;
pub mod renderer;
pub mod rotate;
pub mod scale;
pub mod scenes;
pub mod sphere;
pub mod texture;
pub mod translate;
pub mod triangles;
pub mod vec3;

View File

@ -7,7 +7,8 @@ use std::{
mpsc::{sync_channel, Receiver, SyncSender},
Arc, Mutex,
},
thread, time,
thread,
time::{self, Instant},
};
use core_affinity;
@ -41,6 +42,7 @@ pub enum Model {
Mandelbrot,
PerlinDebug,
Spheramid,
Stltest,
Test,
Tron,
Tutorial,
@ -58,6 +60,7 @@ impl Model {
Model::Mandelbrot => scenes::mandelbrot::new(opt),
Model::PerlinDebug => scenes::perlin_debug::new(opt),
Model::Spheramid => scenes::spheramid::new(opt),
Model::Stltest => scenes::stltest::new(opt),
Model::Test => scenes::test::new(opt),
Model::Tron => scenes::tron::new(opt),
Model::Tutorial => scenes::tutorial::new(opt),
@ -87,6 +90,7 @@ impl str::FromStr for Model {
"mandelbrot" => Ok(Model::Mandelbrot),
"perlin_debug" => Ok(Model::PerlinDebug),
"spheramid" => Ok(Model::Spheramid),
"stltest" => Ok(Model::Stltest),
"test" => Ok(Model::Test),
"tron" => Ok(Model::Tron),
"tutorial" => Ok(Model::Tutorial),
@ -107,6 +111,7 @@ impl std::string::ToString for Model {
Model::Mandelbrot => "mandelbrot".to_string(),
Model::PerlinDebug => "perlin_debug".to_string(),
Model::Spheramid => "spheramid".to_string(),
Model::Stltest => "stltest".to_string(),
Model::Test => "test".to_string(),
Model::Tron => "tron".to_string(),
Model::Tutorial => "tutorial".to_string(),
@ -394,6 +399,7 @@ impl AddAssign for RenderStats {
}
fn progress(
start_time: Instant,
last_stat: &RenderStats,
current_stat: &RenderStats,
time_diff: time::Duration,
@ -402,13 +408,18 @@ fn progress(
let human = human::Formatter::new();
let pixel_diff = current_stat.pixels - last_stat.pixels;
let ray_diff = current_stat.rays - last_stat.rays;
let now = time::Instant::now();
let start_diff = now - start_time;
let percent = 100. * current_stat.pixels as f32 / pixel_total as f32;
let eta = 100. * start_diff.as_secs_f32() / percent;
format!(
"{:7} / {:7}pixels ({:2}%) {:7}pixels/s {:7}rays/s",
"{:7} / {:7}pixels ({:2.0}%) {:7}pixels/s {:7}rays/s eta {:.0}s",
human.format(current_stat.pixels as f64),
human.format(pixel_total as f64),
100 * current_stat.pixels / pixel_total,
percent,
human.format(pixel_diff as f64 / time_diff.as_secs_f64()),
human.format(ray_diff as f64 / time_diff.as_secs_f64())
human.format(ray_diff as f64 / time_diff.as_secs_f64()),
eta
)
}
@ -576,10 +587,9 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
drop(pixel_req_rx);
drop(pixel_resp_tx);
let start_time = time::Instant::now();
let (w, h) = (scene.width, scene.height);
handles.push(thread::spawn(move || {
let batch_line_requests = true;
let batch_line_requests = false;
if batch_line_requests {
for y in 0..h {
pixel_req_tx
@ -604,6 +614,7 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
let mut last_time = time::Instant::now();
let mut last_stat: RenderStats = Default::default();
let mut current_stat: RenderStats = Default::default();
let start_time = time::Instant::now();
for resp in pixel_resp_rx {
match resp {
Response::Pixel { x, y, pixel, rs } => {
@ -621,9 +632,15 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
let now = time::Instant::now();
let time_diff = now - last_time;
if time_diff > time::Duration::from_secs(1) {
info!(
println!(
"{}",
progress(&last_stat, &current_stat, time_diff, pixel_total)
progress(
start_time,
&last_stat,
&current_stat,
time_diff,
pixel_total
)
);
last_stat = current_stat;
last_time = now;
@ -633,10 +650,16 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
thr.join().expect("thread join");
}
let time_diff = time::Instant::now() - start_time;
info!(
println!(
"Runtime {} seconds {}",
time_diff.as_secs_f32(),
progress(&Default::default(), &current_stat, time_diff, pixel_total)
progress(
start_time,
&Default::default(),
&current_stat,
time_diff,
pixel_total
)
);
output::write_images(&scene, time_diff, output_dir)

View File

@ -0,0 +1,53 @@
use crate::{
aabb::AABB,
hitable::{Hit, HitRecord},
ray::Ray,
vec3::Vec3,
};
#[derive(Debug)]
pub struct Scale<H>
where
H: Hit,
{
hitable: H,
scale: Vec3,
}
impl<H> Scale<H>
where
H: Hit,
{
pub fn new<V>(hitable: H, scale: V) -> Scale<H>
where
V: Into<Vec3>,
{
Scale {
hitable,
scale: scale.into(),
}
}
}
impl<H> Hit for Scale<H>
where
H: Hit,
{
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
let moved_r = Ray::new(r.origin / self.scale, r.direction, r.time);
if let Some(rec) = self.hitable.hit(moved_r, t_min, t_max) {
return Some(HitRecord {
p: rec.p * self.scale,
..rec
});
}
None
}
fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB> {
if let Some(bbox) = self.hitable.bounding_box(t_min, t_max) {
return Some(AABB::new(bbox.min() * self.scale, bbox.max() * self.scale));
}
None
}
}

View File

@ -7,6 +7,7 @@ pub mod final_scene;
pub mod mandelbrot;
pub mod perlin_debug;
pub mod spheramid;
pub mod stltest;
pub mod test;
pub mod tron;
pub mod tutorial;

View File

@ -0,0 +1,126 @@
use std::io::{BufReader, Cursor};
use stl::STL;
use crate::{
bvh_triangles::BVHTriangles,
camera::Camera,
hitable::Hit,
hitable_list::HitableList,
kdtree::KDTree,
material::{DiffuseLight, Lambertian, Metal},
rect::{XYRect, XZRect},
renderer::{Opt, Scene},
scale::Scale,
sphere::Sphere,
texture::ConstantTexture,
vec3::Vec3,
};
pub fn new(opt: &Opt) -> Scene {
let lookfrom = Vec3::new(-20., 100., -100.);
let lookat = Vec3::new(0., 10., 0.);
let dist_to_focus = 10.0;
let aperture = 0.0;
let time_min = 0.;
let time_max = 1.;
let camera = Camera::new(
lookfrom,
lookat,
Vec3::new(0., 1., 0.),
45.,
opt.width as f32 / opt.height as f32,
aperture,
dist_to_focus,
time_min,
time_max,
);
let ground_color = if opt.use_accel {
ConstantTexture::new(Vec3::new(1.0, 0.4, 0.4))
} else {
ConstantTexture::new(Vec3::new(0.4, 0.4, 0.4))
};
let stl_cube = STL::parse(
BufReader::new(Cursor::new(include_bytes!("../../stls/20mm cube.stl"))),
false,
)
.expect("failed to parse cube");
let light_size = 50.;
let light_height = 200.;
let objects: Vec<Box<dyn Hit>> = vec![
// Light from above - white
Box::new(XZRect::new(
-light_size,
light_size,
-light_size,
light_size,
light_height,
DiffuseLight::new(ConstantTexture::new(Vec3::new(15., 15., 15.))),
)),
// Light from back - green
Box::new(XYRect::new(
-light_size,
light_size,
-light_size,
light_size,
-light_height,
DiffuseLight::new(ConstantTexture::new(Vec3::new(1., 15., 1.))),
)),
// Light from front - blue
Box::new(XYRect::new(
-light_size,
light_size,
-light_size,
light_size,
light_height,
DiffuseLight::new(ConstantTexture::new(Vec3::new(1., 1., 15.))),
)),
// Earth sized sphere
Box::new(Sphere::new(
Vec3::new(0., -1200., 0.),
1000.,
Lambertian::new(ground_color),
)),
/*
// Blue sphere
Box::new(Sphere::new(
Vec3::new(-40., 20., 0.),
20.,
Lambertian::new(ConstantTexture::new(Vec3::new(0.1, 0.2, 0.5))),
)),
*/
// Shiny sphere
Box::new(Sphere::new(
Vec3::new(40., 20., -40.),
20.,
Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
)),
Box::new(Sphere::new(
Vec3::new(-40., 20., 40.),
20.,
Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
)),
// STL Mesh
Box::new(Scale::new(
BVHTriangles::new(
&stl_cube,
Lambertian::new(ConstantTexture::new(Vec3::new(0.6, 0.6, 0.6))),
),
200.,
)),
];
let world: Box<dyn Hit> = if opt.use_accel {
Box::new(KDTree::new(objects, time_min, time_max))
} else {
Box::new(HitableList::new(objects))
};
Scene {
camera,
world,
subsamples: opt.subsamples,
num_threads: opt.num_threads,
width: opt.width,
height: opt.height,
..Default::default()
}
}

View File

@ -0,0 +1,168 @@
use std::f32::EPSILON;
use stl::STL;
use crate::{
aabb::AABB,
hitable::{Hit, HitRecord},
material::Material,
ray::Ray,
vec3::{cross, dot, Vec3},
};
#[derive(Debug)]
pub struct Triangle {
normal: Vec3,
verts: [Vec3; 3],
// Precomputed data
}
#[derive(Debug)]
pub struct Triangles<M>
where
M: Material,
{
pub triangles: Vec<Triangle>,
pub bbox: AABB,
material: M,
}
impl<M> Triangles<M>
where
M: Material,
{
pub fn new(stl: &STL, material: M, scale_factor: f32) -> Triangles<M> {
let triangles: Vec<_> = stl
.triangles
.iter()
.map(|t| {
let v0 = t.verts[0] * scale_factor;
let v1 = t.verts[1] * scale_factor;
let v2 = t.verts[2] * scale_factor;
Triangle {
normal: t.normal,
verts: [v0, v1, v2],
}
})
.collect();
let (min, max) = triangles.iter().fold(
(Vec3::from(f32::MAX), Vec3::from(f32::MIN)),
|(min, max), t| {
let t_min_x = t.verts[0].x.min(t.verts[1].x.min(t.verts[2].x));
let t_min_y = t.verts[0].y.min(t.verts[1].y.min(t.verts[2].y));
let t_min_z = t.verts[0].z.min(t.verts[1].z.min(t.verts[2].z));
let t_max_x = t.verts[0].x.max(t.verts[1].x.max(t.verts[2].x));
let t_max_y = t.verts[0].y.max(t.verts[1].y.max(t.verts[2].y));
let t_max_z = t.verts[0].z.max(t.verts[1].z.max(t.verts[2].z));
(
Vec3::from([min.x.min(t_min_x), min.y.min(t_min_y), min.z.min(t_min_z)]),
Vec3::from([max.x.max(t_max_x), max.y.max(t_max_y), max.z.max(t_max_z)]),
)
},
);
let bbox = AABB::new(min, max);
Triangles {
triangles,
bbox,
material,
}
}
}
/// Based on https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection.html
fn ray_triangle_intersect_moller_trumbore(r: Ray, tri: &Triangle) -> Option<RayTriangleResult> {
// #ifdef MOLLER_TRUMBORE
// Vec3f v0v1 = v1 - v0;
// Vec3f v0v2 = v2 - v0;
// Vec3f pvec = dir.crossProduct(v0v2);
// float det = v0v1.dotProduct(pvec);
// #ifdef CULLING
// // if the determinant is negative, the triangle is 'back facing'
// // if the determinant is close to 0, the ray misses the triangle
// if (det < kEpsilon) return false;
// #else
// // ray and triangle are parallel if det is close to 0
// if (fabs(det) < kEpsilon) return false;
// #endif
// float invDet = 1 / det;
//
// Vec3f tvec = orig - v0;
// u = tvec.dotProduct(pvec) * invDet;
// if (u < 0 || u > 1) return false;
//
// Vec3f qvec = tvec.crossProduct(v0v1);
// v = dir.dotProduct(qvec) * invDet;
// if (v < 0 || u + v > 1) return false;
//
// t = v0v2.dotProduct(qvec) * invDet;
//
let v0 = tri.verts[0];
let v1 = tri.verts[1];
let v2 = tri.verts[2];
let v0v1 = v1 - v0;
let v0v2 = v2 - v0;
let p = cross(r.direction, v0v2);
let det = dot(v0v1, p);
if det < EPSILON {
return None;
}
let inv_det = 1. / det;
let t = r.origin - v0;
let u = dot(t, p) * inv_det;
if u < 0. || u > 1. {
return None;
}
let q = cross(t, v0v1);
let v = dot(r.direction, q) * inv_det;
if v < 0. || u + v > 1. {
return None;
}
let t = dot(v0v2, q) * inv_det;
if t > EPSILON {
return Some(RayTriangleResult {
t,
p: r.origin + r.direction * t,
});
}
None
}
impl<M> Hit for Triangles<M>
where
M: Material,
{
fn hit(&self, r: Ray, _t_min: f32, _t_max: f32) -> Option<HitRecord> {
// TODO(wathiede): add an acceleration structure to more cheaply skip some triangles.
for tri in &self.triangles {
if let Some(RayTriangleResult { t, p }) = ray_triangle_intersect_moller_trumbore(r, tri)
{
//if let Some(RayTriangleResult { t, p }) = ray_triangle_intersect_geometric(r, tri) {
// We don't support UV (yet?).
let uv = (0.5, 0.5);
return Some(HitRecord {
t,
uv,
p,
normal: tri.normal,
material: &self.material,
});
}
}
None
}
fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
Some(self.bbox)
}
}
struct RayTriangleResult {
t: f32,
p: Vec3,
}

View File

@ -1,274 +1 @@
use std::{
convert::From,
fmt,
num::ParseFloatError,
ops::{Add, Div, Index, Mul, Neg, Sub},
str,
};
use serde_derive::{Deserialize, Serialize};
#[derive(Default, Debug, Deserialize, Serialize, PartialEq, Copy, Clone)]
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
pub fn cross(v1: Vec3, v2: Vec3) -> Vec3 {
Vec3 {
x: v1.y * v2.z - v1.z * v2.y,
y: v1.z * v2.x - v1.x * v2.z,
z: v1.x * v2.y - v1.y * v2.x,
}
}
pub fn dot(v1: Vec3, v2: Vec3) -> f32 {
v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
}
impl Vec3 {
pub fn new(x: f32, y: f32, z: f32) -> Vec3 {
Vec3 { x, y, z }
}
pub fn min(self) -> f32 {
self.x.min(self.y).min(self.z)
}
pub fn max(self) -> f32 {
self.x.max(self.y).max(self.z)
}
pub fn length(self) -> f32 {
self.squared_length().sqrt()
}
pub fn squared_length(self) -> f32 {
self.x * self.x + self.y * self.y + self.z * self.z
}
pub fn unit_vector(self) -> Vec3 {
self / self.length()
}
pub fn make_unit_vector(&mut self) {
*self = self.unit_vector();
}
}
impl From<[f32; 3]> for Vec3 {
fn from(v: [f32; 3]) -> Self {
Vec3 {
x: v[0],
y: v[1],
z: v[2],
}
}
}
impl fmt::Display for Vec3 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {} {}", self.x, self.y, self.z)
}
}
impl str::FromStr for Vec3 {
type Err = ParseFloatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let coords: Vec<&str> = s.split(' ').collect();
Ok(Vec3 {
x: coords[0].parse::<f32>()?,
y: coords[1].parse::<f32>()?,
z: coords[2].parse::<f32>()?,
})
}
}
impl Add<f32> for Vec3 {
type Output = Vec3;
fn add(self, r: f32) -> Vec3 {
Vec3 {
x: self.x + r,
y: self.y + r,
z: self.z + r,
}
}
}
impl Add for Vec3 {
type Output = Vec3;
fn add(self, r: Vec3) -> Vec3 {
Vec3 {
x: self.x + r.x,
y: self.y + r.y,
z: self.z + r.z,
}
}
}
impl Div<Vec3> for f32 {
type Output = Vec3;
fn div(self, r: Vec3) -> Vec3 {
Vec3 {
x: self / r.x,
y: self / r.y,
z: self / r.z,
}
}
}
impl Div<f32> for Vec3 {
type Output = Vec3;
fn div(self, r: f32) -> Vec3 {
Vec3 {
x: self.x / r,
y: self.y / r,
z: self.z / r,
}
}
}
impl Index<usize> for Vec3 {
type Output = f32;
fn index(&self, idx: usize) -> &f32 {
match idx {
0 => &self.x,
1 => &self.y,
2 => &self.z,
_ => panic!("idx {} out of range for vec3", idx),
}
}
}
impl Mul for Vec3 {
type Output = Vec3;
fn mul(self, r: Vec3) -> Vec3 {
Vec3 {
x: self.x * r.x,
y: self.y * r.y,
z: self.z * r.z,
}
}
}
impl Mul<Vec3> for f32 {
type Output = Vec3;
fn mul(self, v: Vec3) -> Vec3 {
Vec3 {
x: v.x * self,
y: v.y * self,
z: v.z * self,
}
}
}
impl Mul<f32> for Vec3 {
type Output = Vec3;
fn mul(self, r: f32) -> Vec3 {
Vec3 {
x: self.x * r,
y: self.y * r,
z: self.z * r,
}
}
}
impl Neg for Vec3 {
type Output = Vec3;
fn neg(self) -> Vec3 {
-1. * self
}
}
impl Sub for Vec3 {
type Output = Vec3;
fn sub(self, r: Vec3) -> Vec3 {
Vec3 {
x: self.x - r.x,
y: self.y - r.y,
z: self.z - r.z,
}
}
}
#[cfg(test)]
mod tests {
use std::{f32::consts::PI, str::FromStr};
use super::*;
#[test]
fn vec_add() {
let v0 = Vec3::new(1., 2., 3.);
let v1 = Vec3::new(1., 1., 1.);
assert_eq!(v0 + v1, Vec3::new(2., 3., 4.));
}
#[test]
fn vec_div() {
let v0 = Vec3::new(1., 2., 4.);
assert_eq!(v0 / 2., Vec3::new(0.5, 1., 2.));
}
#[test]
fn vec_mul() {
let v0 = Vec3::new(1., 2., 4.);
assert_eq!(v0 * 0.5, Vec3::new(0.5, 1., 2.));
assert_eq!(v0 * v0, Vec3::new(1., 4., 16.));
}
#[test]
fn vec_sub() {
let v0 = Vec3::new(1., 2., 3.);
let v1 = Vec3::new(1., 1., 1.);
assert_eq!(v0 - v1, Vec3::new(0., 1., 2.));
}
#[test]
fn vec_idx() {
let v0 = Vec3::new(1., 2., 3.);
assert_eq!(v0[2], 3.);
}
#[test]
fn vec_display() {
let v0 = Vec3::new(1., 2., 3.);
assert_eq!(format!("{}", v0), "1 2 3".to_owned());
}
#[test]
fn vec_from_str() {
assert_eq!(Vec3::from_str("1. 2. 3.").unwrap(), Vec3::new(1., 2., 3.));
}
#[test]
fn vec_str_roundtrip() {
let v = Vec3::from_str("1 2 3").unwrap();
let s = format!("{}", v);
assert_eq!(v, Vec3::from_str(&s).unwrap());
}
#[test]
fn vec_dot() {
let v0 = Vec3::new(1., 0., 0.);
let v1 = Vec3::new(-1., 0., 0.);
assert_eq!(dot(v0, v1), v0.length() * v1.length() * PI.cos());
}
#[test]
fn vec_cross() {
let v0 = Vec3::new(1., 0., 0.);
let v1 = Vec3::new(0., 1., 0.);
assert_eq!(cross(v0, v1), Vec3::new(0., 0., 1.));
}
}
pub use vec3::*;

Binary file not shown.

View File

@ -1,2 +0,0 @@
imports_granularity = "Crate"
format_code_in_doc_comments = true

View File

@ -2,12 +2,12 @@
name = "tracer"
version = "0.1.0"
authors = ["Bill Thiede <git@xinu.tv>"]
edition = "2018"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4.5"
log = "0.4.17"
renderer = { path = "../renderer" }
stderrlog = "0.4.1"
structopt = "0.2.10"
stderrlog = "0.4.3"
structopt = "0.2.18"

10
rtiow/vec3/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "vec3"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0.152", features = ["serde_derive"] }
serde_derive = "1.0.152"

2
rtiow/vec3/src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
mod vec3;
pub use vec3::*;

309
rtiow/vec3/src/vec3.rs Normal file
View File

@ -0,0 +1,309 @@
use std::{
convert::From,
fmt,
num::ParseFloatError,
ops::{Add, Div, Index, Mul, Neg, Sub},
str,
};
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Deserialize, Serialize, PartialEq, Copy, Clone)]
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
// Return a `Vec3` with the lowest of each component in v1 or v2.
pub fn min(v1: Vec3, v2: Vec3) -> Vec3 {
Vec3 {
x: v1.x.min(v2.x),
y: v1.y.min(v2.y),
z: v1.z.min(v2.z),
}
}
// Return a `Vec3` with the greatest of each component in v1 or v2.
pub fn max(v1: Vec3, v2: Vec3) -> Vec3 {
Vec3 {
x: v1.x.max(v2.x),
y: v1.y.max(v2.y),
z: v1.z.max(v2.z),
}
}
pub fn cross(v1: Vec3, v2: Vec3) -> Vec3 {
Vec3 {
x: v1.y * v2.z - v1.z * v2.y,
y: v1.z * v2.x - v1.x * v2.z,
z: v1.x * v2.y - v1.y * v2.x,
}
}
pub fn dot(v1: Vec3, v2: Vec3) -> f32 {
v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
}
impl Vec3 {
pub fn new(x: f32, y: f32, z: f32) -> Vec3 {
Vec3 { x, y, z }
}
pub fn min(self) -> f32 {
self.x.min(self.y).min(self.z)
}
pub fn max(self) -> f32 {
self.x.max(self.y).max(self.z)
}
pub fn length(self) -> f32 {
self.squared_length().sqrt()
}
pub fn squared_length(self) -> f32 {
self.x * self.x + self.y * self.y + self.z * self.z
}
pub fn unit_vector(self) -> Vec3 {
self / self.length()
}
pub fn make_unit_vector(&mut self) {
*self = self.unit_vector();
}
}
impl From<f32> for Vec3 {
fn from(v: f32) -> Self {
Vec3 { x: v, y: v, z: v }
}
}
impl From<[f32; 3]> for Vec3 {
fn from(v: [f32; 3]) -> Self {
Vec3 {
x: v[0],
y: v[1],
z: v[2],
}
}
}
impl fmt::Display for Vec3 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {} {}", self.x, self.y, self.z)
}
}
impl str::FromStr for Vec3 {
type Err = ParseFloatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let coords: Vec<&str> = s.split(' ').collect();
Ok(Vec3 {
x: coords[0].parse::<f32>()?,
y: coords[1].parse::<f32>()?,
z: coords[2].parse::<f32>()?,
})
}
}
impl Add<f32> for Vec3 {
type Output = Vec3;
fn add(self, r: f32) -> Vec3 {
Vec3 {
x: self.x + r,
y: self.y + r,
z: self.z + r,
}
}
}
impl Add for Vec3 {
type Output = Vec3;
fn add(self, r: Vec3) -> Vec3 {
Vec3 {
x: self.x + r.x,
y: self.y + r.y,
z: self.z + r.z,
}
}
}
impl Div<Vec3> for Vec3 {
type Output = Vec3;
fn div(self, r: Vec3) -> Vec3 {
Vec3 {
x: self.x / r.x,
y: self.y / r.y,
z: self.z / r.z,
}
}
}
impl Div<Vec3> for f32 {
type Output = Vec3;
fn div(self, r: Vec3) -> Vec3 {
Vec3 {
x: self / r.x,
y: self / r.y,
z: self / r.z,
}
}
}
impl Div<f32> for Vec3 {
type Output = Vec3;
fn div(self, r: f32) -> Vec3 {
Vec3 {
x: self.x / r,
y: self.y / r,
z: self.z / r,
}
}
}
impl Index<usize> for Vec3 {
type Output = f32;
fn index(&self, idx: usize) -> &f32 {
match idx {
0 => &self.x,
1 => &self.y,
2 => &self.z,
_ => panic!("idx {} out of range for vec3", idx),
}
}
}
impl Mul for Vec3 {
type Output = Vec3;
fn mul(self, r: Vec3) -> Vec3 {
Vec3 {
x: self.x * r.x,
y: self.y * r.y,
z: self.z * r.z,
}
}
}
impl Mul<Vec3> for f32 {
type Output = Vec3;
fn mul(self, v: Vec3) -> Vec3 {
Vec3 {
x: v.x * self,
y: v.y * self,
z: v.z * self,
}
}
}
impl Mul<f32> for Vec3 {
type Output = Vec3;
fn mul(self, r: f32) -> Vec3 {
Vec3 {
x: self.x * r,
y: self.y * r,
z: self.z * r,
}
}
}
impl Neg for Vec3 {
type Output = Vec3;
fn neg(self) -> Vec3 {
-1. * self
}
}
impl Sub for Vec3 {
type Output = Vec3;
fn sub(self, r: Vec3) -> Vec3 {
Vec3 {
x: self.x - r.x,
y: self.y - r.y,
z: self.z - r.z,
}
}
}
#[cfg(test)]
mod tests {
use std::{f32::consts::PI, str::FromStr};
use super::*;
#[test]
fn vec_add() {
let v0 = Vec3::new(1., 2., 3.);
let v1 = Vec3::new(1., 1., 1.);
assert_eq!(v0 + v1, Vec3::new(2., 3., 4.));
}
#[test]
fn vec_div() {
let v0 = Vec3::new(1., 2., 4.);
assert_eq!(v0 / 2., Vec3::new(0.5, 1., 2.));
}
#[test]
fn vec_mul() {
let v0 = Vec3::new(1., 2., 4.);
assert_eq!(v0 * 0.5, Vec3::new(0.5, 1., 2.));
assert_eq!(v0 * v0, Vec3::new(1., 4., 16.));
}
#[test]
fn vec_sub() {
let v0 = Vec3::new(1., 2., 3.);
let v1 = Vec3::new(1., 1., 1.);
assert_eq!(v0 - v1, Vec3::new(0., 1., 2.));
}
#[test]
fn vec_idx() {
let v0 = Vec3::new(1., 2., 3.);
assert_eq!(v0[2], 3.);
}
#[test]
fn vec_display() {
let v0 = Vec3::new(1., 2., 3.);
assert_eq!(format!("{}", v0), "1 2 3".to_owned());
}
#[test]
fn vec_from_str() {
assert_eq!(Vec3::from_str("1. 2. 3.").unwrap(), Vec3::new(1., 2., 3.));
}
#[test]
fn vec_str_roundtrip() {
let v = Vec3::from_str("1 2 3").unwrap();
let s = format!("{}", v);
assert_eq!(v, Vec3::from_str(&s).unwrap());
}
#[test]
fn vec_dot() {
let v0 = Vec3::new(1., 0., 0.);
let v1 = Vec3::new(-1., 0., 0.);
assert_eq!(dot(v0, v1), v0.length() * v1.length() * PI.cos());
}
#[test]
fn vec_cross() {
let v0 = Vec3::new(1., 0., 0.);
let v1 = Vec3::new(0., 1., 0.);
assert_eq!(cross(v0, v1), Vec3::new(0., 0., 1.));
}
}

56
zigrtiow/test/threads.zig Normal file
View File

@ -0,0 +1,56 @@
const std = @import("std");
const Thread = std.Thread;
const info = std.log.info;
const Queue = std.atomic.Queue;
const Task = struct {
row_idx: usize,
row_pixels: []u8,
};
fn worker(q: *Queue(Task)) void {
while (true) {
if (q.get()) |node| {
const task = node.data;
info("starting thread: {}", .{task.row_idx});
} else {
return;
}
}
}
pub fn main() anyerror!void {
const allocator = std.heap.page_allocator;
const cpus = try Thread.getCpuCount();
const width = 512;
const height = 256;
var pixels: [width * height * 3]u8 = undefined;
var threads: std.ArrayList(std.Thread) = std.ArrayList(std.Thread).init(allocator);
var q = Queue(Task).init();
const Node = Queue(Task).Node;
var row: usize = 0;
while (row < height) : (row += 1) {
const node = try allocator.create(Node);
node.* = .{
.prev = undefined,
.next = undefined,
.data = Task{
.row_idx = row,
.row_pixels = pixels[row * width * 3 .. (row + 1) * width * 3],
},
};
q.put(node);
}
var t: usize = 0;
while (t < cpus) : (t += 1) {
try threads.append(try Thread.spawn(.{}, worker, .{&q}));
}
// Wait for all threads to finish.
for (threads.items) |thread| {
Thread.join(thread);
}
info("main done", .{});
}