Compare commits
15 Commits
3c28466d68
...
1076e6dcaf
| Author | SHA1 | Date | |
|---|---|---|---|
| 1076e6dcaf | |||
| 7d9750b9d0 | |||
| 88b8c547e0 | |||
| ac555beafc | |||
| 0158f9ea15 | |||
| 450342c3d4 | |||
| 41f9fa2742 | |||
| 7ec30d8557 | |||
| f51d3396f4 | |||
| a2f9166b5a | |||
| f73a471cb6 | |||
| cd149755cb | |||
| 9188ce17fa | |||
| 63975bad96 | |||
| df928e1779 |
81
rtiow/Cargo.lock
generated
81
rtiow/Cargo.lock
generated
@ -357,6 +357,21 @@ version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@ -1550,6 +1565,12 @@ version = "0.2.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.8"
|
||||
@ -1758,7 +1779,7 @@ dependencies = [
|
||||
"log 0.4.17",
|
||||
"mime 0.2.6",
|
||||
"mime_guess 1.8.8",
|
||||
"quick-error",
|
||||
"quick-error 1.2.3",
|
||||
"rand 0.6.5",
|
||||
"safemem",
|
||||
"tempfile",
|
||||
@ -1869,6 +1890,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2155,12 +2177,39 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proptest"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29f1b898011ce9595050a68e60f90bad083ff2987a695a42357134c8381fba70"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"lazy_static 1.4.0",
|
||||
"num-traits",
|
||||
"quick-error 2.0.1",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_xorshift 0.3.0",
|
||||
"regex-syntax",
|
||||
"rusty-fork",
|
||||
"tempfile",
|
||||
"unarray",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.6.13"
|
||||
@ -2472,6 +2521,7 @@ dependencies = [
|
||||
"log 0.4.17",
|
||||
"num_cpus",
|
||||
"pretty_assertions",
|
||||
"proptest",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@ -2490,7 +2540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a"
|
||||
dependencies = [
|
||||
"hostname",
|
||||
"quick-error",
|
||||
"quick-error 1.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2514,6 +2564,18 @@ version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
|
||||
|
||||
[[package]]
|
||||
name = "rusty-fork"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"quick-error 1.2.3",
|
||||
"tempfile",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.12"
|
||||
@ -3347,6 +3409,12 @@ version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "unarray"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "1.4.2"
|
||||
@ -3536,6 +3604,15 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.2"
|
||||
|
||||
@ -34,6 +34,7 @@ strum_macros = "0.24.3"
|
||||
[dev-dependencies]
|
||||
criterion = "0.4"
|
||||
pretty_assertions = "1.3.0"
|
||||
proptest = "1.1.0"
|
||||
|
||||
[features]
|
||||
profile = ["cpuprofiler"]
|
||||
|
||||
@ -33,15 +33,23 @@ impl fmt::Debug for AABB {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"AABB: <{} - {}> Vol: {}",
|
||||
"AABB: <{} - {}> Vol: {} Area: {}",
|
||||
self.bounds[0],
|
||||
self.bounds[1],
|
||||
self.volume()
|
||||
self.volume(),
|
||||
self.area()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl AABB {
|
||||
// Create AABB with min = f32::MAX and max = -f32::MAX. It is expected the caller will use grow to create
|
||||
// a vaild AABB.
|
||||
pub fn infinite() -> AABB {
|
||||
AABB {
|
||||
bounds: [Vec3::from(f32::MAX), Vec3::from(f32::MIN)],
|
||||
}
|
||||
}
|
||||
pub fn new<V: Into<Vec3>>(min: V, max: V) -> AABB {
|
||||
let min: Vec3 = min.into();
|
||||
let max: Vec3 = max.into();
|
||||
@ -51,10 +59,35 @@ impl AABB {
|
||||
AABB { bounds: [min, max] }
|
||||
}
|
||||
|
||||
pub fn area(&self) -> f32 {
|
||||
if self.max().x == f32::MIN || self.min().x == f32::MAX {
|
||||
return 0.;
|
||||
}
|
||||
let e = self.max() - self.min();
|
||||
let v = e.x * e.y + e.y * e.z + e.z * e.x;
|
||||
if v.is_finite() {
|
||||
v
|
||||
} else {
|
||||
0.
|
||||
}
|
||||
}
|
||||
pub fn volume(&self) -> f32 {
|
||||
(self.min().x - self.max().x).abs()
|
||||
if self.max().x == f32::MIN || self.min().x == f32::MAX {
|
||||
return 0.;
|
||||
}
|
||||
let v = (self.min().x - self.max().x).abs()
|
||||
* (self.min().y - self.max().y).abs()
|
||||
* (self.min().z - self.max().z).abs()
|
||||
* (self.min().z - self.max().z).abs();
|
||||
if v.is_finite() {
|
||||
v
|
||||
} else {
|
||||
0.
|
||||
}
|
||||
}
|
||||
|
||||
pub fn grow(&mut self, v: Vec3) {
|
||||
self.bounds[0] = vec3::min(self.bounds[0], v);
|
||||
self.bounds[1] = vec3::max(self.bounds[1], v);
|
||||
}
|
||||
|
||||
pub fn longest_axis(&self) -> usize {
|
||||
@ -86,10 +119,14 @@ impl AABB {
|
||||
// TODO(wathiede): implement branchless https://tavianator.com/cgit/dimension.git/tree/libdimension/bvh/bvh.c#n194
|
||||
|
||||
pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
||||
self.hit_simd(r, t_min, t_max).is_some()
|
||||
}
|
||||
|
||||
pub fn hit_distance(&self, r: Ray, t_min: f32, t_max: f32) -> Option<f32> {
|
||||
self.hit_simd(r, t_min, t_max)
|
||||
}
|
||||
|
||||
pub fn hit_naive(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
||||
pub fn hit_naive(&self, r: Ray, t_min: f32, t_max: f32) -> Option<f32> {
|
||||
let mut t_min = t_min;
|
||||
let mut t_max = t_max;
|
||||
for axis in 0..3 {
|
||||
@ -100,13 +137,13 @@ impl AABB {
|
||||
t_min = t0.max(t_min);
|
||||
t_max = t1.min(t_max);
|
||||
if t_max <= t_min {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
true
|
||||
Some(t_min)
|
||||
}
|
||||
|
||||
pub fn hit2(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
||||
pub fn hit2(&self, r: Ray, t_min: f32, t_max: f32) -> Option<f32> {
|
||||
let mut t_min = t_min;
|
||||
let mut t_max = t_max;
|
||||
for axis in 0..3 {
|
||||
@ -122,13 +159,13 @@ impl AABB {
|
||||
t_min = max(t0, t_min);
|
||||
t_max = min(t1, t_max);
|
||||
if t_max <= t_min {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
true
|
||||
Some(t_min)
|
||||
}
|
||||
|
||||
pub fn hit_precompute(&self, r: Ray, t0: f32, t1: f32) -> bool {
|
||||
pub fn hit_precompute(&self, r: Ray, t0: f32, t1: f32) -> Option<f32> {
|
||||
// TODO(wathiede): this has bugs.
|
||||
let mut t_min = (self.bounds[r.sign[0]].x - r.origin.x) * r.inv_direction.x;
|
||||
let mut t_max = (self.bounds[1 - r.sign[0]].x - r.origin.x) * r.inv_direction.x;
|
||||
@ -136,7 +173,7 @@ impl AABB {
|
||||
let t_y_max = (self.bounds[1 - r.sign[0]].y - r.origin.y) * r.inv_direction.y;
|
||||
|
||||
if t_min > t_y_max || t_y_min > t_max {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
|
||||
if t_y_min > t_min {
|
||||
@ -149,7 +186,7 @@ impl AABB {
|
||||
let t_z_min = (self.bounds[r.sign[2]].z - r.origin.z) * r.inv_direction.z;
|
||||
let t_z_max = (self.bounds[1 - r.sign[2]].z - r.origin.z) * r.inv_direction.z;
|
||||
if t_min > t_z_max || t_z_min > t_max {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
if t_z_min > t_min {
|
||||
t_min = t_z_min;
|
||||
@ -157,10 +194,14 @@ impl AABB {
|
||||
if t_z_max < t_max {
|
||||
t_max = t_z_max;
|
||||
}
|
||||
t_min < t1 && t_max > t0
|
||||
if t_min < t1 && t_max > t0 {
|
||||
Some(t_min)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hit_simd(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
||||
pub fn hit_simd(&self, r: Ray, t_min: f32, t_max: f32) -> Option<f32> {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
unsafe {
|
||||
use std::arch::x86_64::*;
|
||||
@ -177,14 +218,18 @@ impl AABB {
|
||||
let vmin4: (f32, f32, f32, f32) = std::mem::transmute(vmin4);
|
||||
let tmax = min(min(vmax4.0, min(vmax4.1, vmax4.2)), t_max);
|
||||
let tmin = max(max(vmin4.0, max(vmin4.1, vmin4.2)), t_min);
|
||||
tmin <= tmax
|
||||
if tmin <= tmax {
|
||||
Some(tmin)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
// TODO(wathiede): add NEON implementation.
|
||||
self.hit2(r, t_min, t_max)
|
||||
}
|
||||
|
||||
pub fn hit_fast(&self, r: Ray, _t_min: f32, _t_max: f32) -> bool {
|
||||
pub fn hit_fast(&self, r: Ray, _t_min: f32, _t_max: f32) -> Option<f32> {
|
||||
let b = self;
|
||||
let mut t1 = (b.min()[0] - r.origin[0]) * 1. / r.direction[0];
|
||||
let mut t2 = (b.max()[0] - r.origin[0]) * 1. / r.direction[0];
|
||||
@ -200,7 +245,11 @@ impl AABB {
|
||||
tmax = tmax.min(t1.max(t2));
|
||||
}
|
||||
|
||||
tmax > tmin.max(0.0)
|
||||
if tmax > tmin.max(0.0) {
|
||||
Some(tmin)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,6 +271,13 @@ pub fn surrounding_box(box0: &AABB, box1: &AABB) -> AABB {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn infinite() {
|
||||
let bb = AABB::infinite();
|
||||
assert_eq!(bb.area(), 0.);
|
||||
assert_eq!(bb.volume(), 0.);
|
||||
}
|
||||
|
||||
macro_rules! hit_test {
|
||||
($($name:ident,)*) => {
|
||||
$(
|
||||
@ -238,74 +294,74 @@ mod tests {
|
||||
fn hit_front() {
|
||||
let bb = test_bb();
|
||||
let r = Ray::new([-2., 0., 0.], [1., 0., 0.], 0.5);
|
||||
assert!(bb.$name(r, T_MIN, T_MAX));
|
||||
assert!(bb.$name(r, T_MIN, T_MAX).is_some());
|
||||
}
|
||||
#[test]
|
||||
fn hit_back() {
|
||||
let bb = test_bb();
|
||||
let r = Ray::new([2., 0., 0.], [-1., 0., 0.], 0.5);
|
||||
assert!(bb.$name(r, T_MIN, T_MAX));
|
||||
assert!(bb.$name(r, T_MIN, T_MAX).is_some());
|
||||
}
|
||||
#[test]
|
||||
fn hit_top() {
|
||||
let bb = test_bb();
|
||||
let r = Ray::new([0., 2., 0.], [0., -1., 0.], 0.5);
|
||||
assert!(bb.$name(r, T_MIN, T_MAX));
|
||||
assert!(bb.$name(r, T_MIN, T_MAX).is_some());
|
||||
}
|
||||
#[test]
|
||||
fn hit_bottom() {
|
||||
let bb = test_bb();
|
||||
let r = Ray::new([0., -2., 0.], [0., 1., 0.], 0.5);
|
||||
assert!(bb.$name(r, T_MIN, T_MAX));
|
||||
assert!(bb.$name(r, T_MIN, T_MAX).is_some());
|
||||
}
|
||||
#[test]
|
||||
fn hit_left() {
|
||||
let bb = test_bb();
|
||||
let r = Ray::new([0., 0., -2.], [0., 0., 1.], 0.5);
|
||||
assert!(bb.$name(r, T_MIN, T_MAX));
|
||||
assert!(bb.$name(r, T_MIN, T_MAX).is_some());
|
||||
}
|
||||
#[test]
|
||||
fn hit_right() {
|
||||
let bb = test_bb();
|
||||
let r = Ray::new([0., 0., 2.], [0., 0., -1.], 0.5);
|
||||
assert!(bb.$name(r, T_MIN, T_MAX));
|
||||
assert!(bb.$name(r, T_MIN, T_MAX).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn miss_front() {
|
||||
let bb = test_bb();
|
||||
let r = Ray::new([0., 1.1, -1.1], [0., -1., 0.], 0.5);
|
||||
assert!(!bb.$name(r, T_MIN, T_MAX));
|
||||
assert_eq!(bb.$name(r, T_MIN, T_MAX), None);
|
||||
}
|
||||
#[test]
|
||||
fn miss_back() {
|
||||
let bb = test_bb();
|
||||
let r = Ray::new([0., 1.1, 1.1], [0., -1., 0.], 0.5);
|
||||
assert!(!bb.$name(r, T_MIN, T_MAX));
|
||||
assert_eq!(bb.$name(r, T_MIN, T_MAX), None);
|
||||
}
|
||||
#[test]
|
||||
fn miss_top() {
|
||||
let bb = test_bb();
|
||||
let r = Ray::new([0., 1.1, -1.1], [0., 0., 1.], 0.5);
|
||||
assert!(!bb.$name(r, T_MIN, T_MAX));
|
||||
assert_eq!(bb.$name(r, T_MIN, T_MAX), None);
|
||||
}
|
||||
#[test]
|
||||
fn miss_bottom() {
|
||||
let bb = test_bb();
|
||||
let r = Ray::new([0., -1.1, -1.1], [0., 0., 1.], 0.5);
|
||||
assert!(!bb.$name(r, T_MIN, T_MAX));
|
||||
assert_eq!(bb.$name(r, T_MIN, T_MAX), None);
|
||||
}
|
||||
#[test]
|
||||
fn miss_left() {
|
||||
let bb = test_bb();
|
||||
let r = Ray::new([-1.1, 0., -1.1], [0., 0., 1.], 0.5);
|
||||
assert!(!bb.$name(r, T_MIN, T_MAX));
|
||||
assert_eq!(bb.$name(r, T_MIN, T_MAX), None);
|
||||
}
|
||||
#[test]
|
||||
fn miss_right() {
|
||||
let bb = test_bb();
|
||||
let r = Ray::new([1.1, 0., -1.1], [0., 0., 1.], 0.5);
|
||||
assert!(!bb.$name(r, T_MIN, T_MAX));
|
||||
assert_eq!(bb.$name(r, T_MIN, T_MAX), None);
|
||||
}
|
||||
}
|
||||
)*
|
||||
@ -314,8 +370,8 @@ mod tests {
|
||||
|
||||
hit_test! {
|
||||
hit_naive,
|
||||
hit2,
|
||||
hit_fast,
|
||||
hit_simd,
|
||||
hit_fast,
|
||||
hit2,
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
use std::f32::EPSILON;
|
||||
use std::fmt;
|
||||
|
||||
use log::info;
|
||||
use stl::STL;
|
||||
|
||||
use crate::{
|
||||
@ -16,19 +17,24 @@ use crate::{
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct BVHNode {
|
||||
aabb: AABB,
|
||||
// When prim_count==0, left_first holds the left child's index in bvh_nodes. When >0 left_first
|
||||
// When tri_count==0, left_first holds the left child's index in bvh_nodes. When >0 left_first
|
||||
// holds the index for the first triangle in triangles.
|
||||
left_first: u32,
|
||||
prim_count: u32,
|
||||
tri_count: u32,
|
||||
}
|
||||
|
||||
impl BVHNode {
|
||||
fn is_leaf(&self) -> bool {
|
||||
self.prim_count > 0
|
||||
self.tri_count > 0
|
||||
}
|
||||
|
||||
fn cost(&self) -> f32 {
|
||||
let area = self.aabb.area();
|
||||
self.tri_count as f32 * area
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Triangle {
|
||||
centroid: Vec3,
|
||||
verts: [Vec3; 3],
|
||||
@ -48,10 +54,17 @@ where
|
||||
M: Material,
|
||||
{
|
||||
pub triangles: Vec<Triangle>,
|
||||
triangle_index: Vec<usize>,
|
||||
material: M,
|
||||
bvh_nodes: Vec<BVHNode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Bin {
|
||||
bounds: AABB,
|
||||
tri_count: usize,
|
||||
}
|
||||
|
||||
impl<M> fmt::Debug for BVHTriangles<M>
|
||||
where
|
||||
M: Material,
|
||||
@ -80,7 +93,7 @@ where
|
||||
}
|
||||
let n = &self.bvh_nodes[i];
|
||||
if n.is_leaf() {
|
||||
for t_idx in n.left_first..(n.left_first + n.prim_count) {
|
||||
for t_idx in n.left_first..(n.left_first + n.tri_count) {
|
||||
if f.alternate() {
|
||||
write!(f, "\t")?;
|
||||
}
|
||||
@ -95,12 +108,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SplitCost {
|
||||
pos: f32,
|
||||
axis: usize,
|
||||
cost: f32,
|
||||
}
|
||||
|
||||
const ROOT_NODE_IDX: usize = 0;
|
||||
impl<M> BVHTriangles<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
pub fn new(stl: &STL, material: M) -> BVHTriangles<M> {
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
assert_eq!(std::mem::size_of::<BVHNode>(), 32);
|
||||
let div3 = 1. / 3.;
|
||||
let triangles: Vec<_> = stl
|
||||
@ -117,15 +139,61 @@ where
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let triangle_index = (0..triangles.len()).collect();
|
||||
|
||||
let n = 2 * triangles.len() - 2;
|
||||
let bvh_nodes = Vec::with_capacity(n);
|
||||
let mut bvh = BVHTriangles {
|
||||
triangles,
|
||||
triangle_index,
|
||||
bvh_nodes,
|
||||
material,
|
||||
};
|
||||
bvh.build_bvh();
|
||||
info!(
|
||||
"BVHTriangles build time {:0.3}s",
|
||||
now.elapsed().as_secs_f32()
|
||||
);
|
||||
struct Stats {
|
||||
nodes: usize,
|
||||
leafs: usize,
|
||||
min_tris: u32,
|
||||
max_tris: u32,
|
||||
}
|
||||
impl Default for Stats {
|
||||
fn default() -> Self {
|
||||
Stats {
|
||||
nodes: 0,
|
||||
leafs: 0,
|
||||
min_tris: u32::MAX,
|
||||
max_tris: u32::MIN,
|
||||
}
|
||||
}
|
||||
}
|
||||
let stats = bvh.bvh_nodes.iter().fold(Stats::default(), |mut stats, n| {
|
||||
stats.nodes += 1;
|
||||
if n.is_leaf() {
|
||||
stats.leafs += 1;
|
||||
stats.min_tris = n.tri_count.min(stats.min_tris);
|
||||
stats.max_tris = n.tri_count.max(stats.max_tris);
|
||||
}
|
||||
stats
|
||||
});
|
||||
info!("BVHTriangles build stats:");
|
||||
info!(" Nodes: {}", stats.nodes);
|
||||
info!(" Leaves: {}", stats.leafs);
|
||||
info!(" Tris: {}", bvh.triangles.len());
|
||||
info!(" Min Tri: {}", stats.min_tris);
|
||||
info!(" Max Tri: {}", stats.max_tris);
|
||||
info!(" Avg Tri: {}", bvh.triangles.len() / stats.leafs);
|
||||
info!(" Predict: {}", bvh.bvh_nodes.capacity());
|
||||
info!(" Actual: {}", bvh.bvh_nodes.len());
|
||||
info!(
|
||||
" Savings: {} bytes",
|
||||
(bvh.bvh_nodes.capacity() - bvh.bvh_nodes.len()) * std::mem::size_of::<BVHNode>()
|
||||
);
|
||||
bvh.bvh_nodes.shrink_to_fit();
|
||||
//dbg!(&bvh);
|
||||
bvh
|
||||
}
|
||||
|
||||
@ -134,7 +202,7 @@ where
|
||||
let root = BVHNode {
|
||||
aabb: AABB::default(),
|
||||
left_first: 0,
|
||||
prim_count: self.triangles.len() as u32,
|
||||
tri_count: self.triangles.len() as u32,
|
||||
};
|
||||
self.bvh_nodes.push(root);
|
||||
self.update_node_bounds(ROOT_NODE_IDX);
|
||||
@ -145,8 +213,9 @@ where
|
||||
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.left_first..(node.left_first + node.prim_count) {
|
||||
let leaf_tri = &self.triangles[i as usize];
|
||||
for i in node.left_first..(node.left_first + node.tri_count) {
|
||||
let leaf_tri_idx = self.triangle_index[i as usize];
|
||||
let leaf_tri = &self.triangles[leaf_tri_idx];
|
||||
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]);
|
||||
@ -156,37 +225,118 @@ where
|
||||
}
|
||||
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 (left_first, prim_count, left_count, i) = {
|
||||
let node = &self.bvh_nodes[idx];
|
||||
|
||||
// Compute split plane and position.
|
||||
let extent = node.aabb.max() - node.aabb.min();
|
||||
let axis = node.aabb.longest_axis();
|
||||
let split_pos = node.aabb.min()[axis] + extent[axis] * 0.5;
|
||||
fn find_best_split_plane(&self, node: &BVHNode) -> SplitCost {
|
||||
let mut best = SplitCost {
|
||||
pos: 0.,
|
||||
cost: f32::MAX,
|
||||
axis: usize::MAX,
|
||||
};
|
||||
|
||||
for axis in 0..3 {
|
||||
let mut bounds_min = f32::MAX;
|
||||
let mut bounds_max = f32::MIN;
|
||||
|
||||
for i in 0..node.tri_count {
|
||||
let triangle = &self.triangles[self.triangle_index[(node.left_first + i) as usize]];
|
||||
bounds_min = bounds_min.min(triangle.centroid[axis]);
|
||||
bounds_max = bounds_max.max(triangle.centroid[axis]);
|
||||
}
|
||||
|
||||
if bounds_min == bounds_max {
|
||||
continue;
|
||||
}
|
||||
|
||||
const NUM_BINS: usize = 8;
|
||||
let mut bins: Vec<_> = (0..NUM_BINS)
|
||||
.map(|_| Bin {
|
||||
bounds: AABB::infinite(),
|
||||
tri_count: 0,
|
||||
})
|
||||
.collect();
|
||||
let scale = bins.len() as f32 / (bounds_max - bounds_min);
|
||||
// populate the bins
|
||||
for i in 0..node.tri_count {
|
||||
let triangle = &self.triangles[self.triangle_index[(node.left_first + i) as usize]];
|
||||
let bin_idx = (triangle.centroid[axis] - bounds_min) * scale;
|
||||
let bin_idx = (bins.len() - 1).min(bin_idx as usize);
|
||||
|
||||
bins[bin_idx].tri_count += 1;
|
||||
bins[bin_idx].bounds.grow(triangle.verts[0]);
|
||||
bins[bin_idx].bounds.grow(triangle.verts[1]);
|
||||
bins[bin_idx].bounds.grow(triangle.verts[2]);
|
||||
}
|
||||
|
||||
// gather data for the 7 planes between the 8 bins
|
||||
let mut left_area: Vec<_> = (0..bins.len() - 1).map(|_| 0.).collect();
|
||||
let mut right_area: Vec<_> = (0..bins.len() - 1).map(|_| 0.).collect();
|
||||
let mut left_count: Vec<_> = (0..bins.len() - 1).map(|_| 0).collect();
|
||||
let mut right_count: Vec<_> = (0..bins.len() - 1).map(|_| 0).collect();
|
||||
let mut left_box = AABB::infinite();
|
||||
let mut right_box = AABB::infinite();
|
||||
let mut left_sum = 0;
|
||||
let mut right_sum = 0;
|
||||
for i in 0..(bins.len() - 1) {
|
||||
left_sum += bins[i].tri_count;
|
||||
left_count[i] = left_sum;
|
||||
left_box.grow(bins[i].bounds.min());
|
||||
left_box.grow(bins[i].bounds.max());
|
||||
left_area[i] = left_box.area();
|
||||
|
||||
right_sum += bins[bins.len() - 1 - i].tri_count;
|
||||
right_count[bins.len() - 2 - i] = right_sum;
|
||||
right_box.grow(bins[bins.len() - 1 - i].bounds.min());
|
||||
right_box.grow(bins[bins.len() - 1 - i].bounds.max());
|
||||
right_area[bins.len() - 2 - i] = right_box.area();
|
||||
}
|
||||
|
||||
let scale = (bounds_max - bounds_min) / bins.len() as f32;
|
||||
// calculate SAH cost for the 7 planes
|
||||
for i in 0..(bins.len() - 1) {
|
||||
let plane_cost =
|
||||
left_count[i] as f32 * left_area[i] + right_count[i] as f32 * right_area[i];
|
||||
if plane_cost < best.cost {
|
||||
best.axis = axis;
|
||||
best.pos = bounds_min + scale * (i as f32 + 1.);
|
||||
best.cost = plane_cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
best
|
||||
}
|
||||
|
||||
fn subdivide(&mut self, idx: usize) {
|
||||
let (left_first, tri_count, left_count, i) = {
|
||||
let node = &self.bvh_nodes[idx];
|
||||
let split = self.find_best_split_plane(&node);
|
||||
let no_split_cost = node.cost();
|
||||
// Stop subdividing if it isn't getting any better.
|
||||
if split.cost >= no_split_cost {
|
||||
return;
|
||||
}
|
||||
|
||||
// Split the group in two halves.
|
||||
let mut i = node.left_first as isize;
|
||||
let mut j = i + node.prim_count as isize - 1;
|
||||
let mut j = i + node.tri_count as isize - 1;
|
||||
while i <= j {
|
||||
if self.triangles[i as usize].centroid[axis] < split_pos {
|
||||
if self.triangles[self.triangle_index[i as usize]].centroid[split.axis] < split.pos
|
||||
{
|
||||
i += 1;
|
||||
} else {
|
||||
self.triangles.swap(i as usize, j as usize);
|
||||
self.triangles.swap(
|
||||
self.triangle_index[i as usize],
|
||||
self.triangle_index[j as usize],
|
||||
);
|
||||
j -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Create child nodes for each half.
|
||||
let left_count = i as u32 - node.left_first;
|
||||
if left_count == 0 || left_count == node.prim_count {
|
||||
if left_count == 0 || left_count == node.tri_count {
|
||||
return;
|
||||
}
|
||||
(node.left_first, node.prim_count, left_count, i)
|
||||
(node.left_first, node.tri_count, left_count, i)
|
||||
};
|
||||
|
||||
// create child nodes
|
||||
@ -195,75 +345,92 @@ where
|
||||
let left = BVHNode {
|
||||
aabb: AABB::default(),
|
||||
left_first,
|
||||
prim_count: left_count as u32,
|
||||
tri_count: left_count as u32,
|
||||
};
|
||||
let right = BVHNode {
|
||||
aabb: AABB::default(),
|
||||
left_first: i as u32,
|
||||
prim_count: (prim_count - left_count) as u32,
|
||||
tri_count: (tri_count - left_count) as u32,
|
||||
};
|
||||
self.bvh_nodes.push(left);
|
||||
self.bvh_nodes.push(right);
|
||||
let node = &mut self.bvh_nodes[idx];
|
||||
node.left_first = left_child_idx;
|
||||
node.prim_count = 0;
|
||||
node.tri_count = 0;
|
||||
|
||||
// Recurse
|
||||
self.update_node_bounds(left_child_idx as usize);
|
||||
self.update_node_bounds(right_child_idx as usize);
|
||||
self.subdivide(left_child_idx as usize);
|
||||
self.update_node_bounds(right_child_idx as usize);
|
||||
self.subdivide(right_child_idx as usize);
|
||||
}
|
||||
|
||||
fn intersect_bvh(&self, r: Ray, node_idx: u32, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
let node = &self.bvh_nodes[node_idx as usize];
|
||||
if !node.aabb.hit(r, t_min, t_max) {
|
||||
return None;
|
||||
}
|
||||
//dbg!(&self);
|
||||
fn intersect_bvh(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
let mut node = &self.bvh_nodes[ROOT_NODE_IDX];
|
||||
let mut stack = Vec::with_capacity(2);
|
||||
let mut nearest = None;
|
||||
loop {
|
||||
if node.is_leaf() {
|
||||
let canditate = (node.left_first..(node.left_first + node.tri_count))
|
||||
// Map from idx to Triangle
|
||||
.map(|idx| &self.triangles[self.triangle_index[idx as usize]])
|
||||
// Try to hit all triangles for this node, filtering out misses.
|
||||
.filter_map(|tri| intersect_tri(r, &tri))
|
||||
// Find the nearest hit (if any).
|
||||
.min_by(|a, b| a.t.partial_cmp(&b.t).unwrap());
|
||||
|
||||
if node.is_leaf() {
|
||||
return self
|
||||
.triangles
|
||||
.iter()
|
||||
.map(|tri| {
|
||||
if let Some(RayTriangleResult { t, p }) = intersect_tri(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).unit_vector();
|
||||
//println!("hit triangle {tri:?}");
|
||||
Some(HitRecord {
|
||||
t,
|
||||
uv,
|
||||
p,
|
||||
normal,
|
||||
material: &self.material,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
// merge candidate with nearest.
|
||||
nearest = match (&canditate, &nearest) {
|
||||
(Some(_), None) => canditate,
|
||||
(None, Some(_)) => nearest,
|
||||
(Some(c), Some(n)) => {
|
||||
//info!("merging {c:#?} and {n:#?}");
|
||||
if c.t < n.t {
|
||||
canditate
|
||||
} else {
|
||||
nearest
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter_map(|hr| hr)
|
||||
.min_by(|a, b| a.t.partial_cmp(&b.t).unwrap());
|
||||
} else {
|
||||
let r1 = self.intersect_bvh(r, node.left_first, t_min, t_max);
|
||||
let r2 = self.intersect_bvh(r, node.left_first + 1, t_min, t_max);
|
||||
// Merge results, if both hit, take the one closest to the ray origin (smallest t
|
||||
// value).
|
||||
match (&r1, &r2) {
|
||||
(Some(_), None) => return r1,
|
||||
(None, Some(_)) => return r2,
|
||||
(None, None) => (),
|
||||
(Some(rp1), Some(rp2)) => return if rp1.t < rp2.t { r1 } else { r2 },
|
||||
(None, None) => None,
|
||||
};
|
||||
if stack.is_empty() {
|
||||
break;
|
||||
}
|
||||
node = stack.pop().unwrap();
|
||||
continue;
|
||||
}
|
||||
|
||||
let child1 = &self.bvh_nodes[node.left_first as usize];
|
||||
let child2 = &self.bvh_nodes[node.left_first as usize + 1];
|
||||
let dist1 = child1.aabb.hit_distance(r, t_min, t_max);
|
||||
let dist2 = child2.aabb.hit_distance(r, t_min, t_max);
|
||||
// Swap c1/c2 & d1/d2 based on d1/d2.
|
||||
let (child1, child2, dist1, dist2) = match (dist1, dist2) {
|
||||
(Some(d1), Some(d2)) if d1 > d2 => (child2, child1, dist2, dist1),
|
||||
(None, Some(_)) => (child2, child1, dist2, dist1),
|
||||
_ => (child1, child2, dist1, dist2),
|
||||
};
|
||||
|
||||
// dist1/child1 should now be the nearest hit.
|
||||
|
||||
// If we missed dist1/child1, then we implicitly missed dist2/child2, so pop a child
|
||||
// from the stack or exit the function.
|
||||
if dist1.is_none() {
|
||||
if stack.is_empty() {
|
||||
break;
|
||||
}
|
||||
node = stack.pop().unwrap();
|
||||
} else {
|
||||
// We hit child1, so process it next.
|
||||
node = child1;
|
||||
// If we also hit child2 save it on the stack so we can process it later.
|
||||
if dist2.is_some() {
|
||||
stack.push(child2);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
nearest
|
||||
.and_then(|rtr: RayTriangleResult| Some(rtr.hit_record_with_material(&self.material)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -325,6 +492,7 @@ fn intersect_tri(r: Ray, tri: &Triangle) -> Option<RayTriangleResult> {
|
||||
return Some(RayTriangleResult {
|
||||
t,
|
||||
p: r.point_at_parameter(t),
|
||||
tri: tri.clone(),
|
||||
});
|
||||
}
|
||||
None
|
||||
@ -335,17 +503,41 @@ 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)
|
||||
self.intersect_bvh(r, t_min, t_max)
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
|
||||
Some(self.bvh_nodes[0].aabb)
|
||||
Some(self.bvh_nodes[ROOT_NODE_IDX].aabb)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RayTriangleResult {
|
||||
t: f32,
|
||||
p: Vec3,
|
||||
tri: Triangle,
|
||||
}
|
||||
|
||||
impl RayTriangleResult {
|
||||
fn hit_record_with_material<'m>(self, material: &'m dyn Material) -> HitRecord<'m> {
|
||||
// We don't support UV (yet?).
|
||||
let uv = (0.5, 0.5);
|
||||
let v0 = self.tri.verts[0];
|
||||
let v1 = self.tri.verts[1];
|
||||
let v2 = self.tri.verts[2];
|
||||
|
||||
let v0v1 = v1 - v0;
|
||||
let v0v2 = v2 - v0;
|
||||
let normal = cross(v0v1, v0v2).unit_vector();
|
||||
//println!("hit triangle {tri:?}");
|
||||
HitRecord {
|
||||
t: self.t,
|
||||
uv,
|
||||
p: self.p,
|
||||
normal,
|
||||
material,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -360,67 +552,13 @@ mod tests {
|
||||
texture::ConstantTexture,
|
||||
};
|
||||
use pretty_assertions::assert_eq;
|
||||
use proptest::prelude::*;
|
||||
use std::{
|
||||
io::{BufReader, Cursor},
|
||||
sync::Arc,
|
||||
};
|
||||
use stl::STL;
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn build_bvh() {
|
||||
let stl_triangles: Vec<_> = (0..4)
|
||||
.flat_map(|y| {
|
||||
(0..2).map(move |x| {
|
||||
let x = x as f32;
|
||||
let y = y as f32;
|
||||
stl::Triangle {
|
||||
normal: [1., 0., 0.].into(),
|
||||
verts: [
|
||||
[2. * x + 0., 2. * y + 0., 0.].into(),
|
||||
[2. * x + 1., 2. * y + 0., 0.].into(),
|
||||
[2. * x + 1., 2. * y + 1., 0.].into(),
|
||||
],
|
||||
attr: 0,
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let stl = STL {
|
||||
header: [0; 80],
|
||||
triangles: stl_triangles,
|
||||
};
|
||||
/*
|
||||
let mut bvh_triangles: Vec<_> = stl_triangles
|
||||
.iter()
|
||||
.map(|tri| {
|
||||
let div3 = 1. / 3.;
|
||||
let v0 = tri.verts[0];
|
||||
let v1 = tri.verts[1];
|
||||
let v2 = tri.verts[2];
|
||||
let centroid = (v0 + v1 + v2) * div3;
|
||||
Triangle {
|
||||
centroid,
|
||||
verts: tri.verts,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
bvh_triangles.sort_by(|a, b| a.centroid.y.partial_cmp(&b.centroid.y).unwrap());
|
||||
let material = Lambertian::new(ConstantTexture::new([0., 0., 0.]));
|
||||
let bvh_nodes = Default::default();
|
||||
let want = BVHTriangles {
|
||||
triangles: bvh_triangles,
|
||||
bvh_nodes,
|
||||
material,
|
||||
};
|
||||
*/
|
||||
let material = Lambertian::new(ConstantTexture::new([0., 0., 0.]));
|
||||
let bvh = BVHTriangles::new(&stl, material);
|
||||
dbg!(&bvh);
|
||||
assert_eq!(bvh.bvh_nodes.len(), 2 * bvh.triangles.len() - 2);
|
||||
}
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn compare_cuboid() {
|
||||
let c = Cuboid::new(
|
||||
@ -478,7 +616,6 @@ mod tests {
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
// These currently differ between STL and cuboid.
|
||||
if false {
|
||||
// Outward in at an angle.
|
||||
let sqrt2 = 2f32.sqrt();
|
||||
@ -490,8 +627,6 @@ mod tests {
|
||||
]);
|
||||
}
|
||||
|
||||
// TODO(wathiede): proptest this, it's still not perfectly equal when rendering.
|
||||
|
||||
for r in rays.into_iter() {
|
||||
let c_hit = c
|
||||
.hit(r, 0., f32::MAX)
|
||||
@ -515,4 +650,62 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
// TODO(wathiede): make this work.
|
||||
//#[test]
|
||||
fn compare_cuboid_proptest(
|
||||
ox in -20.0f32..40.0,
|
||||
oy in -20.0f32..40.0,
|
||||
oz in -20.0f32..40.0,
|
||||
dx in -1.0f32..1.0,
|
||||
dy in -1.0f32..1.0,
|
||||
dz in -1.0f32..1.0,
|
||||
) {
|
||||
let r = Ray::new([ox,oy,oz].into(), Vec3::new(dx, dy, dz).unit_vector(), 0.5);
|
||||
let c = Cuboid::new(
|
||||
[0., 0., 0.].into(),
|
||||
[20., 20., 20.].into(),
|
||||
Arc::new(Dielectric::new(1.5)),
|
||||
);
|
||||
let stl_cube = STL::parse(
|
||||
BufReader::new(Cursor::new(include_bytes!("../stls/cube.stl"))),
|
||||
false,
|
||||
)
|
||||
.expect("failed to parse cube");
|
||||
|
||||
let s = BVHTriangles::new(
|
||||
&stl_cube,
|
||||
Dielectric::new(1.5),
|
||||
//Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
|
||||
//Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))),
|
||||
);
|
||||
|
||||
let c_hit = c .hit(r, 0., f32::MAX);
|
||||
let s_hit = s .hit(r, 0., f32::MAX);
|
||||
|
||||
match (c_hit, s_hit) {
|
||||
(Some(_), None)=>assert!(false, "hit cuboid but not STL"),
|
||||
(None, Some(_))=>assert!(false, "hit STL but not cuboid"),
|
||||
(Some(c_hit), Some(s_hit))=> {
|
||||
assert!(
|
||||
(c_hit.t - s_hit.t).abs() < 0.00001,
|
||||
"{r:?} [t] c_hit: {c_hit:#?}, s_hit: {s_hit:#?}"
|
||||
);
|
||||
// uv isn't valid for BVHTriangles.
|
||||
// assert_eq!( c_hit.uv, s_hit.uv, "{i}: [uv] c_hit: {c_hit:?}, s_hit: {s_hit:?}");
|
||||
assert_eq!(
|
||||
c_hit.p, s_hit.p,
|
||||
"{r:?}: [p] c_hit: {c_hit:#?}, s_hit: {s_hit:#?}"
|
||||
);
|
||||
assert_eq!(
|
||||
c_hit.normal, s_hit.normal,
|
||||
"{r:?}: [normal] c_hit: {c_hit:?}, s_hit: {s_hit:?}"
|
||||
);
|
||||
},
|
||||
// It's okay if they both miss.
|
||||
(None,None)=>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ impl Material for Box<dyn Material> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Isotropic<T>
|
||||
where
|
||||
T: Texture,
|
||||
@ -83,7 +83,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Lambertian<T>
|
||||
where
|
||||
T: Texture,
|
||||
@ -115,7 +115,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Metal {
|
||||
albedo: Vec3,
|
||||
fuzzy: f32,
|
||||
@ -170,7 +170,7 @@ fn schlick(cosine: f32, ref_idx: f32) -> f32 {
|
||||
r0 + (1. - r0) * (1. - cosine).powf(5.)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Dielectric {
|
||||
ref_idx: f32,
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ use std::{
|
||||
Arc, Mutex,
|
||||
},
|
||||
thread,
|
||||
time::{self, Instant},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use core_affinity;
|
||||
@ -363,13 +363,13 @@ fn progress(
|
||||
start_time: Instant,
|
||||
last_stat: &RenderStats,
|
||||
current_stat: &RenderStats,
|
||||
time_diff: time::Duration,
|
||||
time_diff: Duration,
|
||||
pixel_total: usize,
|
||||
) -> String {
|
||||
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 now = Instant::now();
|
||||
let start_diff = now - start_time;
|
||||
let ratio = current_stat.pixels as f32 / pixel_total as f32;
|
||||
let percent = ratio * 100.;
|
||||
@ -390,6 +390,7 @@ fn progress(
|
||||
enum Request {
|
||||
Pixel { x: usize, y: usize },
|
||||
Line { width: usize, y: usize },
|
||||
// TODO(wathiede): add Cohort that does 4x4 or 8x8 pixel chunks.
|
||||
}
|
||||
|
||||
enum Response {
|
||||
@ -576,10 +577,10 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
|
||||
info!("Rendering with {} subsamples", scene.subsamples);
|
||||
|
||||
let pixel_total = scene.width * scene.height;
|
||||
let mut last_time = time::Instant::now();
|
||||
let mut last_time = Instant::now();
|
||||
let mut last_stat: RenderStats = Default::default();
|
||||
let mut current_stat: RenderStats = Default::default();
|
||||
let start_time = time::Instant::now();
|
||||
let render_start_time = Instant::now();
|
||||
for resp in pixel_resp_rx {
|
||||
match resp {
|
||||
Response::Pixel { x, y, pixel, rs } => {
|
||||
@ -594,13 +595,13 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
|
||||
}
|
||||
}
|
||||
|
||||
let now = time::Instant::now();
|
||||
let now = Instant::now();
|
||||
let time_diff = now - last_time;
|
||||
if time_diff > time::Duration::from_secs(1) {
|
||||
if time_diff > Duration::from_secs(5) {
|
||||
println!(
|
||||
"{}",
|
||||
progress(
|
||||
start_time,
|
||||
render_start_time,
|
||||
&last_stat,
|
||||
¤t_stat,
|
||||
time_diff,
|
||||
@ -614,12 +615,12 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
|
||||
for thr in handles {
|
||||
thr.join().expect("thread join");
|
||||
}
|
||||
let time_diff = time::Instant::now() - start_time;
|
||||
let time_diff = Instant::now() - render_start_time;
|
||||
println!(
|
||||
"Runtime {} seconds {}",
|
||||
"Render {} seconds {}",
|
||||
time_diff.as_secs_f32(),
|
||||
progress(
|
||||
start_time,
|
||||
render_start_time,
|
||||
&Default::default(),
|
||||
¤t_stat,
|
||||
time_diff,
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
use std::{
|
||||
io::{BufReader, Cursor},
|
||||
sync::Arc,
|
||||
};
|
||||
use std::io::{BufReader, Cursor};
|
||||
|
||||
use stl::STL;
|
||||
|
||||
use crate::{
|
||||
bvh_triangles::BVHTriangles,
|
||||
camera::Camera,
|
||||
cuboid::Cuboid,
|
||||
hitable::Hit,
|
||||
hitable_list::HitableList,
|
||||
kdtree::KDTree,
|
||||
@ -17,6 +13,7 @@ use crate::{
|
||||
scale::Scale,
|
||||
sphere::Sphere,
|
||||
texture::{ConstantTexture, EnvMap},
|
||||
translate::Translate,
|
||||
vec3::Vec3,
|
||||
};
|
||||
|
||||
@ -46,8 +43,7 @@ pub fn new(opt: &Opt) -> Scene {
|
||||
|
||||
let stl_cube = STL::parse(
|
||||
BufReader::new(Cursor::new(include_bytes!(
|
||||
"../../stls/cube.stl"
|
||||
//"../../stls/stanford_dragon-lowres.stl" //"../../stls/stanford_dragon.stl"
|
||||
"../../stls/stanford_dragon-lowres.stl" //"../../stls/stanford_dragon.stl"
|
||||
))),
|
||||
false,
|
||||
)
|
||||
@ -81,20 +77,9 @@ pub fn new(opt: &Opt) -> Scene {
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))),
|
||||
)),
|
||||
// STL Mesh
|
||||
Box::new(Scale::new(
|
||||
BVHTriangles::new(
|
||||
&stl_cube,
|
||||
Dielectric::new(1.5),
|
||||
//Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
|
||||
//Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))),
|
||||
),
|
||||
1.,
|
||||
//250.,
|
||||
)),
|
||||
Box::new(Cuboid::new(
|
||||
[-20., 0., 0.].into(),
|
||||
[0., 20., 20.].into(),
|
||||
Arc::new(Dielectric::new(1.5)),
|
||||
Box::new(Translate::new(
|
||||
Scale::new(BVHTriangles::new(&stl_cube, Dielectric::new(1.5)), 250.),
|
||||
[0., -10., 0.],
|
||||
)),
|
||||
];
|
||||
let world: Box<dyn Hit> = if opt.use_accel {
|
||||
|
||||
@ -12,7 +12,7 @@ use crate::{
|
||||
hitable::Hit,
|
||||
hitable_list::HitableList,
|
||||
kdtree::KDTree,
|
||||
material::{Dielectric, Lambertian},
|
||||
material::{Dielectric, Lambertian, Metal},
|
||||
renderer::{Opt, Scene},
|
||||
sphere::Sphere,
|
||||
texture::{ConstantTexture, EnvMap},
|
||||
@ -43,13 +43,20 @@ pub fn new(opt: &Opt) -> Scene {
|
||||
ConstantTexture::new(Vec3::new(0.4, 0.4, 0.4))
|
||||
};
|
||||
|
||||
let glass = Dielectric::new(1.5);
|
||||
let metal = Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2);
|
||||
let red = Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2)));
|
||||
|
||||
let box_material = glass;
|
||||
let _ = glass;
|
||||
let _ = metal;
|
||||
let _ = red;
|
||||
|
||||
let stl_cube = STL::parse(
|
||||
BufReader::new(Cursor::new(include_bytes!("../../stls/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
|
||||
// Earth sized sphere
|
||||
@ -77,16 +84,11 @@ pub fn new(opt: &Opt) -> Scene {
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))),
|
||||
)),
|
||||
// STL Mesh
|
||||
Box::new(BVHTriangles::new(
|
||||
&stl_cube,
|
||||
Dielectric::new(1.5),
|
||||
//Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
|
||||
//Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))),
|
||||
)),
|
||||
Box::new(BVHTriangles::new(&stl_cube, box_material.clone())),
|
||||
Box::new(Cuboid::new(
|
||||
[-20., 0., 0.].into(),
|
||||
[0., 20., 20.].into(),
|
||||
Arc::new(Dielectric::new(1.5)),
|
||||
Arc::new(box_material),
|
||||
)),
|
||||
];
|
||||
let world: Box<dyn Hit> = if opt.use_accel {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::{texture::Texture, vec3::Vec3};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ConstantTexture {
|
||||
color: Vec3,
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#![warn(unused_extern_crates)]
|
||||
use std::fs;
|
||||
use std::{fs, time::Instant};
|
||||
|
||||
#[cfg(feature = "profile")]
|
||||
use cpuprofiler::PROFILER;
|
||||
@ -36,6 +36,7 @@ impl MockProfiler {
|
||||
static PROFILER: MockProfiler = MockProfiler {};
|
||||
|
||||
fn main() -> Result<(), std::io::Error> {
|
||||
let start_time = Instant::now();
|
||||
stderrlog::new()
|
||||
.verbosity(3)
|
||||
.timestamp(stderrlog::Timestamp::Millisecond)
|
||||
@ -46,7 +47,7 @@ fn main() -> Result<(), std::io::Error> {
|
||||
eprintln!("--model should be one of {:?}", Model::VARIANTS);
|
||||
return Ok(());
|
||||
}
|
||||
info!("{:?}", opt);
|
||||
info!("{:#?}", opt);
|
||||
let scene = opt.model.as_ref().unwrap().scene(&opt);
|
||||
fs::create_dir_all(&opt.output)?;
|
||||
if opt.pprof.is_some() && !cfg!(feature = "profile") {
|
||||
@ -65,5 +66,7 @@ fn main() -> Result<(), std::io::Error> {
|
||||
PROFILER.lock().unwrap().stop().unwrap();
|
||||
}
|
||||
|
||||
let time_diff = Instant::now() - start_time;
|
||||
info!("Total runtime {} seconds", time_diff.as_secs_f32());
|
||||
res
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user