1
0

Added back codebase from previous projects

This commit is contained in:
Ishan Jain 2023-05-31 11:36:19 +05:30
parent 3e53256e53
commit 96e103ee61
Signed by: ishan
GPG Key ID: 0506DB2A1CC75C27
46 changed files with 3776 additions and 26 deletions

5
.gitignore vendored
View File

@ -1 +1,4 @@
/target target/
*.ppm
*.png
!renders/*

345
Cargo.lock generated
View File

@ -2,6 +2,42 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 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]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -9,10 +45,102 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "getrandom" name = "cmake"
version = "0.2.3" version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@ -20,10 +148,134 @@ dependencies = [
] ]
[[package]] [[package]]
name = "libc" name = "hermit-abi"
version = "0.2.107" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
@ -72,14 +324,83 @@ dependencies = [
] ]
[[package]] [[package]]
name = "raytracing-the-rest-of-your-life" name = "rayon"
version = "0.1.0" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
dependencies = [ dependencies = [
"rand", "either",
"rayon-core",
] ]
[[package]] [[package]]
name = "wasi" name = "rayon-core"
version = "0.10.2+wasi-snapshot-preview1" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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"

View File

@ -4,4 +4,13 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [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

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

36
src/aabb.rs Normal file
View 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
View 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
}

View 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
View 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,
)
}
}

View 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,
)
}
}

View 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
View 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
View 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),
}
}
}

View 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
View 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
View 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
View 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),
}
}
}

View 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
View 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
View 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
}
}

View 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))
}
}

View 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;

View 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))
}
}

View 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,
}
}
}

View 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
View 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))
}
}

View 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)
}
}

View File

@ -0,0 +1,3 @@
mod constant_medium;
pub use constant_medium::ConstantMedium;

View File

@ -1,20 +1,195 @@
use rand::Rng; #![allow(clippy::suspicious_arithmetic_impl)]
#[inline] mod aabb;
fn pdf(x: f64) -> f64 { mod camera;
3.0 * x * x / 8.0 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() { #[cfg(feature = "gui")]
let mut rng = rand::thread_rng(); fn run(mut width: usize, mut height: usize) -> Result<(), String> {
const N: u64 = 100000; use sdl2::{
let mut sum = 0.0; event::{Event, WindowEvent},
keyboard::Keycode,
pixels::PixelFormatEnum,
};
for _ in 0..N { let sdl_ctx = sdl2::init()?;
let x: f64 = rng.gen_range(0.0f64..=8.0).powf(1.0 / 3.0); 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);
} }

View 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())),
)
}
}
}

View 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)
}
}

View 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(),
)),
)
}
}

View 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
View 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
View 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
View 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)
}
}
}

View 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
View 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
View 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);
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}
}