Added back codebase from previous projects
This commit is contained in:
parent
3e53256e53
commit
96e103ee61
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1 +1,4 @@
|
|||
/target
|
||||
target/
|
||||
*.ppm
|
||||
*.png
|
||||
!renders/*
|
||||
|
|
345
Cargo.lock
generated
345
Cargo.lock
generated
|
@ -2,6 +2,42 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
@ -9,10 +45,102 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
name = "cmake"
|
||||
version = "0.1.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
|
@ -20,10 +148,134 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.107"
|
||||
name = "hermit-abi"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
|
||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.24.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"jpeg-decoder",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packed_simd"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cf965495ba3190af4c54375cb7d5d4777954457a215035a783f9aec5380b19f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
|
@ -72,14 +324,83 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "raytracing-the-rest-of-your-life"
|
||||
version = "0.1.0"
|
||||
name = "rayon"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
name = "rayon-core"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raytracing-the-rest-of-your-life"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"num-traits",
|
||||
"packed_simd",
|
||||
"rand",
|
||||
"rayon",
|
||||
"sdl2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "sdl2"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"sdl2-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2-sys"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cmake",
|
||||
"libc",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -4,4 +4,13 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8.4"
|
||||
image = { version = "0.24.6", default-features = false, features = ["jpeg", "png"] }
|
||||
num-traits = "0.2.15"
|
||||
packed_simd = "0.3.8"
|
||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||
rayon = "1.7.0"
|
||||
sdl2 = { version = "0.35.2", features = ["bundled"], optional = true }
|
||||
|
||||
[features]
|
||||
default = ["gui"]
|
||||
gui = ["sdl2"]
|
||||
|
|
BIN
assets/earthmap.jpg
Normal file
BIN
assets/earthmap.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 158 KiB |
36
src/aabb.rs
Normal file
36
src/aabb.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use crate::types::{Ray, Vec3};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Aabb {
|
||||
pub min: Vec3,
|
||||
pub max: Vec3,
|
||||
}
|
||||
|
||||
impl Aabb {
|
||||
pub const fn new(min: Vec3, max: Vec3) -> Self {
|
||||
Self { min, max }
|
||||
}
|
||||
|
||||
pub fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> bool {
|
||||
let min = (self.min - ray.origin) / ray.direction;
|
||||
let max = (self.max - ray.origin) / ray.direction;
|
||||
|
||||
let mins = min.min(max);
|
||||
let maxs = min.max(max);
|
||||
|
||||
let tmin = mins.max_element(t_min);
|
||||
let tmax = maxs.min_element(t_max);
|
||||
|
||||
tmax > tmin
|
||||
}
|
||||
|
||||
pub fn surrounding_box(box0: Aabb, box1: Aabb) -> Self {
|
||||
let smol_box = Vec3::min(box0.min, box1.min);
|
||||
let big_box = Vec3::max(box0.max, box1.max);
|
||||
|
||||
Self {
|
||||
min: smol_box,
|
||||
max: big_box,
|
||||
}
|
||||
}
|
||||
}
|
90
src/camera.rs
Normal file
90
src/camera.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
use {
|
||||
crate::types::{Ray, Vec3},
|
||||
rand::Rng,
|
||||
};
|
||||
|
||||
pub struct Camera {
|
||||
origin: Vec3,
|
||||
horizontal: Vec3,
|
||||
vertical: Vec3,
|
||||
lower_left_corner: Vec3,
|
||||
lens_radius: f64,
|
||||
|
||||
// position vectors
|
||||
u: Vec3,
|
||||
v: Vec3,
|
||||
w: Vec3,
|
||||
|
||||
shutter_open: f64,
|
||||
shutter_close: f64,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
// vertical_fov is the viewable angle from top->bottom
|
||||
// look_from is basically camera position
|
||||
// look_at is the point where camera is looking
|
||||
// v_up is camera's up vector. i.e. it points upwards from the camera
|
||||
// orthogonal to look_from - look_at vector
|
||||
pub fn new(
|
||||
look_from: Vec3,
|
||||
look_at: Vec3,
|
||||
v_up: Vec3,
|
||||
vertical_fov: f64,
|
||||
aspect: f64,
|
||||
aperture: f64,
|
||||
focus_distance: f64,
|
||||
shutter_open: f64,
|
||||
shutter_close: f64,
|
||||
) -> Self {
|
||||
// convert degree to radian
|
||||
let angle = vertical_fov * std::f64::consts::PI / 180.0;
|
||||
let half_height = (angle / 2.0).tan();
|
||||
let half_width = aspect * half_height;
|
||||
|
||||
let origin = look_from;
|
||||
let w = (look_from - look_at).unit_vector();
|
||||
let u = v_up.cross(&w).unit_vector();
|
||||
let v = w.cross(&u);
|
||||
|
||||
let lower_left_corner = origin
|
||||
- u * focus_distance * half_width
|
||||
- v * focus_distance * half_height
|
||||
- w * focus_distance;
|
||||
let horizontal = u * half_width * focus_distance * 2.0;
|
||||
let vertical = v * half_height * focus_distance * 2.0;
|
||||
let lens_radius = aperture / 2.0;
|
||||
|
||||
Self {
|
||||
origin,
|
||||
horizontal,
|
||||
vertical,
|
||||
lower_left_corner,
|
||||
lens_radius,
|
||||
u,
|
||||
v,
|
||||
w,
|
||||
shutter_open,
|
||||
shutter_close,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_ray<R: Rng + ?Sized>(&self, u: f64, v: f64, rng: &mut R) -> Ray {
|
||||
let rd = random_in_unit_disk(rng) * self.lens_radius;
|
||||
let offset = self.u * rd.x() + self.v * rd.y();
|
||||
let time = rng.gen_range(self.shutter_open..=self.shutter_close);
|
||||
Ray::new(
|
||||
self.origin + offset,
|
||||
self.lower_left_corner + self.horizontal * u + self.vertical * v - self.origin - offset,
|
||||
time,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn random_in_unit_disk<R: Rng + ?Sized>(rng: &mut R) -> Vec3 {
|
||||
let mut p = Vec3::new(rng.gen::<f64>(), rng.gen::<f64>(), 0.0) * 2.0 - Vec3::new(1.0, 1.0, 0.0);
|
||||
|
||||
while p.dot(&p) >= 1.0 {
|
||||
p = Vec3::new(rng.gen::<f64>(), rng.gen::<f64>(), 0.0) * 2.0 - Vec3::new(1.0, 0.0, 0.0);
|
||||
}
|
||||
p
|
||||
}
|
126
src/demos/checkered_motion_blur.rs
Normal file
126
src/demos/checkered_motion_blur.rs
Normal file
|
@ -0,0 +1,126 @@
|
|||
use crate::{
|
||||
demos::{Demo, ParallelHit},
|
||||
hitable::{
|
||||
shapes::{MovingSphere, Sphere},
|
||||
BvhNode,
|
||||
},
|
||||
materials::{Dielectric, Lambertian, Metal},
|
||||
texture::{Checker, Solid},
|
||||
types::Vec3,
|
||||
Camera,
|
||||
};
|
||||
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct CheckeredMotionBlur {}
|
||||
|
||||
impl Demo for CheckeredMotionBlur {
|
||||
type DemoT = BvhNode<Arc<dyn ParallelHit>>;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"checkered_motion_blur"
|
||||
}
|
||||
|
||||
fn get_background(&self) -> Vec3 {
|
||||
Vec3::new(0.7, 0.8, 1.0)
|
||||
}
|
||||
|
||||
fn world(&self) -> Self::DemoT {
|
||||
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(500);
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
|
||||
|
||||
world.push(Arc::new(Sphere::new(
|
||||
Vec3::new(0.0, -1000.0, 0.0),
|
||||
1000.0,
|
||||
Lambertian::new(Checker::new(
|
||||
Solid::new(Vec3::new(0.2, 0.3, 0.1)),
|
||||
Solid::new(Vec3::new(0.9, 0.9, 0.9)),
|
||||
)),
|
||||
)));
|
||||
|
||||
let radius = 0.2;
|
||||
let l = Vec3::new(4.0, 0.2, 0.0);
|
||||
|
||||
for a in -10..10 {
|
||||
let a = a as f64;
|
||||
for b in -10..10 {
|
||||
let b = b as f64;
|
||||
let choose_material_probability = rng.gen::<f64>();
|
||||
let center = Vec3::new(a + 0.9 * rng.gen::<f64>(), 0.2, b + 0.9 * rng.gen::<f64>());
|
||||
|
||||
if (center - l).length() > 0.9 {
|
||||
if choose_material_probability < 0.8 {
|
||||
// diffuse material
|
||||
world.push(Arc::new(MovingSphere::new(
|
||||
center,
|
||||
center + Vec3::new(0.0, 0.5 * rng.gen::<f64>(), 0.0),
|
||||
0.0,
|
||||
1.0,
|
||||
radius,
|
||||
Lambertian::new(Solid::new(Vec3::new(
|
||||
rng.gen::<f64>() * rng.gen::<f64>(),
|
||||
rng.gen::<f64>() * rng.gen::<f64>(),
|
||||
rng.gen::<f64>() * rng.gen::<f64>(),
|
||||
))),
|
||||
)));
|
||||
} else if choose_material_probability < 0.95 {
|
||||
// metal material
|
||||
world.push(Arc::new(Sphere::new(
|
||||
center,
|
||||
radius,
|
||||
Metal::with_fuzz(
|
||||
Vec3::new(
|
||||
(1.0 + rng.gen::<f64>()) * 0.5,
|
||||
(1.0 + rng.gen::<f64>()) * 0.5,
|
||||
(1.0 + rng.gen::<f64>()) * 0.5,
|
||||
),
|
||||
0.5 * rng.gen::<f64>(),
|
||||
),
|
||||
)));
|
||||
} else {
|
||||
// glass material
|
||||
world.push(Arc::new(Sphere::new(center, radius, Dielectric::new(1.5))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
world.push(Arc::new(Sphere::new(
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
1.0,
|
||||
Dielectric::new(1.5),
|
||||
)));
|
||||
world.push(Arc::new(Sphere::new(
|
||||
Vec3::new(-4.0, 1.0, 0.0),
|
||||
1.0,
|
||||
Lambertian::new(Solid::new(Vec3::new(0.4, 0.2, 0.1))),
|
||||
)));
|
||||
world.push(Arc::new(Sphere::new(
|
||||
Vec3::new(4.0, 1.0, 0.0),
|
||||
1.0,
|
||||
Metal::with_fuzz(Vec3::new(0.7, 0.6, 0.5), 0.0),
|
||||
)));
|
||||
|
||||
BvhNode::new(&mut rng, &mut world, 0.0, 1.0)
|
||||
}
|
||||
|
||||
fn camera(&self, aspect_ratio: f64) -> Camera {
|
||||
let lookfrom = Vec3::new(13.0, 2.0, 3.0);
|
||||
let lookat = Vec3::new(0.0, 0.0, 0.0);
|
||||
let aperture = 0.1;
|
||||
let focus_distance = 10.0;
|
||||
Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
20.0,
|
||||
aspect_ratio,
|
||||
aperture,
|
||||
focus_distance,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
}
|
161
src/demos/cornell_box.rs
Normal file
161
src/demos/cornell_box.rs
Normal file
|
@ -0,0 +1,161 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use rand::{prelude::SmallRng, Rng, SeedableRng};
|
||||
|
||||
use crate::{
|
||||
demos::Demo,
|
||||
hitable::{
|
||||
hitable_list::HitableList,
|
||||
shapes::{Cuboid, MovingSphere, RectBuilder, Sphere},
|
||||
volume::ConstantMedium,
|
||||
Hitable,
|
||||
},
|
||||
materials::{Dielectric, DiffuseLight, Isotropic, Lambertian, MaterialBuilder, Metal},
|
||||
texture::{ImageTexture, PerlinNoise, Solid},
|
||||
types::Vec3,
|
||||
BvhNode, Camera,
|
||||
};
|
||||
|
||||
pub struct CornellBox {}
|
||||
|
||||
impl Demo for CornellBox {
|
||||
type DemoT = HitableList;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"cornell_box"
|
||||
}
|
||||
|
||||
fn world(&self) -> Self::DemoT {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
|
||||
|
||||
let mut ground_boxes = HitableList { list: Vec::new() };
|
||||
let ground = Lambertian::new(Solid::new(Vec3::new(0.48, 0.83, 0.53)));
|
||||
|
||||
for i in 0..20 {
|
||||
let i = i as f64;
|
||||
for j in 0..20 {
|
||||
let j = j as f64;
|
||||
|
||||
let w = 100.0;
|
||||
let x0 = -1000.0 + i * w;
|
||||
let z0 = -1000.0 + j * w;
|
||||
let y0 = 0.0;
|
||||
|
||||
let x1 = x0 + w;
|
||||
let y1 = rng.gen_range(1.0..=101.0);
|
||||
let z1 = z0 + w;
|
||||
|
||||
ground_boxes.push(Arc::new(Cuboid::new(
|
||||
Vec3::new(x0, y0, z0),
|
||||
Vec3::new(x1, y1, z1),
|
||||
ground.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let mut objects = HitableList { list: Vec::new() };
|
||||
objects.push(Arc::new(BvhNode::new(
|
||||
&mut rng,
|
||||
&mut ground_boxes.list,
|
||||
0.0,
|
||||
1.0,
|
||||
)));
|
||||
|
||||
let light = DiffuseLight::new(Solid::new(Vec3::splat(7.0)));
|
||||
objects.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(123.0..=423.0)
|
||||
.z(147.0..=412.0)
|
||||
.y(554.0)
|
||||
.material(light),
|
||||
));
|
||||
|
||||
let center1 = Vec3::new(400.0, 400.0, 200.0);
|
||||
let center2 = center1 + Vec3::new(30.0, 0.0, 0.0);
|
||||
objects.push(Arc::new(MovingSphere::new(
|
||||
center1,
|
||||
center2,
|
||||
0.0,
|
||||
1.0,
|
||||
50.0,
|
||||
Lambertian::new(Solid::new(Vec3::new(0.7, 0.3, 0.1))),
|
||||
)));
|
||||
|
||||
objects.push(Arc::new(Sphere::new(
|
||||
Vec3::new(260.0, 150.0, 45.0),
|
||||
50.0,
|
||||
Dielectric::new(1.5),
|
||||
)));
|
||||
|
||||
objects.push(Arc::new(Sphere::new(
|
||||
Vec3::new(0.0, 150.0, 145.0),
|
||||
50.0,
|
||||
Metal::with_fuzz(Vec3::new(0.8, 0.8, 0.9), 1.0),
|
||||
)));
|
||||
|
||||
let boundary = Sphere::new(Vec3::new(360.0, 150.0, 145.0), 70.0, Dielectric::new(1.5));
|
||||
objects.push(Arc::new(boundary.clone()));
|
||||
objects.push(Arc::new(ConstantMedium::new(
|
||||
boundary,
|
||||
Isotropic::new(Solid::new(Vec3::new(0.2, 0.4, 0.9))),
|
||||
0.2,
|
||||
)));
|
||||
|
||||
objects.push(Arc::new(ConstantMedium::new(
|
||||
Sphere::new(Vec3::splat(0.0), 5000.0, Dielectric::new(1.5)),
|
||||
Isotropic::new(Solid::new(Vec3::splat(1.0))),
|
||||
0.0001,
|
||||
)));
|
||||
|
||||
let earthmap = ImageTexture::from_filename("assets/earthmap.jpg")
|
||||
.expect("error in reading assets/earthmap.jpg");
|
||||
objects.push(Arc::new(Sphere::new(
|
||||
Vec3::new(400.0, 200.0, 400.0),
|
||||
100.0,
|
||||
Lambertian::new(earthmap),
|
||||
)));
|
||||
|
||||
objects.push(Arc::new(Sphere::new(
|
||||
Vec3::new(220.0, 280.0, 300.0),
|
||||
80.0,
|
||||
Lambertian::new(PerlinNoise::with_scale(&mut rng, 0.1)),
|
||||
)));
|
||||
|
||||
let mut boxes2 = HitableList { list: Vec::new() };
|
||||
let white = Lambertian::new(Solid::new(Vec3::splat(0.73)));
|
||||
for _ in 0..1000 {
|
||||
boxes2.push(Arc::new(Sphere::new(
|
||||
Vec3::random_in_range(&mut rng, 0.0..=165.0),
|
||||
10.0,
|
||||
white.clone(),
|
||||
)));
|
||||
}
|
||||
|
||||
objects.push(Arc::new(
|
||||
BvhNode::new(&mut rng, &mut boxes2.list, 0.0, 1.0)
|
||||
.rotate_y(15.0)
|
||||
.translate(Vec3::new(-100.0, 270.0, 395.0)),
|
||||
));
|
||||
|
||||
objects
|
||||
}
|
||||
|
||||
fn camera(&self, aspect_ratio: f64) -> Camera {
|
||||
let lookfrom = Vec3::new(478.0, 278.0, -600.0);
|
||||
let lookat = Vec3::new(278.0, 278.0, 0.0);
|
||||
let aperture = 0.1;
|
||||
let focus_distance = 40.0;
|
||||
Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
40.0,
|
||||
aspect_ratio,
|
||||
aperture,
|
||||
focus_distance,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
}
|
123
src/demos/cornell_smoke_and_fog.rs
Normal file
123
src/demos/cornell_smoke_and_fog.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use rand::{prelude::SmallRng, SeedableRng};
|
||||
|
||||
use crate::{
|
||||
demos::{Demo, ParallelHit},
|
||||
hitable::{
|
||||
shapes::{Cuboid, RectBuilder},
|
||||
volume::ConstantMedium,
|
||||
Hitable,
|
||||
},
|
||||
materials::{DiffuseLight, Isotropic, Lambertian, MaterialBuilder},
|
||||
texture::Solid,
|
||||
types::Vec3,
|
||||
BvhNode, Camera,
|
||||
};
|
||||
|
||||
pub struct CornellSmokeAndFog {}
|
||||
|
||||
impl Demo for CornellSmokeAndFog {
|
||||
type DemoT = BvhNode<Arc<dyn ParallelHit>>;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"cornell_smoke_and_fog"
|
||||
}
|
||||
|
||||
fn world(&self) -> Self::DemoT {
|
||||
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(8);
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
|
||||
|
||||
let red = Lambertian::new(Solid::new(Vec3::new(0.65, 0.05, 0.05)));
|
||||
let white = Lambertian::new(Solid::new(Vec3::splat(0.73)));
|
||||
let green = Lambertian::new(Solid::new(Vec3::new(0.12, 0.45, 0.15)));
|
||||
let light = DiffuseLight::new(Solid::new(Vec3::splat(7.0)));
|
||||
|
||||
world.push(Arc::new(
|
||||
RectBuilder
|
||||
.y(0.0..=555.0)
|
||||
.z(0.0..=555.0)
|
||||
.x(555.0)
|
||||
.material(green),
|
||||
));
|
||||
world.push(Arc::new(
|
||||
RectBuilder
|
||||
.y(0.0..=555.0)
|
||||
.z(0.0..=555.0)
|
||||
.x(0.0)
|
||||
.material(red),
|
||||
));
|
||||
|
||||
world.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(113.0..=443.0)
|
||||
.z(127.0..=432.0)
|
||||
.y(554.0)
|
||||
.material(light),
|
||||
));
|
||||
world.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(0.0..=555.0)
|
||||
.z(0.0..=555.0)
|
||||
.y(0.0)
|
||||
.material(white.clone()),
|
||||
));
|
||||
|
||||
world.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(0.0..=555.0)
|
||||
.z(0.0..=555.0)
|
||||
.y(555.0)
|
||||
.material(white.clone()),
|
||||
));
|
||||
world.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(0.0..=555.0)
|
||||
.y(0.0..=555.0)
|
||||
.z(555.0)
|
||||
.material(white.clone()),
|
||||
));
|
||||
|
||||
// Add the two boxes
|
||||
world.push(Arc::new(ConstantMedium::new(
|
||||
Cuboid::new(
|
||||
Vec3::splat(0.0),
|
||||
Vec3::new(165.0, 330.0, 165.0),
|
||||
white.clone(),
|
||||
)
|
||||
.rotate_y(15.0)
|
||||
.translate(Vec3::new(265.0, 0.0, 295.0)),
|
||||
Isotropic::new(Solid::new(Vec3::splat(0.0))),
|
||||
0.01,
|
||||
)));
|
||||
world.push(Arc::new(ConstantMedium::new(
|
||||
Cuboid::new(Vec3::splat(0.0), Vec3::splat(165.0), white)
|
||||
.rotate_y(-18.0)
|
||||
.translate(Vec3::new(130.0, 0.0, 65.0)),
|
||||
Isotropic::new(Solid::new(Vec3::splat(1.0))),
|
||||
0.01,
|
||||
)));
|
||||
|
||||
BvhNode::new(&mut rng, &mut world, 0.0, 1.0)
|
||||
}
|
||||
|
||||
fn camera(&self, aspect_ratio: f64) -> Camera {
|
||||
let lookfrom = Vec3::new(278.0, 278.0, -800.0);
|
||||
let lookat = Vec3::new(278.0, 278.0, 0.0);
|
||||
let aperture = 0.1;
|
||||
let focus_distance = 40.0;
|
||||
Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
40.0,
|
||||
aspect_ratio,
|
||||
aperture,
|
||||
focus_distance,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
}
|
64
src/demos/image_texture.rs
Normal file
64
src/demos/image_texture.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use rand::{prelude::SmallRng, SeedableRng};
|
||||
|
||||
use crate::{
|
||||
demos::{Demo, ParallelHit},
|
||||
hitable::shapes::Sphere,
|
||||
materials::Lambertian,
|
||||
texture::ImageTexture,
|
||||
types::Vec3,
|
||||
BvhNode, Camera,
|
||||
};
|
||||
|
||||
pub struct ImageTextureDemo {}
|
||||
|
||||
impl Demo for ImageTextureDemo {
|
||||
type DemoT = BvhNode<Arc<dyn ParallelHit>>;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"image_texture"
|
||||
}
|
||||
|
||||
fn get_background(&self) -> Vec3 {
|
||||
Vec3::new(0.7, 0.8, 1.0)
|
||||
}
|
||||
|
||||
fn world(&self) -> Self::DemoT {
|
||||
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(1);
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
|
||||
|
||||
let earth_texture = match ImageTexture::from_filename("assets/earthmap.jpg") {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("error in creating image texture: {}", e),
|
||||
};
|
||||
|
||||
world.push(Arc::new(Sphere::new(
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
2.0,
|
||||
Lambertian::new(earth_texture),
|
||||
)));
|
||||
|
||||
BvhNode::new(&mut rng, &mut world, 0.0, 1.0)
|
||||
}
|
||||
|
||||
fn camera(&self, aspect_ratio: f64) -> Camera {
|
||||
let lookfrom = Vec3::new(13.0, 2.0, 3.0);
|
||||
let lookat = Vec3::new(0.0, 0.0, 0.0);
|
||||
let aperture = 0.1;
|
||||
let focus_distance = 12.0;
|
||||
Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
20.0,
|
||||
aspect_ratio,
|
||||
aperture,
|
||||
focus_distance,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
}
|
117
src/demos/instances.rs
Normal file
117
src/demos/instances.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use rand::{prelude::SmallRng, SeedableRng};
|
||||
|
||||
use crate::{
|
||||
demos::{Demo, ParallelHit},
|
||||
hitable::{
|
||||
shapes::{Cuboid, RectBuilder},
|
||||
Hitable,
|
||||
},
|
||||
materials::{DiffuseLight, Lambertian, MaterialBuilder},
|
||||
texture::Solid,
|
||||
types::Vec3,
|
||||
BvhNode, Camera,
|
||||
};
|
||||
|
||||
pub struct Instances {}
|
||||
|
||||
impl Demo for Instances {
|
||||
type DemoT = BvhNode<Arc<dyn ParallelHit>>;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"instances"
|
||||
}
|
||||
|
||||
fn world(&self) -> Self::DemoT {
|
||||
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(8);
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
|
||||
|
||||
let red = Lambertian::new(Solid::new(Vec3::new(0.65, 0.05, 0.05)));
|
||||
let white = Lambertian::new(Solid::new(Vec3::splat(0.73)));
|
||||
let green = Lambertian::new(Solid::new(Vec3::new(0.12, 0.45, 0.15)));
|
||||
let light = DiffuseLight::new(Solid::new(Vec3::splat(15.0)));
|
||||
|
||||
world.push(Arc::new(
|
||||
RectBuilder
|
||||
.y(0.0..=555.0)
|
||||
.z(0.0..=555.0)
|
||||
.x(555.0)
|
||||
.material(green),
|
||||
));
|
||||
world.push(Arc::new(
|
||||
RectBuilder
|
||||
.y(0.0..=555.0)
|
||||
.z(0.0..=555.0)
|
||||
.x(0.0)
|
||||
.material(red),
|
||||
));
|
||||
world.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(213.0..=343.0)
|
||||
.z(227.0..=332.0)
|
||||
.y(554.0)
|
||||
.material(light),
|
||||
));
|
||||
|
||||
world.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(0.0..=555.0)
|
||||
.z(0.0..=555.0)
|
||||
.y(0.0)
|
||||
.material(white.clone()),
|
||||
));
|
||||
world.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(0.0..=555.0)
|
||||
.z(0.0..=555.0)
|
||||
.y(555.0)
|
||||
.material(white.clone()),
|
||||
));
|
||||
world.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(0.0..=555.0)
|
||||
.y(0.0..=555.0)
|
||||
.z(555.0)
|
||||
.material(white.clone()),
|
||||
));
|
||||
|
||||
// Add the two boxes
|
||||
world.push(Arc::new(
|
||||
Cuboid::new(
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(165.0, 330.0, 165.0),
|
||||
white.clone(),
|
||||
)
|
||||
.rotate_y(15.0)
|
||||
.translate(Vec3::new(265.0, 0.0, 295.0)),
|
||||
));
|
||||
world.push(Arc::new(
|
||||
Cuboid::new(Vec3::new(0.0, 0.0, 0.0), Vec3::splat(165.0), white)
|
||||
.rotate_y(-18.0)
|
||||
.translate(Vec3::new(130.0, 0.0, 65.0)),
|
||||
));
|
||||
|
||||
BvhNode::new(&mut rng, &mut world, 0.0, 1.0)
|
||||
}
|
||||
|
||||
fn camera(&self, aspect_ratio: f64) -> Camera {
|
||||
let lookfrom = Vec3::new(278.0, 278.0, -800.0);
|
||||
let lookat = Vec3::new(278.0, 278.0, 0.0);
|
||||
let aperture = 0.1;
|
||||
let focus_distance = 40.0;
|
||||
Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
40.0,
|
||||
aspect_ratio,
|
||||
aperture,
|
||||
focus_distance,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
}
|
234
src/demos/mod.rs
Normal file
234
src/demos/mod.rs
Normal file
|
@ -0,0 +1,234 @@
|
|||
use crate::{
|
||||
hitable::{hitable_list::HitableList, BvhNode, Hitable},
|
||||
types::{Color, Vec3},
|
||||
Camera, HORIZONTAL_PARTITION, VERTICAL_PARTITION,
|
||||
};
|
||||
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||
use rayon::prelude::*;
|
||||
use std::{
|
||||
fmt::{Display, Formatter, Result as FmtResult},
|
||||
fs::File,
|
||||
io::Write,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
mod checkered_motion_blur;
|
||||
mod cornell_box;
|
||||
mod cornell_smoke_and_fog;
|
||||
mod image_texture;
|
||||
mod instances;
|
||||
mod perlin_noise_ball;
|
||||
mod simple_light;
|
||||
mod two_spheres;
|
||||
|
||||
pub use checkered_motion_blur::CheckeredMotionBlur;
|
||||
pub use cornell_box::CornellBox;
|
||||
pub use cornell_smoke_and_fog::CornellSmokeAndFog;
|
||||
pub use image_texture::ImageTextureDemo;
|
||||
pub use instances::Instances;
|
||||
pub use perlin_noise_ball::PerlinNoiseBall;
|
||||
pub use simple_light::SimpleLight;
|
||||
pub use two_spheres::TwoSpheres;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Chunk {
|
||||
num: usize,
|
||||
x: usize,
|
||||
y: usize,
|
||||
nx: usize,
|
||||
ny: usize,
|
||||
start_x: usize,
|
||||
start_y: usize,
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Display for Chunk {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
write!(
|
||||
f,
|
||||
"Chunk #{}: Start X = {} Start Y = {} Size X = {} Size = {}",
|
||||
self.num, self.start_x, self.start_y, self.nx, self.ny
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ParallelHit: Hitable + Send + Sync {}
|
||||
impl<T: Hitable + Send + Sync> ParallelHit for T {}
|
||||
|
||||
pub trait Demo: Send + Sync {
|
||||
type DemoT: Hitable + Send + Sync;
|
||||
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
fn world(&self) -> Self::DemoT;
|
||||
|
||||
fn camera(&self, aspect_ratio: f64) -> Camera;
|
||||
|
||||
fn get_background(&self) -> Vec3 {
|
||||
Vec3::new(0.0, 0.0, 0.0)
|
||||
}
|
||||
|
||||
fn render_chunk(&self, chunk: &mut Chunk, camera: &Camera, world: &Self::DemoT, samples: u16) {
|
||||
let &mut Chunk {
|
||||
num: _,
|
||||
x,
|
||||
y,
|
||||
nx,
|
||||
ny,
|
||||
start_x,
|
||||
start_y,
|
||||
ref mut buffer,
|
||||
} = chunk;
|
||||
let mut offset = 0;
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
|
||||
let background = self.get_background();
|
||||
|
||||
assert!(buffer.len() >= nx * ny * 4);
|
||||
|
||||
(start_y..start_y + ny).for_each(|j| {
|
||||
(start_x..start_x + nx).for_each(|i| {
|
||||
let mut color = Vec3::new(0.0, 0.0, 0.0);
|
||||
for _s in 0..samples {
|
||||
let u = (i as f64 + rng.gen::<f64>()) / x as f64;
|
||||
let v = (j as f64 + rng.gen::<f64>()) / y as f64;
|
||||
|
||||
let ray = camera.get_ray(u, v, &mut rng);
|
||||
color += ray.color(world, &mut rng, &background, 0);
|
||||
}
|
||||
|
||||
color /= samples as f64;
|
||||
self.update_rgb(buffer, color, offset);
|
||||
offset += 4;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn render(&self, buf: &mut Vec<u8>, x: usize, y: usize, samples: u16) {
|
||||
let world = self.world();
|
||||
let delta_x = x / VERTICAL_PARTITION;
|
||||
let delta_y = y / HORIZONTAL_PARTITION;
|
||||
let remx = x % VERTICAL_PARTITION;
|
||||
let remy = y % HORIZONTAL_PARTITION;
|
||||
|
||||
// There can be tiny error here if the canvas height/width is not perfectly divisible
|
||||
// by vertical/horizontal partitions in the chunks around the edges
|
||||
// but umm, i'll just ignore those for now.
|
||||
let camera = self.camera(delta_x as f64 / delta_y as f64);
|
||||
let buf = Arc::new(Mutex::new(buf));
|
||||
|
||||
(0..VERTICAL_PARTITION).into_par_iter().for_each(|j| {
|
||||
let buf = buf.clone();
|
||||
(0..HORIZONTAL_PARTITION).into_par_iter().for_each(|i| {
|
||||
let mut nx = delta_x;
|
||||
let mut ny = delta_y;
|
||||
let start_y = j * ny;
|
||||
let start_x = i * nx;
|
||||
|
||||
match (i + 1, j + 1) {
|
||||
(HORIZONTAL_PARTITION, VERTICAL_PARTITION) => {
|
||||
nx += remx;
|
||||
ny += remy;
|
||||
}
|
||||
(HORIZONTAL_PARTITION, _) => nx += remx,
|
||||
(_, VERTICAL_PARTITION) => ny += remy,
|
||||
_ => (),
|
||||
};
|
||||
|
||||
let mut chunk = Chunk {
|
||||
num: j * HORIZONTAL_PARTITION + i,
|
||||
x,
|
||||
y,
|
||||
nx,
|
||||
ny,
|
||||
start_x,
|
||||
start_y,
|
||||
buffer: vec![0; nx * ny * 4],
|
||||
};
|
||||
|
||||
println!("{}", chunk);
|
||||
self.render_chunk(&mut chunk, &camera, &world, samples);
|
||||
|
||||
let mut buf = buf.lock().unwrap();
|
||||
let mut temp_offset = 0;
|
||||
for j in start_y..start_y + ny {
|
||||
let real_offset = ((y - j - 1) * x + start_x) * 4;
|
||||
|
||||
buf[real_offset..real_offset + nx * 4]
|
||||
.copy_from_slice(&chunk.buffer[temp_offset..temp_offset + nx * 4]);
|
||||
|
||||
temp_offset += nx * 4;
|
||||
}
|
||||
|
||||
println!("Rendered {}", chunk);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_rgb(&self, buffer: &mut [u8], color: Vec3, offset: usize) {
|
||||
let color: Color = color.into();
|
||||
|
||||
if let Some(pos) = buffer.get_mut(offset) {
|
||||
*pos = color.0;
|
||||
}
|
||||
if let Some(pos) = buffer.get_mut(offset + 1) {
|
||||
*pos = color.1
|
||||
}
|
||||
if let Some(pos) = buffer.get_mut(offset + 2) {
|
||||
*pos = color.2;
|
||||
}
|
||||
}
|
||||
|
||||
fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize, samples: u16) {
|
||||
let header = format!("P3\n{} {}\n255\n", width, height);
|
||||
|
||||
let mut file = match File::create(&format!(
|
||||
"{}-{}x{}_{}.ppm",
|
||||
self.name(),
|
||||
width,
|
||||
height,
|
||||
samples,
|
||||
)) {
|
||||
Ok(file) => file,
|
||||
Err(e) => panic!("couldn't create {}: {}", self.name(), e),
|
||||
};
|
||||
file.write_all(header.as_bytes())
|
||||
.expect("error in writing file header");
|
||||
|
||||
for i in buf.chunks(4) {
|
||||
match file.write_all(format!("{} {} {}\n", i[0], i[1], i[2]).as_bytes()) {
|
||||
Ok(_) => (),
|
||||
Err(e) => panic!("couldn't write to {}: {}", self.name(), e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DemoWrapper {
|
||||
HitableList(Box<dyn Demo<DemoT = HitableList>>),
|
||||
BVHNode(Box<dyn Demo<DemoT = BvhNode<Arc<dyn ParallelHit>>>>),
|
||||
}
|
||||
|
||||
impl DemoWrapper {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
DemoWrapper::HitableList(v) => v.name(),
|
||||
DemoWrapper::BVHNode(v) => v.name(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize, samples: u16) {
|
||||
match self {
|
||||
DemoWrapper::HitableList(v) => v.save_as_ppm(buf, width, height, samples),
|
||||
DemoWrapper::BVHNode(v) => v.save_as_ppm(buf, width, height, samples),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, buf: &mut Vec<u8>, x: usize, y: usize, samples: u16) {
|
||||
match self {
|
||||
DemoWrapper::HitableList(v) => v.render(buf, x, y, samples),
|
||||
DemoWrapper::BVHNode(v) => v.render(buf, x, y, samples),
|
||||
}
|
||||
}
|
||||
}
|
65
src/demos/perlin_noise_ball.rs
Normal file
65
src/demos/perlin_noise_ball.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use rand::{prelude::SmallRng, SeedableRng};
|
||||
|
||||
use crate::{
|
||||
demos::{Demo, ParallelHit},
|
||||
hitable::{shapes::Sphere, BvhNode},
|
||||
materials::Lambertian,
|
||||
texture::PerlinNoise,
|
||||
types::Vec3,
|
||||
Camera,
|
||||
};
|
||||
|
||||
pub struct PerlinNoiseBall {}
|
||||
|
||||
impl Demo for PerlinNoiseBall {
|
||||
type DemoT = BvhNode<Arc<dyn ParallelHit>>;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"perlin_noise"
|
||||
}
|
||||
|
||||
fn get_background(&self) -> Vec3 {
|
||||
Vec3::new(0.7, 0.8, 1.0)
|
||||
}
|
||||
|
||||
fn world(&self) -> Self::DemoT {
|
||||
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(2);
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
|
||||
|
||||
world.push(Arc::new(Sphere::new(
|
||||
Vec3::new(0.0, -1000.0, 0.0),
|
||||
1000.0,
|
||||
Lambertian::new(PerlinNoise::with_scale(&mut rng, 4.0)),
|
||||
)));
|
||||
|
||||
world.push(Arc::new(Sphere::new(
|
||||
Vec3::new(0.0, 2.0, 0.0),
|
||||
2.0,
|
||||
Lambertian::new(PerlinNoise::with_scale(&mut rng, 4.0)),
|
||||
)));
|
||||
|
||||
BvhNode::new(&mut rng, &mut world, 0.0, 1.0)
|
||||
}
|
||||
|
||||
fn camera(&self, aspect_ratio: f64) -> Camera {
|
||||
let lookfrom = Vec3::new(13.0, 2.0, 3.0);
|
||||
let lookat = Vec3::new(0.0, 0.0, 0.0);
|
||||
let aperture = 0.1;
|
||||
let focus_distance = 10.0;
|
||||
Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
20.0,
|
||||
aspect_ratio,
|
||||
aperture,
|
||||
focus_distance,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
}
|
80
src/demos/simple_light.rs
Normal file
80
src/demos/simple_light.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use rand::{prelude::SmallRng, SeedableRng};
|
||||
|
||||
use crate::{
|
||||
demos::{Demo, ParallelHit},
|
||||
hitable::{
|
||||
shapes::{RectBuilder, Sphere},
|
||||
BvhNode,
|
||||
},
|
||||
materials::{DiffuseLight, Lambertian, MaterialBuilder},
|
||||
texture::{PerlinNoise, Solid},
|
||||
types::Vec3,
|
||||
Camera,
|
||||
};
|
||||
|
||||
pub struct SimpleLight {}
|
||||
|
||||
impl Demo for SimpleLight {
|
||||
type DemoT = BvhNode<Arc<dyn ParallelHit>>;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"simple_light"
|
||||
}
|
||||
|
||||
fn world(&self) -> Self::DemoT {
|
||||
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(5);
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
|
||||
|
||||
world.push(Arc::new(Sphere::new(
|
||||
Vec3::new(0.0, -1000.0, 0.0),
|
||||
1000.0,
|
||||
Lambertian::new(PerlinNoise::with_scale(&mut rng, 4.0)),
|
||||
)));
|
||||
world.push(Arc::new(Sphere::new(
|
||||
Vec3::new(0.0, 2.0, 0.0),
|
||||
2.0,
|
||||
Lambertian::new(PerlinNoise::with_scale(&mut rng, 4.0)),
|
||||
)));
|
||||
|
||||
world.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(3.0..=5.0)
|
||||
.y(1.0..=3.0)
|
||||
.z(-2.0)
|
||||
.material(DiffuseLight::new(Solid::new(Vec3::new(4.0, 4.0, 4.0)))),
|
||||
));
|
||||
world.push(Arc::new(Sphere::new(
|
||||
Vec3::new(0.0, 7.0, 0.0),
|
||||
2.0,
|
||||
DiffuseLight::new(Solid::new(Vec3::new(4.0, 4.0, 4.0))),
|
||||
)));
|
||||
world.push(Arc::new(Sphere::new(
|
||||
Vec3::new(-40.0, 2.0, 5.0),
|
||||
1.0,
|
||||
DiffuseLight::new(Solid::new(Vec3::new(4.0, 4.0, 4.0))),
|
||||
)));
|
||||
|
||||
BvhNode::new(&mut rng, &mut world, 0.0, 1.0)
|
||||
}
|
||||
|
||||
fn camera(&self, aspect_ratio: f64) -> crate::Camera {
|
||||
let lookfrom = Vec3::new(26.0, 3.0, 6.0);
|
||||
let lookat = Vec3::new(0.0, 2.0, 0.0);
|
||||
let aperture = 0.0;
|
||||
let focus_distance = 10.0;
|
||||
Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
20.0,
|
||||
aspect_ratio,
|
||||
aperture,
|
||||
focus_distance,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
}
|
71
src/demos/two_spheres.rs
Normal file
71
src/demos/two_spheres.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use rand::{prelude::SmallRng, SeedableRng};
|
||||
|
||||
use crate::{
|
||||
demos::{Demo, ParallelHit},
|
||||
hitable::{shapes::Sphere, BvhNode},
|
||||
materials::Lambertian,
|
||||
texture::{Checker, Solid},
|
||||
types::Vec3,
|
||||
Camera,
|
||||
};
|
||||
|
||||
pub struct TwoSpheres {}
|
||||
|
||||
impl Demo for TwoSpheres {
|
||||
type DemoT = BvhNode<Arc<dyn ParallelHit>>;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"two_checkered_sphere"
|
||||
}
|
||||
|
||||
fn get_background(&self) -> Vec3 {
|
||||
Vec3::new(0.7, 0.8, 1.0)
|
||||
}
|
||||
|
||||
fn world(&self) -> Self::DemoT {
|
||||
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(2);
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
|
||||
|
||||
world.push(Arc::new(Sphere::new(
|
||||
Vec3::new(0.0, -10.0, 0.0),
|
||||
10.0,
|
||||
Lambertian::new(Checker::new(
|
||||
Solid::new(Vec3::new(0.2, 0.3, 0.1)),
|
||||
Solid::new(Vec3::new(0.9, 0.9, 0.9)),
|
||||
)),
|
||||
)));
|
||||
|
||||
world.push(Arc::new(Sphere::new(
|
||||
Vec3::new(0.0, 10.0, 0.0),
|
||||
10.0,
|
||||
Lambertian::new(Checker::new(
|
||||
Solid::new(Vec3::new(0.2, 0.3, 0.1)),
|
||||
Solid::new(Vec3::new(0.9, 0.9, 0.9)),
|
||||
)),
|
||||
)));
|
||||
|
||||
BvhNode::new(&mut rng, &mut world, 0.0, 1.0)
|
||||
}
|
||||
|
||||
fn camera(&self, aspect_ratio: f64) -> Camera {
|
||||
let lookfrom = Vec3::new(13.0, 2.0, 3.0);
|
||||
let lookat = Vec3::new(0.0, 0.0, 0.0);
|
||||
let aperture = 0.1;
|
||||
let focus_distance = 10.0;
|
||||
Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
20.0,
|
||||
aspect_ratio,
|
||||
aperture,
|
||||
focus_distance,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
}
|
139
src/hitable/bvh.rs
Normal file
139
src/hitable/bvh.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use rand::{prelude::SliceRandom, Rng};
|
||||
|
||||
use crate::{
|
||||
hitable::{HitRecord, Hitable},
|
||||
types::Ray,
|
||||
Aabb,
|
||||
};
|
||||
|
||||
pub struct BvhNode<T: Hitable> {
|
||||
bounding_box: Aabb,
|
||||
left: HitNode<T>,
|
||||
right: HitNode<T>,
|
||||
}
|
||||
|
||||
impl<T: Hitable + Clone> BvhNode<T> {
|
||||
pub fn new<R: Rng + ?Sized>(rng: &mut R, objects: &mut [T], t0: f64, t1: f64) -> Self {
|
||||
let comparator = [
|
||||
Self::box_x_compare,
|
||||
Self::box_y_compare,
|
||||
Self::box_z_compare,
|
||||
]
|
||||
.choose(rng)
|
||||
.unwrap();
|
||||
|
||||
let (left, right) = match objects.len() {
|
||||
1 => (
|
||||
HitNode::Direct(objects[0].clone()),
|
||||
HitNode::Direct(objects[0].clone()),
|
||||
),
|
||||
2 => match comparator(&objects[0], &objects[1]) {
|
||||
Ordering::Greater => (
|
||||
HitNode::Direct(objects[1].clone()),
|
||||
HitNode::Direct(objects[0].clone()),
|
||||
),
|
||||
_ => (
|
||||
HitNode::Direct(objects[0].clone()),
|
||||
HitNode::Direct(objects[1].clone()),
|
||||
),
|
||||
},
|
||||
|
||||
n => {
|
||||
objects.sort_by(comparator);
|
||||
let (l, r) = objects.split_at_mut(n / 2);
|
||||
(
|
||||
HitNode::Bvh(Box::new(BvhNode::new(rng, l, t0, t1))),
|
||||
HitNode::Bvh(Box::new(BvhNode::new(rng, r, t0, t1))),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let left_box = left
|
||||
.bounding_box(t0, t1)
|
||||
.expect("missing bounding box for left BVH Node");
|
||||
let right_box = right
|
||||
.bounding_box(t0, t1)
|
||||
.expect("missing bounding box for right BVH Node");
|
||||
|
||||
Self {
|
||||
left,
|
||||
right,
|
||||
bounding_box: Aabb::surrounding_box(left_box, right_box),
|
||||
}
|
||||
}
|
||||
|
||||
fn box_x_compare(obj1: &T, obj2: &T) -> Ordering {
|
||||
if let (Some(bbox_a), Some(bbox_b)) =
|
||||
(obj1.bounding_box(0.0, 0.0), obj2.bounding_box(0.0, 0.0))
|
||||
{
|
||||
return bbox_a.min.x().partial_cmp(&bbox_b.min.x()).unwrap();
|
||||
}
|
||||
|
||||
panic!("No bounding box for this BVH Node!!")
|
||||
}
|
||||
|
||||
fn box_y_compare(obj1: &T, obj2: &T) -> Ordering {
|
||||
if let (Some(bbox_a), Some(bbox_b)) =
|
||||
(obj1.bounding_box(0.0, 0.0), obj2.bounding_box(0.0, 0.0))
|
||||
{
|
||||
return bbox_a.min.y().partial_cmp(&bbox_b.min.y()).unwrap();
|
||||
}
|
||||
|
||||
panic!("No bounding box for this BVH Node!!")
|
||||
}
|
||||
|
||||
fn box_z_compare(obj1: &T, obj2: &T) -> Ordering {
|
||||
if let (Some(bbox_a), Some(bbox_b)) =
|
||||
(obj1.bounding_box(0.0, 0.0), obj2.bounding_box(0.0, 0.0))
|
||||
{
|
||||
return bbox_a.min.z().partial_cmp(&bbox_b.min.z()).unwrap();
|
||||
}
|
||||
|
||||
panic!("No bounding box for this BVH Node!!")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hitable> Hitable for BvhNode<T> {
|
||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||
if !self.bounding_box.hit(ray, t_min, t_max) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let hbox_left = self.left.hit(ray, t_min, t_max);
|
||||
|
||||
let hbox_right = if let Some(ref hleft) = hbox_left {
|
||||
self.right.hit(ray, t_min, hleft.t)
|
||||
} else {
|
||||
self.right.hit(ray, t_min, t_max)
|
||||
};
|
||||
|
||||
hbox_right.or(hbox_left)
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _t_min: f64, _t_max: f64) -> Option<Aabb> {
|
||||
Some(self.bounding_box)
|
||||
}
|
||||
}
|
||||
|
||||
enum HitNode<T: Hitable> {
|
||||
Bvh(Box<BvhNode<T>>),
|
||||
Direct(T),
|
||||
}
|
||||
|
||||
impl<T: Hitable> HitNode<T> {
|
||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||
match self {
|
||||
HitNode::Bvh(node) => node.hit(ray, t_min, t_max),
|
||||
HitNode::Direct(node) => node.hit(ray, t_min, t_max),
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_box(&self, t0: f64, t1: f64) -> Option<Aabb> {
|
||||
match self {
|
||||
HitNode::Bvh(node) => node.bounding_box(t0, t1),
|
||||
HitNode::Direct(node) => node.bounding_box(t0, t1),
|
||||
}
|
||||
}
|
||||
}
|
54
src/hitable/hitable_list.rs
Normal file
54
src/hitable/hitable_list.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
demos::ParallelHit,
|
||||
hitable::{HitRecord, Hitable},
|
||||
types::Ray,
|
||||
Aabb,
|
||||
};
|
||||
|
||||
pub struct HitableList {
|
||||
pub list: Vec<Arc<dyn ParallelHit>>,
|
||||
}
|
||||
|
||||
impl Hitable for HitableList {
|
||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||
let mut closest_so_far = t_max;
|
||||
let mut hit_rec: Option<HitRecord> = None;
|
||||
for obj in &self.list {
|
||||
if let Some(l_hit_rec) = obj.hit(ray, t_min, closest_so_far) {
|
||||
closest_so_far = l_hit_rec.t;
|
||||
hit_rec = Some(l_hit_rec);
|
||||
}
|
||||
}
|
||||
hit_rec
|
||||
}
|
||||
|
||||
fn bounding_box(&self, t0: f64, t1: f64) -> Option<Aabb> {
|
||||
if self.list.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut output_box = None;
|
||||
|
||||
for obj in self.list.iter() {
|
||||
if let Some(bbox) = obj.bounding_box(t0, t1) {
|
||||
if let Some(ref mut opbox) = output_box {
|
||||
*opbox = Aabb::surrounding_box(*opbox, bbox);
|
||||
} else {
|
||||
output_box = Some(bbox);
|
||||
}
|
||||
} else {
|
||||
return output_box;
|
||||
}
|
||||
}
|
||||
|
||||
output_box
|
||||
}
|
||||
}
|
||||
|
||||
impl HitableList {
|
||||
pub fn push(&mut self, obj: Arc<dyn ParallelHit>) {
|
||||
self.list.push(obj);
|
||||
}
|
||||
}
|
117
src/hitable/mod.rs
Normal file
117
src/hitable/mod.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
pub mod bvh;
|
||||
pub mod hitable_list;
|
||||
mod rotate;
|
||||
pub mod shapes;
|
||||
mod translate;
|
||||
pub mod volume;
|
||||
|
||||
pub use bvh::*;
|
||||
pub use translate::*;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
hitable::rotate::Rotate,
|
||||
types::{Ray, Vec3},
|
||||
Aabb, Material, X, Y, Z,
|
||||
};
|
||||
|
||||
pub struct HitRecord<'a> {
|
||||
/// Rays are represented by A + t * B
|
||||
/// where A is the source point and B destination point
|
||||
/// by adjusting t we can move forward/back on the ray
|
||||
///
|
||||
/// t is the point at which a ray intersected another object.
|
||||
/// As in, If we put this value of t in A + t * B equation, We'll get the exact
|
||||
/// point at which a ray intersects some other object
|
||||
pub t: f64,
|
||||
/// Ray object otherwise is represented by the Source/Destination points
|
||||
/// p is what we get when we perform the operation, A + t * B
|
||||
/// i.e. A vector from Ray source to the point t
|
||||
pub p: Vec3,
|
||||
|
||||
/// unit outward facing normal
|
||||
pub normal: Vec3,
|
||||
|
||||
/// material if any of the surface
|
||||
pub material: &'a dyn Material,
|
||||
|
||||
/// texture coordinates for an object
|
||||
pub u: f64,
|
||||
pub v: f64,
|
||||
|
||||
pub front_face: bool,
|
||||
}
|
||||
|
||||
impl<'a> HitRecord<'a> {
|
||||
pub fn new(
|
||||
t: f64,
|
||||
p: Vec3,
|
||||
normal: Vec3,
|
||||
material: &'a dyn Material,
|
||||
(u, v): (f64, f64),
|
||||
) -> Self {
|
||||
Self {
|
||||
t,
|
||||
p,
|
||||
normal,
|
||||
material,
|
||||
u,
|
||||
v,
|
||||
front_face: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_face_normal(&mut self, ray: &Ray) {
|
||||
self.front_face = ray.direction.dot(&self.normal) < 0.0;
|
||||
|
||||
self.normal = if self.front_face {
|
||||
self.normal
|
||||
} else {
|
||||
-self.normal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Hitable {
|
||||
fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option<HitRecord>;
|
||||
|
||||
fn bounding_box(&self, _t0: f64, _t1: f64) -> Option<Aabb>;
|
||||
|
||||
fn translate(self, offset: impl Into<Vec3>) -> Translate<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Translate::new(self, offset.into())
|
||||
}
|
||||
|
||||
fn rotate_x(self, angle: f64) -> Rotate<X, Y, Z, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Rotate::new(self, angle)
|
||||
}
|
||||
|
||||
fn rotate_y(self, angle: f64) -> Rotate<Y, X, Z, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Rotate::new(self, angle)
|
||||
}
|
||||
|
||||
fn rotate_z(self, angle: f64) -> Rotate<Z, Y, X, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Rotate::new(self, angle)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hitable + ?Sized> Hitable for Arc<T> {
|
||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||
self.as_ref().hit(ray, t_min, t_max)
|
||||
}
|
||||
fn bounding_box(&self, t0: f64, t1: f64) -> Option<Aabb> {
|
||||
self.as_ref().bounding_box(t0, t1)
|
||||
}
|
||||
}
|
130
src/hitable/rotate.rs
Normal file
130
src/hitable/rotate.rs
Normal file
|
@ -0,0 +1,130 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use crate::{
|
||||
hitable::{HitRecord, Hitable},
|
||||
types::{Ray, Vec3},
|
||||
Aabb, Dimension,
|
||||
};
|
||||
|
||||
pub struct Rotate<D1, D2, D3, T: Hitable> {
|
||||
hitable: T,
|
||||
sin_theta: f64,
|
||||
cos_theta: f64,
|
||||
bbox: Option<Aabb>,
|
||||
|
||||
_tag: PhantomData<(D1, D2, D3)>,
|
||||
}
|
||||
|
||||
impl<D1, D2, D3, T> Rotate<D1, D2, D3, T>
|
||||
where
|
||||
D1: Dimension,
|
||||
D2: Dimension,
|
||||
D3: Dimension,
|
||||
T: Hitable,
|
||||
{
|
||||
pub fn new(object: T, angle: f64) -> Rotate<D1, D2, D3, T> {
|
||||
let radians = angle.to_radians();
|
||||
let sin_theta = radians.sin();
|
||||
let cos_theta = radians.cos();
|
||||
|
||||
let mut min = Vec3::splat(f64::MAX);
|
||||
let mut max = Vec3::splat(f64::MIN);
|
||||
|
||||
let bbox = if let Some(bbox) = object.bounding_box(0.0, 1.0) {
|
||||
for i in 0..2 {
|
||||
let i = i as f64;
|
||||
for j in 0..2 {
|
||||
let j = j as f64;
|
||||
for k in 0..2 {
|
||||
let k = k as f64;
|
||||
|
||||
// D1 will be the axis about which we are rotating
|
||||
let d1 = i * bbox.max.get::<D1>() + (1.0 - i) * bbox.min.get::<D1>();
|
||||
|
||||
let d2 = j * bbox.max.get::<D2>() + (1.0 - j) * bbox.min.get::<D2>();
|
||||
let d3 = k * bbox.max.get::<D3>() + (1.0 - k) * bbox.min.get::<D3>();
|
||||
|
||||
let new_d2 = cos_theta * d2 + sin_theta * d3;
|
||||
let new_d3 = -sin_theta * d2 + cos_theta * d3;
|
||||
|
||||
let tester = Vec3::splat(0.0)
|
||||
.set::<D1>(d1)
|
||||
.set::<D2>(new_d2)
|
||||
.set::<D3>(new_d3);
|
||||
|
||||
min = Vec3::min(tester, min);
|
||||
max = Vec3::max(tester, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Aabb::new(min, max)
|
||||
} else {
|
||||
Aabb::new(min, max)
|
||||
};
|
||||
|
||||
Rotate {
|
||||
hitable: object,
|
||||
sin_theta,
|
||||
cos_theta,
|
||||
bbox: Some(bbox),
|
||||
_tag: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D1, D2, D3, T> Hitable for Rotate<D1, D2, D3, T>
|
||||
where
|
||||
D1: Dimension,
|
||||
D2: Dimension,
|
||||
D3: Dimension,
|
||||
T: Hitable,
|
||||
{
|
||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||
let origin = ray
|
||||
.origin
|
||||
.set::<D2>(
|
||||
self.cos_theta * ray.origin.get::<D2>() - self.sin_theta * ray.origin.get::<D3>(),
|
||||
)
|
||||
.set::<D3>(
|
||||
self.sin_theta * ray.origin.get::<D2>() + self.cos_theta * ray.origin.get::<D3>(),
|
||||
);
|
||||
|
||||
let direction = ray
|
||||
.direction
|
||||
.set::<D2>(
|
||||
self.cos_theta * ray.direction.get::<D2>()
|
||||
- self.sin_theta * ray.direction.get::<D3>(),
|
||||
)
|
||||
.set::<D3>(
|
||||
self.sin_theta * ray.direction.get::<D2>()
|
||||
+ self.cos_theta * ray.direction.get::<D3>(),
|
||||
);
|
||||
|
||||
let rotated_ray = Ray::new(origin, direction, ray.time());
|
||||
|
||||
let mut hit = self.hitable.hit(&rotated_ray, t_min, t_max)?;
|
||||
|
||||
hit.p = hit
|
||||
.p
|
||||
.set::<D2>(self.cos_theta * hit.p.get::<D2>() + self.sin_theta * hit.p.get::<D3>())
|
||||
.set::<D3>(-self.sin_theta * hit.p.get::<D2>() + self.cos_theta * hit.p.get::<D3>());
|
||||
|
||||
hit.normal = hit
|
||||
.normal
|
||||
.set::<D2>(
|
||||
self.cos_theta * hit.normal.get::<D2>() + self.sin_theta * hit.normal.get::<D3>(),
|
||||
)
|
||||
.set::<D3>(
|
||||
-self.sin_theta * hit.normal.get::<D2>() + self.cos_theta * hit.normal.get::<D3>(),
|
||||
);
|
||||
|
||||
hit.set_face_normal(&rotated_ray);
|
||||
|
||||
Some(hit)
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _t0: f64, _t1: f64) -> Option<Aabb> {
|
||||
self.bbox
|
||||
}
|
||||
}
|
87
src/hitable/shapes/cuboid.rs
Normal file
87
src/hitable/shapes/cuboid.rs
Normal file
|
@ -0,0 +1,87 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
hitable::{hitable_list::HitableList, shapes::RectBuilder, HitRecord, Hitable},
|
||||
materials::{Material, MaterialBuilder},
|
||||
types::{Ray, Vec3},
|
||||
Aabb,
|
||||
};
|
||||
|
||||
pub struct Cuboid {
|
||||
min: Vec3,
|
||||
max: Vec3,
|
||||
sides: HitableList,
|
||||
}
|
||||
|
||||
impl Cuboid {
|
||||
pub fn new(p0: Vec3, p1: Vec3, mat: impl Material + Clone + 'static) -> Self {
|
||||
Self {
|
||||
min: p0,
|
||||
max: p1,
|
||||
sides: Self::build_cuboid(p0, p1, mat),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_cuboid(p0: Vec3, p1: Vec3, mat: impl Material + Clone + 'static) -> HitableList {
|
||||
let mut sides = HitableList {
|
||||
list: Vec::with_capacity(6),
|
||||
};
|
||||
|
||||
sides.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(p0.x()..=p1.x())
|
||||
.y(p0.y()..=p1.y())
|
||||
.z(p1.z())
|
||||
.material(mat.clone()),
|
||||
));
|
||||
sides.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(p0.x()..=p1.x())
|
||||
.y(p0.y()..=p1.y())
|
||||
.z(p0.z())
|
||||
.material(mat.clone()),
|
||||
));
|
||||
|
||||
sides.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(p0.x()..=p1.x())
|
||||
.z(p0.z()..=p1.z())
|
||||
.y(p1.y())
|
||||
.material(mat.clone()),
|
||||
));
|
||||
sides.push(Arc::new(
|
||||
RectBuilder
|
||||
.x(p0.x()..=p1.x())
|
||||
.z(p0.z()..=p1.z())
|
||||
.y(p0.y())
|
||||
.material(mat.clone()),
|
||||
));
|
||||
|
||||
sides.push(Arc::new(
|
||||
RectBuilder
|
||||
.y(p0.y()..=p1.y())
|
||||
.z(p0.z()..=p1.z())
|
||||
.x(p1.x())
|
||||
.material(mat.clone()),
|
||||
));
|
||||
sides.push(Arc::new(
|
||||
RectBuilder
|
||||
.y(p0.y()..=p1.y())
|
||||
.z(p0.z()..=p1.z())
|
||||
.x(p0.x())
|
||||
.material(mat),
|
||||
));
|
||||
|
||||
sides
|
||||
}
|
||||
}
|
||||
|
||||
impl Hitable for Cuboid {
|
||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||
self.sides.hit(ray, t_min, t_max)
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _t0: f64, _t1: f64) -> Option<Aabb> {
|
||||
Some(Aabb::new(self.min, self.max))
|
||||
}
|
||||
}
|
9
src/hitable/shapes/mod.rs
Normal file
9
src/hitable/shapes/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
mod cuboid;
|
||||
mod moving_sphere;
|
||||
mod rectangle;
|
||||
mod sphere;
|
||||
|
||||
pub use cuboid::Cuboid;
|
||||
pub use moving_sphere::MovingSphere;
|
||||
pub use rectangle::RectBuilder;
|
||||
pub use sphere::Sphere;
|
92
src/hitable/shapes/moving_sphere.rs
Normal file
92
src/hitable/shapes/moving_sphere.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
use crate::{
|
||||
hitable::{HitRecord, Hitable},
|
||||
types::{Ray, Vec3},
|
||||
Aabb, Material,
|
||||
};
|
||||
|
||||
pub struct MovingSphere<T: Material + Sized> {
|
||||
radius: f64,
|
||||
center_start: Vec3,
|
||||
center_end: Vec3,
|
||||
time_start: f64,
|
||||
time_end: f64,
|
||||
material: T,
|
||||
}
|
||||
|
||||
impl<T: Material + Sized> MovingSphere<T> {
|
||||
pub fn new(
|
||||
center_start: Vec3,
|
||||
center_end: Vec3,
|
||||
time_start: f64,
|
||||
time_end: f64,
|
||||
radius: f64,
|
||||
material: T,
|
||||
) -> Self {
|
||||
Self {
|
||||
radius,
|
||||
center_start,
|
||||
center_end,
|
||||
time_start,
|
||||
time_end,
|
||||
material,
|
||||
}
|
||||
}
|
||||
|
||||
fn center(&self, time: f64) -> Vec3 {
|
||||
self.center_start
|
||||
+ (self.center_end - self.center_start)
|
||||
* ((time - self.time_start) / (self.time_end - self.time_start))
|
||||
}
|
||||
|
||||
/// p is a point on the sphere of radius 1 & center at origin
|
||||
/// u is between [0,1]. Angle around Y axis from -X axis
|
||||
/// v is between [0,1]. Angle from -Y to +Y axis
|
||||
pub fn get_uv(p: Vec3) -> (f64, f64) {
|
||||
let theta = (-p.y()).acos();
|
||||
let phi = f64::atan2(-p.z(), p.x()) + std::f64::consts::PI;
|
||||
|
||||
let u = phi / (2.0 * std::f64::consts::PI);
|
||||
let v = theta / std::f64::consts::PI;
|
||||
|
||||
(u, v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Material + Sized> Hitable for MovingSphere<T> {
|
||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||
let oc = ray.origin - self.center(ray.time());
|
||||
let a = ray.direction.dot(&ray.direction);
|
||||
let b = oc.dot(&ray.direction);
|
||||
let c = oc.dot(&oc) - self.radius * self.radius;
|
||||
|
||||
let discriminant = b * b - a * c;
|
||||
let discriminant_root = discriminant.sqrt();
|
||||
|
||||
if discriminant > 0.0 {
|
||||
let mut root = (-b - discriminant_root) / a;
|
||||
if root < t_min || root > t_max {
|
||||
root = (-b + discriminant_root) / a;
|
||||
}
|
||||
if root > t_min && root < t_max {
|
||||
let p = ray.point_at_parameter(root);
|
||||
let normal = (p - self.center(ray.time())) / self.radius;
|
||||
|
||||
let mut hit_rec =
|
||||
HitRecord::new(root, p, normal, &self.material, Self::get_uv(normal));
|
||||
|
||||
hit_rec.set_face_normal(ray);
|
||||
|
||||
return Some(hit_rec);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn bounding_box(&self, t0: f64, t1: f64) -> Option<Aabb> {
|
||||
let radius = Vec3::new(self.radius, self.radius, self.radius);
|
||||
let box_smol = Aabb::new(self.center(t0) - radius, self.center(t0) + radius);
|
||||
let box_big = Aabb::new(self.center(t1) - radius, self.center(t1) + radius);
|
||||
|
||||
Some(Aabb::surrounding_box(box_smol, box_big))
|
||||
}
|
||||
}
|
175
src/hitable/shapes/rectangle.rs
Normal file
175
src/hitable/shapes/rectangle.rs
Normal file
|
@ -0,0 +1,175 @@
|
|||
use std::{marker::PhantomData, ops::RangeInclusive};
|
||||
|
||||
use crate::{
|
||||
hitable::{HitRecord, Hitable},
|
||||
materials::MaterialBuilder,
|
||||
types::{Ray, Vec3},
|
||||
Aabb, Dimension, Material, X, Y, Z,
|
||||
};
|
||||
|
||||
type DimRange = RangeInclusive<f64>;
|
||||
|
||||
pub struct Rectangle<D1, D2, D3, T> {
|
||||
d1_range: DimRange,
|
||||
d2_range: DimRange,
|
||||
d3: f64,
|
||||
material: T,
|
||||
tag: PhantomData<(D1, D2, D3)>,
|
||||
}
|
||||
|
||||
impl<D1, D2, D3, T> Hitable for Rectangle<D1, D2, D3, T>
|
||||
where
|
||||
T: Material,
|
||||
D1: Dimension,
|
||||
D2: Dimension,
|
||||
D3: Dimension,
|
||||
{
|
||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||
let t = (self.d3 - ray.origin.get::<D3>()) / ray.direction.get::<D3>();
|
||||
|
||||
if t < t_min || t > t_max {
|
||||
return None;
|
||||
}
|
||||
|
||||
let d1 = ray.origin.get::<D1>() + t * ray.direction.get::<D1>();
|
||||
let d2 = ray.origin.get::<D2>() + t * ray.direction.get::<D2>();
|
||||
|
||||
if !self.d1_range.contains(&d1) || !self.d2_range.contains(&d2) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let u = (d1 - self.d1_range.start()) / (self.d1_range.end() - self.d1_range.start());
|
||||
let v = (d2 - self.d2_range.start()) / (self.d2_range.end() - self.d2_range.start());
|
||||
|
||||
let mut hit_rec = HitRecord::new(
|
||||
t,
|
||||
ray.point_at_parameter(t),
|
||||
Vec3::splat(0.0).set::<D3>(1.0),
|
||||
&self.material,
|
||||
(u, v),
|
||||
);
|
||||
|
||||
hit_rec.set_face_normal(ray);
|
||||
|
||||
Some(hit_rec)
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _t0: f64, _t1: f64) -> Option<Aabb> {
|
||||
// Since this is a axis aligned Rectangle and we are using AABB BVH, Gap between the rectangle and
|
||||
// the bounding box will be infinitely small
|
||||
|
||||
let (&d1_0, &d1_1) = (self.d1_range.start(), self.d1_range.end());
|
||||
let (&d2_0, &d2_1) = (self.d2_range.start(), self.d2_range.end());
|
||||
|
||||
let min = Vec3::splat(self.d3 - 0.0001)
|
||||
.set::<D1>(d1_0)
|
||||
.set::<D2>(d2_0);
|
||||
let max = Vec3::splat(self.d3 + 0.0001)
|
||||
.set::<D1>(d1_1)
|
||||
.set::<D2>(d2_1);
|
||||
|
||||
Some(Aabb::new(min, max))
|
||||
}
|
||||
}
|
||||
|
||||
// taken from, https://github.com/Globidev/toy-rt/blob/master/trt-core/src/hit/rect.rs#L74
|
||||
// because it's amazing!
|
||||
|
||||
pub struct RectBuilder;
|
||||
|
||||
macro_rules! builder {
|
||||
($name:ident, $dim:ty) => {
|
||||
pub fn $name(self, range: RangeInclusive<f64>) -> OneBoundedRectBuilder<$dim> {
|
||||
OneBoundedRectBuilder {
|
||||
range,
|
||||
tag: PhantomData,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct OneBoundedRectBuilder<D> {
|
||||
range: DimRange,
|
||||
tag: PhantomData<D>,
|
||||
}
|
||||
|
||||
impl RectBuilder {
|
||||
builder!(x, X);
|
||||
builder!(y, Y);
|
||||
builder!(z, Z);
|
||||
}
|
||||
|
||||
macro_rules! one_bounded_rect_builder {
|
||||
($name:ident, $dim1: ty, $dim2: ty) => {
|
||||
pub fn $name(self, d2_range: DimRange) -> TwoBoundedRectBuilder<$dim1, $dim2> {
|
||||
TwoBoundedRectBuilder {
|
||||
d1_range: self.range,
|
||||
d2_range,
|
||||
tag: PhantomData,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl OneBoundedRectBuilder<X> {
|
||||
one_bounded_rect_builder!(y, X, Y);
|
||||
one_bounded_rect_builder!(z, X, Z);
|
||||
}
|
||||
impl OneBoundedRectBuilder<Y> {
|
||||
one_bounded_rect_builder!(x, Y, X);
|
||||
one_bounded_rect_builder!(z, Y, Z);
|
||||
}
|
||||
impl OneBoundedRectBuilder<Z> {
|
||||
one_bounded_rect_builder!(x, Z, X);
|
||||
one_bounded_rect_builder!(y, Z, Y);
|
||||
}
|
||||
|
||||
pub struct TwoBoundedRectBuilder<D1, D2> {
|
||||
d1_range: DimRange,
|
||||
d2_range: DimRange,
|
||||
tag: PhantomData<(D1, D2)>,
|
||||
}
|
||||
|
||||
macro_rules! two_bounded_rect_builder {
|
||||
($name:ident, $dim1: ty, $dim2: ty, $dim3: ty) => {
|
||||
pub fn $name(self, $name: f64) -> ThreeBoundedRectBuilder<$dim1, $dim2, $dim3> {
|
||||
ThreeBoundedRectBuilder {
|
||||
d1_range: self.d1_range,
|
||||
d2_range: self.d2_range,
|
||||
d3: $name,
|
||||
tag: PhantomData,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl TwoBoundedRectBuilder<X, Y> {
|
||||
two_bounded_rect_builder!(z, X, Y, Z);
|
||||
}
|
||||
impl TwoBoundedRectBuilder<X, Z> {
|
||||
two_bounded_rect_builder!(y, X, Z, Y);
|
||||
}
|
||||
impl TwoBoundedRectBuilder<Y, Z> {
|
||||
two_bounded_rect_builder!(x, Y, Z, X);
|
||||
}
|
||||
|
||||
pub struct ThreeBoundedRectBuilder<D1, D2, D3> {
|
||||
d1_range: DimRange,
|
||||
d2_range: DimRange,
|
||||
d3: f64,
|
||||
tag: PhantomData<(D1, D2, D3)>,
|
||||
}
|
||||
|
||||
impl<D1, D2, D3, T> MaterialBuilder<T> for ThreeBoundedRectBuilder<D1, D2, D3> {
|
||||
type Finished = Rectangle<D1, D2, D3, T>;
|
||||
|
||||
fn material(self, material: T) -> Self::Finished {
|
||||
Rectangle {
|
||||
d1_range: self.d1_range,
|
||||
d2_range: self.d2_range,
|
||||
d3: self.d3,
|
||||
material,
|
||||
tag: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
77
src/hitable/shapes/sphere.rs
Normal file
77
src/hitable/shapes/sphere.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use crate::{
|
||||
hitable::{HitRecord, Hitable},
|
||||
types::{Ray, Vec3},
|
||||
Aabb, Material,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Sphere<T: Material + Clone + Sized> {
|
||||
center: Vec3,
|
||||
radius: f64,
|
||||
material: T,
|
||||
}
|
||||
|
||||
impl<T: Material + Clone + Sized> Sphere<T> {
|
||||
pub fn new(center: Vec3, radius: f64, material: T) -> Self {
|
||||
Self {
|
||||
center,
|
||||
radius,
|
||||
material,
|
||||
}
|
||||
}
|
||||
|
||||
/// p is a point on the sphere of radius 1 & center at origin
|
||||
/// u is between [0,1]. Angle around Y axis from -X axis
|
||||
/// v is between [0,1]. Angle from -Y to +Y axis
|
||||
pub fn get_uv(p: Vec3) -> (f64, f64) {
|
||||
let theta = (-p.y()).acos();
|
||||
let phi = f64::atan2(-p.z(), p.x()) + std::f64::consts::PI;
|
||||
|
||||
let u = phi / (2.0 * std::f64::consts::PI);
|
||||
let v = theta / std::f64::consts::PI;
|
||||
|
||||
(u, v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Material + Clone + Sized> Hitable for Sphere<T> {
|
||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||
let oc = ray.origin - self.center;
|
||||
let a = ray.direction.dot(&ray.direction);
|
||||
let b = oc.dot(&ray.direction);
|
||||
let c = oc.dot(&oc) - self.radius * self.radius;
|
||||
|
||||
// The discriminant is calculated using b^2 - 4 * a * c
|
||||
// but in this specific case, If we put the equation in the
|
||||
// formula to find quadratic roots, We can get this shorter
|
||||
// formula to find the discriminant.
|
||||
// Check this for detailed proof
|
||||
// https://vchizhov.github.io/resources/ray%20tracing/ray%20tracing%20tutorial%20series%20vchizhov/ray_casting/part1/intersecting_a_sphere.md.html#appendix
|
||||
let discriminant = b * b - a * c;
|
||||
let discriminant_root = discriminant.sqrt();
|
||||
|
||||
if discriminant > 0.0 {
|
||||
let mut root = (-b - discriminant_root) / a;
|
||||
if root < t_min || root > t_max {
|
||||
root = (-b + discriminant_root) / a;
|
||||
}
|
||||
if root > t_min && root < t_max {
|
||||
let p = ray.point_at_parameter(root);
|
||||
let normal = (p - self.center) / self.radius;
|
||||
|
||||
let mut hit_rec =
|
||||
HitRecord::new(root, p, normal, &self.material, Self::get_uv(normal));
|
||||
|
||||
hit_rec.set_face_normal(ray);
|
||||
|
||||
return Some(hit_rec);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _t0: f64, _t1: f64) -> Option<Aabb> {
|
||||
let radius = Vec3::new(self.radius, self.radius, self.radius);
|
||||
Some(Aabb::new(self.center - radius, self.center + radius))
|
||||
}
|
||||
}
|
37
src/hitable/translate.rs
Normal file
37
src/hitable/translate.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use crate::{
|
||||
hitable::{HitRecord, Hitable},
|
||||
types::{Ray, Vec3},
|
||||
Aabb,
|
||||
};
|
||||
|
||||
pub struct Translate<T> {
|
||||
object: T,
|
||||
offset: Vec3,
|
||||
}
|
||||
|
||||
impl<T> Translate<T> {
|
||||
pub const fn new(object: T, offset: Vec3) -> Self {
|
||||
Self { object, offset }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hitable> Hitable for Translate<T> {
|
||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||
let moved_ray = Ray::new(ray.origin - self.offset, ray.direction, ray.time());
|
||||
|
||||
if let Some(mut hit) = self.object.hit(&moved_ray, t_min, t_max) {
|
||||
hit.p += self.offset;
|
||||
hit.set_face_normal(&moved_ray);
|
||||
|
||||
Some(hit)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_box(&self, t0: f64, t1: f64) -> Option<Aabb> {
|
||||
self.object
|
||||
.bounding_box(t0, t1)
|
||||
.map(|bbox| Aabb::new(bbox.min + self.offset, bbox.max + self.offset))
|
||||
}
|
||||
}
|
63
src/hitable/volume/constant_medium.rs
Normal file
63
src/hitable/volume/constant_medium.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
use crate::{
|
||||
hitable::{HitRecord, Hitable},
|
||||
types::{Ray, Vec3},
|
||||
Aabb, Material,
|
||||
};
|
||||
|
||||
pub struct ConstantMedium<A: Hitable, B: Material> {
|
||||
neg_inv_density: f64,
|
||||
boundary: A,
|
||||
phase_function: B,
|
||||
}
|
||||
|
||||
impl<A: Hitable, B: Material> ConstantMedium<A, B> {
|
||||
pub fn new(boundary: A, phase_function: B, d: f64) -> Self {
|
||||
Self {
|
||||
boundary,
|
||||
phase_function,
|
||||
neg_inv_density: -1.0 / d,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Hitable, B: Material> Hitable for ConstantMedium<A, B> {
|
||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||
let mut hit1 = self.boundary.hit(ray, f64::MIN, f64::MAX)?;
|
||||
let mut hit2 = self.boundary.hit(ray, hit1.t + 0.0001, f64::MAX)?;
|
||||
|
||||
hit1.t = hit1.t.max(t_min);
|
||||
hit2.t = hit2.t.min(t_max);
|
||||
|
||||
if hit1.t >= hit2.t {
|
||||
return None;
|
||||
};
|
||||
|
||||
hit1.t = hit1.t.max(0.0);
|
||||
|
||||
let ray_length = ray.direction.length();
|
||||
let distance_inside_boundary = (hit2.t - hit1.t) * ray_length;
|
||||
let hit_distance = self.neg_inv_density * rand::random::<f64>().ln();
|
||||
|
||||
if hit_distance > distance_inside_boundary {
|
||||
return None;
|
||||
}
|
||||
|
||||
let t = hit1.t + hit_distance / ray_length;
|
||||
|
||||
Some(HitRecord {
|
||||
t,
|
||||
p: ray.point_at_parameter(t),
|
||||
material: &self.phase_function,
|
||||
u: 0.0,
|
||||
v: 0.0,
|
||||
|
||||
// Arbitrary
|
||||
front_face: true,
|
||||
normal: Vec3::new(1.0, 0.0, 0.0),
|
||||
})
|
||||
}
|
||||
|
||||
fn bounding_box(&self, t0: f64, t1: f64) -> Option<Aabb> {
|
||||
self.boundary.bounding_box(t0, t1)
|
||||
}
|
||||
}
|
3
src/hitable/volume/mod.rs
Normal file
3
src/hitable/volume/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod constant_medium;
|
||||
|
||||
pub use constant_medium::ConstantMedium;
|
199
src/main.rs
199
src/main.rs
|
@ -1,20 +1,195 @@
|
|||
use rand::Rng;
|
||||
#![allow(clippy::suspicious_arithmetic_impl)]
|
||||
|
||||
#[inline]
|
||||
fn pdf(x: f64) -> f64 {
|
||||
3.0 * x * x / 8.0
|
||||
mod aabb;
|
||||
mod camera;
|
||||
mod demos;
|
||||
mod hitable;
|
||||
mod materials;
|
||||
mod texture;
|
||||
mod types;
|
||||
|
||||
pub use aabb::Aabb;
|
||||
pub use camera::Camera;
|
||||
pub use materials::Material;
|
||||
pub use texture::Texture;
|
||||
pub use types::{Dimension, X, Y, Z};
|
||||
|
||||
use crate::hitable::BvhNode;
|
||||
use demos::DemoWrapper;
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
pub trait Asf64: num_traits::AsPrimitive<f64> {}
|
||||
impl<T: num_traits::AsPrimitive<f64>> Asf64 for T {}
|
||||
|
||||
const NUM_SAMPLES: u16 = 500;
|
||||
const VERTICAL_PARTITION: usize = 30;
|
||||
const HORIZONTAL_PARTITION: usize = 30;
|
||||
const WIDTH: usize = 800;
|
||||
const HEIGHT: usize = 800;
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
run(WIDTH, HEIGHT)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rng = rand::thread_rng();
|
||||
const N: u64 = 100000;
|
||||
let mut sum = 0.0;
|
||||
#[cfg(feature = "gui")]
|
||||
fn run(mut width: usize, mut height: usize) -> Result<(), String> {
|
||||
use sdl2::{
|
||||
event::{Event, WindowEvent},
|
||||
keyboard::Keycode,
|
||||
pixels::PixelFormatEnum,
|
||||
};
|
||||
|
||||
for _ in 0..N {
|
||||
let x: f64 = rng.gen_range(0.0f64..=8.0).powf(1.0 / 3.0);
|
||||
let sdl_ctx = sdl2::init()?;
|
||||
let video_subsys = sdl_ctx.video()?;
|
||||
let window = video_subsys
|
||||
.window("Ray tracing the Next Week", width as u32, height as u32)
|
||||
.position_centered()
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
sum += x * x / pdf(x);
|
||||
let mut event_pump = sdl_ctx.event_pump()?;
|
||||
|
||||
let mut canvas = window
|
||||
.into_canvas()
|
||||
.target_texture()
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// RGBA framebuffer
|
||||
let mut buffer = vec![0; height * width * 4];
|
||||
|
||||
let texture_creator = canvas.texture_creator();
|
||||
let mut texture = texture_creator
|
||||
.create_texture_static(PixelFormatEnum::BGR888, width as u32, height as u32)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let mut active_demo = DemoWrapper::HitableList(Box::new(demos::CornellBox {}));
|
||||
let mut should_update = true;
|
||||
|
||||
loop {
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. }
|
||||
| Event::KeyDown {
|
||||
keycode: Some(Keycode::Escape),
|
||||
..
|
||||
} => return Ok(()),
|
||||
Event::KeyUp { keycode, .. } => {
|
||||
match keycode {
|
||||
Some(Keycode::S) => {
|
||||
active_demo.save_as_ppm(&buffer, width, height, NUM_SAMPLES);
|
||||
should_update = false;
|
||||
}
|
||||
Some(Keycode::Num1) => {
|
||||
active_demo =
|
||||
DemoWrapper::BVHNode(Box::new(demos::CheckeredMotionBlur {}));
|
||||
should_update = true;
|
||||
}
|
||||
Some(Keycode::Num2) => {
|
||||
active_demo = DemoWrapper::BVHNode(Box::new(demos::TwoSpheres {}));
|
||||
should_update = true;
|
||||
}
|
||||
Some(Keycode::Num3) => {
|
||||
active_demo = DemoWrapper::BVHNode(Box::new(demos::PerlinNoiseBall {}));
|
||||
should_update = true;
|
||||
}
|
||||
Some(Keycode::Num4) => {
|
||||
active_demo =
|
||||
DemoWrapper::BVHNode(Box::new(demos::ImageTextureDemo {}));
|
||||
should_update = true;
|
||||
}
|
||||
Some(Keycode::Num5) => {
|
||||
active_demo = DemoWrapper::BVHNode(Box::new(demos::SimpleLight {}));
|
||||
should_update = true;
|
||||
}
|
||||
Some(Keycode::Num6) => {
|
||||
active_demo = DemoWrapper::BVHNode(Box::new(demos::Instances {}));
|
||||
should_update = true;
|
||||
}
|
||||
Some(Keycode::Num7) => {
|
||||
active_demo =
|
||||
DemoWrapper::BVHNode(Box::new(demos::CornellSmokeAndFog {}));
|
||||
should_update = true;
|
||||
}
|
||||
Some(Keycode::Num8) => {
|
||||
active_demo = DemoWrapper::HitableList(Box::new(demos::CornellBox {}));
|
||||
should_update = true;
|
||||
}
|
||||
None => unreachable!(),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
Event::Window {
|
||||
win_event: WindowEvent::Resized(w, h),
|
||||
..
|
||||
} => {
|
||||
width = w as usize;
|
||||
height = h as usize;
|
||||
buffer.resize(width * height * 4, 0);
|
||||
texture = texture_creator
|
||||
.create_texture_static(PixelFormatEnum::BGR888, width as u32, height as u32)
|
||||
.expect("error in resizing texture");
|
||||
should_update = true;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
if should_update {
|
||||
let now = Instant::now();
|
||||
active_demo.render(&mut buffer, width, height, NUM_SAMPLES);
|
||||
println!(
|
||||
"Demo {} Time Taken(s) = {}",
|
||||
active_demo.name(),
|
||||
now.elapsed().as_secs_f64()
|
||||
);
|
||||
texture.update(None, &buffer, width * 4).unwrap();
|
||||
canvas.copy(&texture, None, None).unwrap();
|
||||
canvas.present();
|
||||
should_update = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("answer = {}", sum / N as f64);
|
||||
#[cfg(not(feature = "gui"))]
|
||||
fn run(width: usize, height: usize) -> Result<(), String> {
|
||||
let demos: [DemoWrapper; 8] = [
|
||||
DemoWrapper::BVHNode(Box::new(demos::CheckeredMotionBlur {})),
|
||||
DemoWrapper::BVHNode(Box::new(demos::TwoSpheres {})),
|
||||
DemoWrapper::BVHNode(Box::new(demos::PerlinNoiseBall {})),
|
||||
DemoWrapper::BVHNode(Box::new(demos::ImageTextureDemo {})),
|
||||
DemoWrapper::BVHNode(Box::new(demos::SimpleLight {})),
|
||||
DemoWrapper::BVHNode(Box::new(demos::Instances {})),
|
||||
DemoWrapper::BVHNode(Box::new(demos::CornellSmokeAndFog {})),
|
||||
DemoWrapper::HitableList(Box::new(demos::CornellBox {})),
|
||||
];
|
||||
|
||||
for demo in demos.iter() {
|
||||
run_and_save_demo(demo, width, height)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "gui"))]
|
||||
fn run_and_save_demo(demo: &DemoWrapper, width: usize, height: usize) {
|
||||
let mut buffer = vec![0; width * height * 4];
|
||||
|
||||
println!(
|
||||
"Starting {} at {}x{} with {} samples",
|
||||
demo.name(),
|
||||
width,
|
||||
height,
|
||||
NUM_SAMPLES
|
||||
);
|
||||
|
||||
let now = Instant::now();
|
||||
demo.render(&mut buffer, width, height, NUM_SAMPLES);
|
||||
println!(
|
||||
"Rendered Demo {}. Time Taken(s) = {}",
|
||||
demo.name(),
|
||||
now.elapsed().as_secs_f64()
|
||||
);
|
||||
|
||||
demo.save_as_ppm(&buffer, width, height, NUM_SAMPLES);
|
||||
}
|
||||
|
|
62
src/materials/dielectric.rs
Normal file
62
src/materials/dielectric.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
use rand::{prelude::SmallRng, Rng};
|
||||
|
||||
use crate::{
|
||||
hitable::HitRecord,
|
||||
materials::{reflect, refract, schlick},
|
||||
types::{Ray, Vec3},
|
||||
Material,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Dielectric {
|
||||
refraction_index: f64,
|
||||
}
|
||||
|
||||
impl Dielectric {
|
||||
pub fn new(refraction_index: f64) -> Self {
|
||||
Self { refraction_index }
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for Dielectric {
|
||||
fn scatter(
|
||||
&self,
|
||||
ray_in: &Ray,
|
||||
hit_rec: &HitRecord,
|
||||
rng: &mut SmallRng,
|
||||
) -> (Vec3, Option<Ray>) {
|
||||
// Glass absorbs nothing! So, Attenuation is always going to be 1.0 for this
|
||||
let attenuation = Vec3::splat(1.0);
|
||||
|
||||
let refraction_ratio = if hit_rec.front_face {
|
||||
1.0 / self.refraction_index
|
||||
} else {
|
||||
self.refraction_index
|
||||
};
|
||||
|
||||
let unit_direction = ray_in.direction.unit_vector();
|
||||
let cosine = (-unit_direction).dot(&hit_rec.normal).min(1.0);
|
||||
let sin_theta = (1.0 - cosine * cosine).sqrt();
|
||||
|
||||
let cannot_refract = refraction_ratio * sin_theta > 1.0;
|
||||
|
||||
if cannot_refract || schlick(cosine, refraction_ratio) > rng.gen::<f64>() {
|
||||
let direction = reflect(unit_direction, hit_rec.normal);
|
||||
(
|
||||
attenuation,
|
||||
Some(Ray::new(hit_rec.p, direction, ray_in.time())),
|
||||
)
|
||||
} else if let Some(direction) = refract(unit_direction, hit_rec.normal, refraction_ratio) {
|
||||
(
|
||||
attenuation,
|
||||
Some(Ray::new(hit_rec.p, direction, ray_in.time())),
|
||||
)
|
||||
} else {
|
||||
let direction = reflect(unit_direction, hit_rec.normal);
|
||||
(
|
||||
attenuation,
|
||||
Some(Ray::new(hit_rec.p, direction, ray_in.time())),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
18
src/materials/diffuse_light.rs
Normal file
18
src/materials/diffuse_light.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use crate::{types::Vec3, Material, Texture};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DiffuseLight<T: Texture> {
|
||||
emit: T,
|
||||
}
|
||||
|
||||
impl<T: Texture> DiffuseLight<T> {
|
||||
pub fn new(emit: T) -> Self {
|
||||
Self { emit }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Texture + Send + Sync> Material for DiffuseLight<T> {
|
||||
fn emit(&self, u: f64, v: f64, p: Vec3) -> Vec3 {
|
||||
self.emit.value(u, v, p)
|
||||
}
|
||||
}
|
31
src/materials/isotropic.rs
Normal file
31
src/materials/isotropic.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use rand::prelude::SmallRng;
|
||||
|
||||
use crate::{
|
||||
hitable::HitRecord,
|
||||
materials::random_point_in_unit_sphere,
|
||||
types::{Ray, Vec3},
|
||||
Material, Texture,
|
||||
};
|
||||
|
||||
pub struct Isotropic<T> {
|
||||
texture: T,
|
||||
}
|
||||
|
||||
impl<T: Texture> Isotropic<T> {
|
||||
pub fn new(texture: T) -> Self {
|
||||
Self { texture }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Texture + Send + Sync> Material for Isotropic<T> {
|
||||
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option<Ray>) {
|
||||
(
|
||||
self.texture.value(hit_rec.u, hit_rec.v, hit_rec.p),
|
||||
Some(Ray::new(
|
||||
hit_rec.p,
|
||||
random_point_in_unit_sphere(rng),
|
||||
ray.time(),
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
31
src/materials/lambertian.rs
Normal file
31
src/materials/lambertian.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use rand::prelude::SmallRng;
|
||||
|
||||
use crate::{
|
||||
hitable::HitRecord,
|
||||
materials::random_point_in_unit_sphere,
|
||||
types::{Ray, Vec3},
|
||||
Material, Texture,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Lambertian<T: Texture> {
|
||||
albedo: T,
|
||||
}
|
||||
|
||||
impl<T: Texture> Lambertian<T> {
|
||||
pub fn new(albedo: T) -> Self {
|
||||
Self { albedo }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Texture + Send + Sync> Material for Lambertian<T> {
|
||||
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option<Ray>) {
|
||||
let scatter_direction = hit_rec.normal + random_point_in_unit_sphere(rng);
|
||||
let scattered_ray = Ray::new(hit_rec.p, scatter_direction, ray.time());
|
||||
|
||||
(
|
||||
self.albedo.value(hit_rec.u, hit_rec.v, hit_rec.p),
|
||||
Some(scattered_ray),
|
||||
)
|
||||
}
|
||||
}
|
46
src/materials/metal.rs
Normal file
46
src/materials/metal.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use rand::prelude::SmallRng;
|
||||
|
||||
use crate::{
|
||||
hitable::HitRecord,
|
||||
materials::{random_point_in_unit_sphere, reflect},
|
||||
types::{Ray, Vec3},
|
||||
Material,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Metal {
|
||||
albedo: Vec3,
|
||||
fuzz: f64,
|
||||
}
|
||||
|
||||
impl Metal {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(albedo: Vec3) -> Self {
|
||||
Self { albedo, fuzz: 0.0 }
|
||||
}
|
||||
pub fn with_fuzz(albedo: Vec3, fuzz: f64) -> Self {
|
||||
Self { albedo, fuzz }
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for Metal {
|
||||
fn scatter(
|
||||
&self,
|
||||
ray_in: &Ray,
|
||||
hit_rec: &HitRecord,
|
||||
rng: &mut SmallRng,
|
||||
) -> (Vec3, Option<Ray>) {
|
||||
let reflected_ray = reflect(ray_in.direction.unit_vector(), hit_rec.normal);
|
||||
let scattered_ray = Ray::new(
|
||||
hit_rec.p,
|
||||
reflected_ray + random_point_in_unit_sphere(rng) * self.fuzz,
|
||||
ray_in.time(),
|
||||
);
|
||||
|
||||
if scattered_ray.direction.dot(&hit_rec.normal) > 0.0 {
|
||||
(self.albedo, Some(scattered_ray))
|
||||
} else {
|
||||
(self.albedo, None)
|
||||
}
|
||||
}
|
||||
}
|
72
src/materials/mod.rs
Normal file
72
src/materials/mod.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
mod dielectric;
|
||||
mod diffuse_light;
|
||||
mod isotropic;
|
||||
mod lambertian;
|
||||
mod metal;
|
||||
|
||||
pub use dielectric::Dielectric;
|
||||
pub use diffuse_light::DiffuseLight;
|
||||
pub use isotropic::Isotropic;
|
||||
pub use lambertian::Lambertian;
|
||||
pub use metal::Metal;
|
||||
use rand::{prelude::SmallRng, Rng};
|
||||
|
||||
use crate::{
|
||||
hitable::HitRecord,
|
||||
types::{Ray, Vec3},
|
||||
};
|
||||
|
||||
pub trait Material: Send + Sync {
|
||||
// scatter returns the attenuation and the scattered ray.
|
||||
// Attenuation is ignored completely if there is no scattered ray
|
||||
fn scatter(
|
||||
&self,
|
||||
_ray: &Ray,
|
||||
_hit_rec: &HitRecord,
|
||||
_rng: &mut SmallRng,
|
||||
) -> (Vec3, Option<Ray>) {
|
||||
(Vec3::splat(0.0), None)
|
||||
}
|
||||
|
||||
fn emit(&self, _u: f64, _v: f64, _p: Vec3) -> Vec3 {
|
||||
Vec3::splat(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
// Christophe Schlick's Polynomial approximation to figure out reflectivity as the angle changes
|
||||
// See Fresnel Equations, https://en.wikipedia.org/wiki/Fresnel_equations
|
||||
fn schlick(cosine: f64, reflection_index: f64) -> f64 {
|
||||
let mut r0 = (1.0 - reflection_index) / (1.0 + reflection_index);
|
||||
r0 = r0 * r0;
|
||||
r0 + (1.0 - r0) * (1.0 - cosine).powf(5.0)
|
||||
}
|
||||
|
||||
fn reflect(incident: Vec3, normal: Vec3) -> Vec3 {
|
||||
incident - normal * incident.dot(&normal) * 2.0
|
||||
}
|
||||
|
||||
// Snell's Law
|
||||
fn refract(incident: Vec3, normal: Vec3, ni_over_nt: f64) -> Option<Vec3> {
|
||||
let uv = incident.unit_vector();
|
||||
let dt = uv.dot(&normal);
|
||||
let discriminant = 1.0 - ni_over_nt * ni_over_nt * (1.0 - dt * dt);
|
||||
if discriminant > 0.0 {
|
||||
Some((uv - normal * dt) * ni_over_nt - normal * discriminant.sqrt())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn random_point_in_unit_sphere<R: Rng + ?Sized>(rng: &mut R) -> Vec3 {
|
||||
let mut point = Vec3::random(rng) * 2.0 - Vec3::splat(1.0);
|
||||
while point.sq_len() >= 1.0 {
|
||||
point = Vec3::random(rng) * 2.0 - Vec3::splat(1.0);
|
||||
}
|
||||
point
|
||||
}
|
||||
|
||||
pub trait MaterialBuilder<T> {
|
||||
type Finished;
|
||||
|
||||
fn material(self, material: T) -> Self::Finished;
|
||||
}
|
25
src/texture/checker.rs
Normal file
25
src/texture/checker.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use crate::{types::Vec3, Texture};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Checker<T: Texture + Clone> {
|
||||
odd: T,
|
||||
even: T,
|
||||
}
|
||||
|
||||
impl<T: Texture + Clone> Checker<T> {
|
||||
pub fn new(even: T, odd: T) -> Self {
|
||||
Self { odd, even }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Texture + Clone> Texture for Checker<T> {
|
||||
fn value(&self, u: f64, v: f64, p: Vec3) -> Vec3 {
|
||||
let sine_wave = f64::sin(10.0 * p.x()) * f64::sin(10.0 * p.y()) * f64::sin(10.0 * p.z());
|
||||
|
||||
if sine_wave < 0.0 {
|
||||
self.odd.value(u, v, p)
|
||||
} else {
|
||||
self.even.value(u, v, p)
|
||||
}
|
||||
}
|
||||
}
|
56
src/texture/image_texture.rs
Normal file
56
src/texture/image_texture.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use image::{error::ImageError, io::Reader as ImageReader};
|
||||
|
||||
use crate::{types::Vec3, Texture};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ImageTexture {
|
||||
image: Vec<u8>,
|
||||
// (width, height)
|
||||
dimensions: (u32, u32),
|
||||
bytes_per_scanline: u32,
|
||||
bytes_per_pixel: u32,
|
||||
}
|
||||
|
||||
impl ImageTexture {
|
||||
#[allow(dead_code)]
|
||||
pub fn from_filename(filename: &str) -> Result<Self, ImageError> {
|
||||
let img = ImageReader::open(filename)?.decode()?;
|
||||
let img = img.to_rgb8();
|
||||
|
||||
let (width, _) = img.dimensions();
|
||||
|
||||
let bytes_per_pixel = 3;
|
||||
|
||||
Ok(Self {
|
||||
image: img.to_vec(),
|
||||
dimensions: img.dimensions(),
|
||||
bytes_per_scanline: bytes_per_pixel * width,
|
||||
bytes_per_pixel,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Texture for ImageTexture {
|
||||
fn value(&self, u: f64, v: f64, _p: Vec3) -> Vec3 {
|
||||
let (width, height) = self.dimensions;
|
||||
|
||||
let u = u.clamp(0.0, 1.0);
|
||||
let v = 1.0 - v.clamp(0.0, 1.0);
|
||||
|
||||
let i = (u * width as f64) as u32;
|
||||
let j = (v * height as f64) as u32;
|
||||
|
||||
let i = i.clamp(0, width - 1);
|
||||
let j = j.clamp(0, height - 1);
|
||||
|
||||
let color_scale = 1.0 / 255.0;
|
||||
|
||||
let pixel = (j * self.bytes_per_scanline + i * self.bytes_per_pixel) as usize;
|
||||
|
||||
Vec3::new(
|
||||
color_scale * (self.image[pixel] as f64),
|
||||
color_scale * (self.image[pixel + 1] as f64),
|
||||
color_scale * (self.image[pixel + 2] as f64),
|
||||
)
|
||||
}
|
||||
}
|
17
src/texture/mod.rs
Normal file
17
src/texture/mod.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
mod checker;
|
||||
mod image_texture;
|
||||
mod perlin;
|
||||
mod perlin_noise;
|
||||
mod solid;
|
||||
|
||||
pub use checker::Checker;
|
||||
pub use image_texture::ImageTexture;
|
||||
pub use perlin::Perlin;
|
||||
pub use perlin_noise::PerlinNoise;
|
||||
pub use solid::Solid;
|
||||
|
||||
use crate::types::Vec3;
|
||||
|
||||
pub trait Texture {
|
||||
fn value(&self, u: f64, v: f64, p: Vec3) -> Vec3;
|
||||
}
|
120
src/texture/perlin.rs
Normal file
120
src/texture/perlin.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
use crate::types::Vec3;
|
||||
use rand::Rng;
|
||||
|
||||
const POINT_COUNT: usize = 256;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Perlin {
|
||||
points: Vec<Vec3>,
|
||||
|
||||
permute_x: Vec<usize>,
|
||||
permute_y: Vec<usize>,
|
||||
permute_z: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Perlin {
|
||||
pub fn new<R: Rng + ?Sized>(rng: &mut R) -> Self {
|
||||
let points = (0..POINT_COUNT)
|
||||
.map(|_| Vec3::random(rng).unit_vector())
|
||||
.collect::<Vec<Vec3>>();
|
||||
|
||||
let permute_x = Self::perlin_generate_permutation(rng);
|
||||
let permute_y = Self::perlin_generate_permutation(rng);
|
||||
let permute_z = Self::perlin_generate_permutation(rng);
|
||||
|
||||
Self {
|
||||
points,
|
||||
permute_x,
|
||||
permute_y,
|
||||
permute_z,
|
||||
}
|
||||
}
|
||||
|
||||
fn perlin_generate_permutation<R: Rng + ?Sized>(rng: &mut R) -> Vec<usize> {
|
||||
let mut p = (0..POINT_COUNT).collect::<Vec<usize>>();
|
||||
permute(rng, &mut p);
|
||||
p
|
||||
}
|
||||
|
||||
pub fn noise(&self, p: Vec3) -> f64 {
|
||||
let mut smooth_grid = [[[Vec3::new(0.0, 0.0, 0.0); 2]; 2]; 2];
|
||||
|
||||
{
|
||||
let i = p.x().floor() as i32;
|
||||
let j = p.y().floor() as i32;
|
||||
let k = p.z().floor() as i32;
|
||||
for (di, a) in smooth_grid.iter_mut().enumerate() {
|
||||
let di = di as i32;
|
||||
for (dj, b) in a.iter_mut().enumerate() {
|
||||
let dj = dj as i32;
|
||||
for (dk, c) in b.iter_mut().enumerate() {
|
||||
let dk = dk as i32;
|
||||
|
||||
*c = self.points[self.permute_x[((i + di) & 255) as usize]
|
||||
^ self.permute_y[((j + dj) & 255) as usize]
|
||||
^ self.permute_z[((k + dk) & 255) as usize]]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let u = p.x() - p.x().floor();
|
||||
let v = p.y() - p.y().floor();
|
||||
let w = p.z() - p.z().floor();
|
||||
|
||||
perlin_interpolate(smooth_grid, u, v, w)
|
||||
}
|
||||
|
||||
pub fn turbulence(&self, p: Vec3, depth: u32) -> f64 {
|
||||
let mut acc = 0.0f64;
|
||||
let mut weight = 1.0;
|
||||
let mut temp_p = p;
|
||||
|
||||
for _i in 0..depth {
|
||||
acc += weight * self.noise(temp_p);
|
||||
weight *= 0.5;
|
||||
temp_p *= 2.0;
|
||||
}
|
||||
|
||||
acc.abs()
|
||||
}
|
||||
}
|
||||
|
||||
fn perlin_interpolate(smooth_grid: [[[Vec3; 2]; 2]; 2], u: f64, v: f64, w: f64) -> f64 {
|
||||
// Hermitian smoothing so we don't see obvious grid features in the picture
|
||||
// Those features show up when we interpolate colors. Those features are
|
||||
// also called mach bands
|
||||
let uu = u * u * (3.0 - 2.0 * u);
|
||||
let vv = v * v * (3.0 - 2.0 * v);
|
||||
let ww = w * w * (3.0 - 2.0 * w);
|
||||
|
||||
let mut acc = 0.0;
|
||||
|
||||
for (di, a) in smooth_grid.iter().enumerate() {
|
||||
let di = di as f64;
|
||||
for (dj, b) in a.iter().enumerate() {
|
||||
let dj = dj as f64;
|
||||
for (dk, c) in b.iter().enumerate() {
|
||||
let dk = dk as f64;
|
||||
|
||||
let wt = Vec3::new(u - di, v - dj, w - dk);
|
||||
|
||||
acc += (di * uu + (1.0 - di) * (1.0 - uu))
|
||||
* (dj * vv + (1.0 - dj) * (1.0 - vv))
|
||||
* (dk * ww + (1.0 - dk) * (1.0 - ww))
|
||||
* c.dot(&wt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
acc
|
||||
}
|
||||
|
||||
fn permute<R: Rng + ?Sized>(rng: &mut R, p: &mut [usize]) {
|
||||
let l = p.len();
|
||||
|
||||
for i in (0..l).rev() {
|
||||
let r = rng.gen_range(0..=i);
|
||||
p.swap(i, r);
|
||||
}
|
||||
}
|
34
src/texture/perlin_noise.rs
Normal file
34
src/texture/perlin_noise.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use rand::Rng;
|
||||
|
||||
use crate::{texture::Perlin, types::Vec3, Texture};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PerlinNoise {
|
||||
noise: Perlin,
|
||||
scale: f64,
|
||||
}
|
||||
|
||||
impl PerlinNoise {
|
||||
#[allow(dead_code)]
|
||||
pub fn new<R: Rng + ?Sized>(rng: &mut R) -> Self {
|
||||
Self {
|
||||
noise: Perlin::new(rng),
|
||||
scale: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_scale<R: Rng + ?Sized>(rng: &mut R, scale: f64) -> Self {
|
||||
Self {
|
||||
noise: Perlin::new(rng),
|
||||
scale,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Texture for PerlinNoise {
|
||||
fn value(&self, _u: f64, _v: f64, p: Vec3) -> Vec3 {
|
||||
Vec3::new(1.0, 1.0, 1.0)
|
||||
* 0.5
|
||||
* (1.0 + (self.scale * p.z() + 10.0 * self.noise.turbulence(p, 7)).sin())
|
||||
}
|
||||
}
|
18
src/texture/solid.rs
Normal file
18
src/texture/solid.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use crate::{types::Vec3, Texture};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Solid {
|
||||
color: Vec3,
|
||||
}
|
||||
|
||||
impl Solid {
|
||||
pub const fn new(color: Vec3) -> Self {
|
||||
Self { color }
|
||||
}
|
||||
}
|
||||
|
||||
impl Texture for Solid {
|
||||
fn value(&self, _u: f64, _v: f64, _p: Vec3) -> Vec3 {
|
||||
self.color
|
||||
}
|
||||
}
|
12
src/types/color.rs
Normal file
12
src/types/color.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use crate::types::Vec3;
|
||||
|
||||
pub struct Color(pub u8, pub u8, pub u8);
|
||||
|
||||
impl From<Vec3> for Color {
|
||||
fn from(v: Vec3) -> Self {
|
||||
let v = v.sqrt() * 255.99;
|
||||
let (r, g, b) = (v.x(), v.y(), v.z());
|
||||
|
||||
Self(r as u8, g as u8, b as u8)
|
||||
}
|
||||
}
|
21
src/types/dimension.rs
Normal file
21
src/types/dimension.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Taken from, https://github.com/Globidev/toy-rt/tree/master/trt-core/src/dimension.rs
|
||||
|
||||
pub trait Dimension {
|
||||
const INDEX: usize;
|
||||
}
|
||||
|
||||
pub struct X;
|
||||
pub struct Y;
|
||||
pub struct Z;
|
||||
|
||||
impl Dimension for X {
|
||||
const INDEX: usize = 0;
|
||||
}
|
||||
|
||||
impl Dimension for Y {
|
||||
const INDEX: usize = 1;
|
||||
}
|
||||
|
||||
impl Dimension for Z {
|
||||
const INDEX: usize = 2;
|
||||
}
|
17
src/types/mod.rs
Normal file
17
src/types/mod.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
mod color;
|
||||
mod dimension;
|
||||
mod ray;
|
||||
|
||||
pub use color::Color;
|
||||
pub use dimension::{Dimension, X, Y, Z};
|
||||
pub use ray::Ray;
|
||||
|
||||
#[cfg(not(target_arch = "x86_64"))]
|
||||
mod vec3;
|
||||
#[cfg(not(target_arch = "x86_64"))]
|
||||
pub use vec3::Vec3;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod simd_vec3;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use simd_vec3::Vec3;
|
54
src/types/ray.rs
Normal file
54
src/types/ray.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use rand::prelude::SmallRng;
|
||||
|
||||
use crate::{hitable::Hitable, types::Vec3};
|
||||
|
||||
pub struct Ray {
|
||||
pub origin: Vec3,
|
||||
pub direction: Vec3,
|
||||
time: f64,
|
||||
}
|
||||
|
||||
impl Ray {
|
||||
pub fn new(origin: Vec3, direction: Vec3, time: f64) -> Ray {
|
||||
Ray {
|
||||
origin,
|
||||
direction,
|
||||
time,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn point_at_parameter(&self, t: f64) -> Vec3 {
|
||||
self.origin + self.direction * t
|
||||
}
|
||||
#[inline]
|
||||
pub const fn time(&self) -> f64 {
|
||||
self.time
|
||||
}
|
||||
|
||||
pub fn color<T: Hitable>(
|
||||
&self,
|
||||
world: &T,
|
||||
rng: &mut SmallRng,
|
||||
background: &Vec3,
|
||||
depth: u32,
|
||||
) -> Vec3 {
|
||||
if let Some(hit_rec) = world.hit(self, 0.001, std::f64::MAX) {
|
||||
if depth >= 50 {
|
||||
Vec3::splat(0.0f64)
|
||||
} else {
|
||||
let material = hit_rec.material;
|
||||
let emitted_color = hit_rec.material.emit(hit_rec.u, hit_rec.v, hit_rec.p);
|
||||
|
||||
if let (attenuation, Some(scattered_ray)) = material.scatter(self, &hit_rec, rng) {
|
||||
emitted_color
|
||||
+ attenuation * scattered_ray.color(world, rng, background, depth + 1)
|
||||
} else {
|
||||
emitted_color
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*background
|
||||
}
|
||||
}
|
||||
}
|
214
src/types/simd_vec3.rs
Normal file
214
src/types/simd_vec3.rs
Normal file
|
@ -0,0 +1,214 @@
|
|||
use std::{
|
||||
fmt::{Display, Formatter, Result as FmtResult},
|
||||
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, RangeInclusive, Sub, SubAssign},
|
||||
};
|
||||
|
||||
use rand::Rng;
|
||||
|
||||
use crate::{Asf64, Dimension, X, Y, Z};
|
||||
|
||||
use packed_simd::{f64x4, shuffle};
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone)]
|
||||
pub struct Vec3(f64x4);
|
||||
|
||||
impl Vec3 {
|
||||
#[inline]
|
||||
pub fn new(a: impl Asf64, b: impl Asf64, c: impl Asf64) -> Vec3 {
|
||||
Self(f64x4::new(a.as_(), b.as_(), c.as_(), 0.0))
|
||||
}
|
||||
|
||||
pub fn splat(xyz: impl Asf64) -> Self {
|
||||
Self::new(xyz, xyz, xyz)
|
||||
}
|
||||
|
||||
pub fn random<R: Rng + ?Sized>(rng: &mut R) -> Self {
|
||||
Self(f64x4::from_slice_unaligned(&rng.gen::<[f64; 4]>()))
|
||||
}
|
||||
|
||||
pub fn random_in_range<R: Rng + ?Sized>(rng: &mut R, range: RangeInclusive<f64>) -> Self {
|
||||
Vec3::new(
|
||||
rng.gen_range(range.clone()),
|
||||
rng.gen_range(range.clone()),
|
||||
rng.gen_range(range),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x(&self) -> f64 {
|
||||
self.get::<X>()
|
||||
}
|
||||
#[inline]
|
||||
pub fn y(&self) -> f64 {
|
||||
self.get::<Y>()
|
||||
}
|
||||
#[inline]
|
||||
pub fn z(&self) -> f64 {
|
||||
self.get::<Z>()
|
||||
}
|
||||
|
||||
pub fn get<D: Dimension>(&self) -> f64 {
|
||||
unsafe { self.0.extract_unchecked(D::INDEX) }
|
||||
}
|
||||
|
||||
pub fn set<D: Dimension>(self, value: f64) -> Self {
|
||||
Self(unsafe { self.0.replace_unchecked(D::INDEX, value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn length(&self) -> f64 {
|
||||
self.sq_len().sqrt()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sq_len(&self) -> f64 {
|
||||
(self.0 * self.0).sum()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dot(&self, v: &Vec3) -> f64 {
|
||||
(self.0 * v.0).sum()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cross(&self, v: &Vec3) -> Vec3 {
|
||||
// https://web.archive.org/web/20210412192227/https://geometrian.com/programming/tutorials/cross-product/index.php
|
||||
let tmp0: f64x4 = shuffle!(self.0, [1, 2, 0, 3]);
|
||||
let tmp1: f64x4 = shuffle!(v.0, [2, 0, 1, 3]);
|
||||
let tmp2: f64x4 = shuffle!(self.0, [2, 0, 1, 3]);
|
||||
let tmp3: f64x4 = shuffle!(v.0, [1, 2, 0, 3]);
|
||||
|
||||
Vec3(tmp0 * tmp1 - tmp2 * tmp3)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unit_vector(self) -> Vec3 {
|
||||
self / self.length()
|
||||
}
|
||||
|
||||
pub fn min(self, other: Self) -> Vec3 {
|
||||
Self(self.0.min(other.0))
|
||||
}
|
||||
|
||||
pub fn max(self, other: Self) -> Vec3 {
|
||||
Self(self.0.max(other.0))
|
||||
}
|
||||
|
||||
pub fn min_element(self, other: f64) -> f64 {
|
||||
unsafe { self.0.replace_unchecked(3, other).min_element() }
|
||||
}
|
||||
|
||||
pub fn max_element(self, other: f64) -> f64 {
|
||||
unsafe { self.0.replace_unchecked(3, other).max_element() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sqrt(self) -> Self {
|
||||
Self(self.0.sqrt())
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn add(self, o: Vec3) -> Vec3 {
|
||||
Vec3(self.0 + o.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Vec3 {
|
||||
fn add_assign(&mut self, o: Vec3) {
|
||||
self.0 += o.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn sub(self, o: Vec3) -> Vec3 {
|
||||
Vec3(self.0 - o.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Vec3 {
|
||||
fn sub_assign(&mut self, o: Vec3) {
|
||||
self.0 -= o.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn neg(self) -> Vec3 {
|
||||
Vec3(-self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<Vec3> for Vec3 {
|
||||
fn mul_assign(&mut self, o: Vec3) {
|
||||
self.0 *= o.0
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<f64> for Vec3 {
|
||||
fn mul_assign(&mut self, o: f64) {
|
||||
self.0 *= o
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for Vec3 {
|
||||
type Output = Vec3;
|
||||
fn mul(self, o: f64) -> Vec3 {
|
||||
Vec3(self.0 * o)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vec3> for Vec3 {
|
||||
type Output = Vec3;
|
||||
fn mul(self, o: Vec3) -> Vec3 {
|
||||
Vec3(self.0 * o.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Vec3> for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn div(self, o: Vec3) -> Vec3 {
|
||||
Vec3(self.0 / o.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn div(self, o: f64) -> Vec3 {
|
||||
let o = 1.0 / o;
|
||||
|
||||
Vec3(self.0 * o)
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign<f64> for Vec3 {
|
||||
fn div_assign(&mut self, o: f64) {
|
||||
let o = 1.0 / o;
|
||||
|
||||
self.0 *= o
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Vec3 {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
f.write_fmt(format_args!(
|
||||
"{} {} {}",
|
||||
self.get::<X>(),
|
||||
self.get::<Y>(),
|
||||
self.get::<Z>()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Asf64, B: Asf64, C: Asf64> From<(A, B, C)> for Vec3 {
|
||||
fn from((x, y, z): (A, B, C)) -> Self {
|
||||
Self::new(x, y, z)
|
||||
}
|
||||
}
|
244
src/types/vec3.rs
Normal file
244
src/types/vec3.rs
Normal file
|
@ -0,0 +1,244 @@
|
|||
use std::{
|
||||
fmt::{Display, Formatter, Result as FmtResult},
|
||||
ops::{
|
||||
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, RangeInclusive, Sub,
|
||||
SubAssign,
|
||||
},
|
||||
};
|
||||
|
||||
use rand::Rng;
|
||||
|
||||
use crate::{Asf64, Dimension, X, Y, Z};
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone)]
|
||||
pub struct Vec3([f64; 3]);
|
||||
|
||||
impl Vec3 {
|
||||
#[inline]
|
||||
pub fn new(a: impl Asf64, b: impl Asf64, c: impl Asf64) -> Vec3 {
|
||||
Self([a.as_(), b.as_(), c.as_()])
|
||||
}
|
||||
|
||||
pub fn splat(xyz: impl Asf64) -> Self {
|
||||
Self::new(xyz, xyz, xyz)
|
||||
}
|
||||
|
||||
pub fn random<R: Rng + ?Sized>(rng: &mut R) -> Self {
|
||||
Self(rng.gen())
|
||||
}
|
||||
|
||||
pub fn random_in_range<R: Rng + ?Sized>(rng: &mut R, range: RangeInclusive<f64>) -> Self {
|
||||
Vec3::new(
|
||||
rng.gen_range(range.clone()),
|
||||
rng.gen_range(range.clone()),
|
||||
rng.gen_range(range),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x(&self) -> f64 {
|
||||
self.get::<X>()
|
||||
}
|
||||
#[inline]
|
||||
pub fn y(&self) -> f64 {
|
||||
self.get::<Y>()
|
||||
}
|
||||
#[inline]
|
||||
pub fn z(&self) -> f64 {
|
||||
self.get::<Z>()
|
||||
}
|
||||
|
||||
pub fn get<D: Dimension>(&self) -> f64 {
|
||||
self.0[D::INDEX]
|
||||
}
|
||||
|
||||
pub fn set<D: Dimension>(mut self, value: f64) -> Self {
|
||||
self.0[D::INDEX] = value;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn length(&self) -> f64 {
|
||||
self.sq_len().sqrt()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sq_len(&self) -> f64 {
|
||||
self.x() * self.x() + self.y() * self.y() + self.z() * self.z()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dot(&self, v: &Vec3) -> f64 {
|
||||
self.x() * v.x() + self.y() * v.y() + self.z() * v.z()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cross(&self, v: &Vec3) -> Vec3 {
|
||||
Vec3([
|
||||
self.y() * v.z() - self.z() * v.y(),
|
||||
self.z() * v.x() - self.x() * v.z(),
|
||||
self.x() * v.y() - self.y() * v.x(),
|
||||
])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unit_vector(self) -> Vec3 {
|
||||
self / self.length()
|
||||
}
|
||||
|
||||
pub fn min(self, other: Self) -> Vec3 {
|
||||
Self([
|
||||
self.x().min(other.x()),
|
||||
self.y().min(other.y()),
|
||||
self.z().min(other.z()),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn max(self, other: Self) -> Vec3 {
|
||||
Self([
|
||||
self.x().max(other.x()),
|
||||
self.y().max(other.y()),
|
||||
self.z().max(other.z()),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn min_element(self, other: f64) -> f64 {
|
||||
self.x().min(self.y()).min(self.z()).min(other)
|
||||
}
|
||||
|
||||
pub fn max_element(self, other: f64) -> f64 {
|
||||
self.x().max(self.y()).max(self.z()).max(other)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sqrt(self) -> Self {
|
||||
Vec3::new(self.x().sqrt(), self.y().sqrt(), self.z().sqrt())
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn add(self, o: Vec3) -> Vec3 {
|
||||
Vec3([self.x() + o.x(), self.y() + o.y(), self.z() + o.z()])
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Vec3 {
|
||||
fn add_assign(&mut self, o: Vec3) {
|
||||
self.0[0] += o.0[0];
|
||||
self.0[1] += o.0[1];
|
||||
self.0[2] += o.0[2];
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn sub(self, o: Vec3) -> Vec3 {
|
||||
Vec3([self.x() - o.x(), self.y() - o.y(), self.z() - o.z()])
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Vec3 {
|
||||
fn sub_assign(&mut self, o: Vec3) {
|
||||
self.0[0] -= o.0[0];
|
||||
self.0[1] -= o.0[1];
|
||||
self.0[2] -= o.0[2];
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn neg(self) -> Vec3 {
|
||||
Vec3([-self.x(), -self.y(), -self.z()])
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<Vec3> for Vec3 {
|
||||
fn mul_assign(&mut self, o: Vec3) {
|
||||
self.0[0] *= o.0[0];
|
||||
self.0[1] *= o.0[1];
|
||||
self.0[2] *= o.0[2];
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<f64> for Vec3 {
|
||||
fn mul_assign(&mut self, o: f64) {
|
||||
self.0[0] *= o;
|
||||
self.0[1] *= o;
|
||||
self.0[2] *= o;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for Vec3 {
|
||||
type Output = Vec3;
|
||||
fn mul(self, o: f64) -> Vec3 {
|
||||
Vec3([self.x() * o, self.y() * o, self.z() * o])
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vec3> for Vec3 {
|
||||
type Output = Vec3;
|
||||
fn mul(self, o: Vec3) -> Vec3 {
|
||||
Vec3([self.x() * o.x(), self.y() * o.y(), self.z() * o.z()])
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Vec3> for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn div(self, o: Vec3) -> Vec3 {
|
||||
Vec3([self.x() / o.x(), self.y() / o.y(), self.z() / o.z()])
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn div(self, o: f64) -> Vec3 {
|
||||
let o = 1.0 / o;
|
||||
Vec3([self.x() * o, self.y() * o, self.z() * o])
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign<f64> for Vec3 {
|
||||
fn div_assign(&mut self, o: f64) {
|
||||
let o = 1.0 / o;
|
||||
self.0[0] *= o;
|
||||
self.0[1] *= o;
|
||||
self.0[2] *= o;
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Vec3 {
|
||||
type Output = f64;
|
||||
|
||||
fn index(&self, q: usize) -> &f64 {
|
||||
&self.0[q]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<usize> for Vec3 {
|
||||
fn index_mut(&mut self, q: usize) -> &mut f64 {
|
||||
&mut self.0[q]
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Vec3 {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
f.write_fmt(format_args!(
|
||||
"{} {} {}",
|
||||
self.get::<X>(),
|
||||
self.get::<Y>(),
|
||||
self.get::<Z>()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Asf64, B: Asf64, C: Asf64> From<(A, B, C)> for Vec3 {
|
||||
fn from((x, y, z): (A, B, C)) -> Self {
|
||||
Self::new(x, y, z)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user