Compare commits
34 Commits
4b8b556ea3
...
master
Author | SHA1 | Date | |
---|---|---|---|
dc19f09da9 | |||
ab349b1d7e | |||
cee009f5a8 | |||
526e92f582
|
|||
a0eda53be1 | |||
a63e001edd | |||
a552c4721f | |||
8501bb3a7a | |||
30b972ce5c | |||
560b921b3a | |||
c90b185b8d | |||
c6b0fb59ae | |||
35643fef39
|
|||
f5f46a7a0b | |||
f2a8db6430 | |||
9ce878a47a | |||
c2d16937ff | |||
429b87e912 | |||
14b5b2b125 | |||
11c4d2c991 | |||
19107f20c9 | |||
d69fd86f8b | |||
38a67a6890 | |||
0e72e21f92 | |||
d5d24cee03 | |||
d10292622a | |||
9564ee92b6 | |||
de64b56b38 | |||
6f6fc5e375 | |||
28105145c4 | |||
93cbb67b39 | |||
dbb0ab2b91 | |||
3de572d4a9
|
|||
558851b5f6
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@
|
|||||||
.idea
|
.idea
|
||||||
*.iml
|
*.iml
|
||||||
.vscode/
|
.vscode/
|
||||||
|
*.png
|
||||||
|
169
Cargo.lock
generated
169
Cargo.lock
generated
@ -2,18 +2,74 @@
|
|||||||
# 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 = "adler32"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
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.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c53dfa917ec274df8ed3c572698f381a24eef2efba9492d797301b72b6db408a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.5"
|
version = "0.5.5"
|
||||||
@ -59,6 +115,15 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deflate"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f"
|
||||||
|
dependencies = [
|
||||||
|
"adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
@ -85,6 +150,31 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.24.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28edd9d7bc256be2502e325ac0628bde30b7001b9b52e0abe31a1a9dc2701212"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder",
|
||||||
|
"color_quant",
|
||||||
|
"jpeg-decoder",
|
||||||
|
"num-iter",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
"png",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jpeg-decoder"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b"
|
||||||
|
dependencies = [
|
||||||
|
"rayon",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.126"
|
version = "0.2.126"
|
||||||
@ -100,6 +190,56 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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-iter"
|
||||||
|
version = "0.1.43"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"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]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
@ -116,6 +256,18 @@ version = "1.12.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
|
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.17.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crc32fast",
|
||||||
|
"deflate",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
@ -180,8 +332,10 @@ dependencies = [
|
|||||||
name = "rustracer"
|
name = "rustracer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"image",
|
||||||
"rand",
|
"rand",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"tobj",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -190,6 +344,21 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tobj"
|
||||||
|
version = "3.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "deacee3abcc4fd8ff3f0f7c08d4583ab51753ed1d5a3acacd6d5773f640c27d6"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
@ -2,9 +2,15 @@
|
|||||||
name = "rustracer"
|
name = "rustracer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
include = ["/src"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
image = { version = "0.24.2", default-features = false, features=["png", "jpeg", "jpeg_rayon"] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rayon = "1.5.3"
|
rayon = "1.5.3"
|
||||||
|
tobj = "3.2.3"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = 1
|
||||||
|
50
src/aabb.rs
Normal file
50
src/aabb.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
use crate::vec3::{Point3};
|
||||||
|
use crate::ray::{Ray};
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
#[derive(Copy, Debug, Clone)]
|
||||||
|
pub struct Aabb {
|
||||||
|
pub minimum: Point3,
|
||||||
|
pub maximum: Point3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Aabb {
|
||||||
|
pub fn min(&self) -> Point3 { self.minimum }
|
||||||
|
pub fn max(&self) -> Point3 { self.maximum }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Aabb {
|
||||||
|
fn default() -> Self {
|
||||||
|
Aabb {minimum: Point3::default(), maximum: Point3::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Aabb {
|
||||||
|
pub fn hit(&self, ray: &Ray, mut t_min: f64, mut t_max: f64) -> bool {
|
||||||
|
for a in 0..3 {
|
||||||
|
let inv_d = 1.0 / ray.direction()[a];
|
||||||
|
let mut t0 = (self.minimum[a] - ray.origin()[a]) * inv_d;
|
||||||
|
let mut t1 = (self.maximum[a] - ray.origin()[a]) * inv_d;
|
||||||
|
if inv_d < 0.0 {
|
||||||
|
mem::swap(&mut t0, &mut t1);
|
||||||
|
}
|
||||||
|
t_min = t_min.max(t0);
|
||||||
|
t_max = t_max.min(t1);
|
||||||
|
if t_max <= t_min {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
pub fn surrounding(&self, other: &Aabb) -> Aabb {
|
||||||
|
let minimum = Point3::new(
|
||||||
|
self.minimum.x().min(other.minimum.x()),
|
||||||
|
self.minimum.y().min(other.minimum.y()),
|
||||||
|
self.minimum.z().min(other.minimum.z()));
|
||||||
|
let maximum = Point3::new(
|
||||||
|
self.maximum.x().max(other.maximum.x()),
|
||||||
|
self.maximum.y().max(other.maximum.y()),
|
||||||
|
self.maximum.z().max(other.maximum.z()));
|
||||||
|
Aabb {minimum, maximum}
|
||||||
|
}
|
||||||
|
}
|
93
src/bvh.rs
Normal file
93
src/bvh.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
use crate::hittable::{HittableList, Hittable, HitRecord, HittableObject};
|
||||||
|
use crate::aabb::Aabb;
|
||||||
|
use std::cmp;
|
||||||
|
use crate::Ray;
|
||||||
|
|
||||||
|
// https://github.com/fralken/ray-tracing-the-next-week/blob/master/src/bvh.rs
|
||||||
|
|
||||||
|
enum BVHNode {
|
||||||
|
Branch { left: Box<BVH>, right: Box<BVH> },
|
||||||
|
Leaf(HittableObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BVH {
|
||||||
|
tree: BVHNode,
|
||||||
|
bounding_box: Aabb
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BVH {
|
||||||
|
pub fn new(mut objects: HittableList, time0: f64, time1: f64) -> Self {
|
||||||
|
fn box_compare(time0: f64, time1: f64, axis: usize) -> impl FnMut(&HittableObject, &HittableObject) -> cmp::Ordering {
|
||||||
|
move |a, b| {
|
||||||
|
let a_bbox = a.bounding_box(time0, time1);
|
||||||
|
let b_bbox = b.bounding_box(time0, time1);
|
||||||
|
if let (Some(a), Some(b)) = (a_bbox, b_bbox) {
|
||||||
|
let ac = a.min()[axis] + a.max()[axis];
|
||||||
|
let bc = b.min()[axis] + b.max()[axis];
|
||||||
|
ac.partial_cmp(&bc).unwrap()
|
||||||
|
} else {
|
||||||
|
panic!("no bounding box in bvh node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis_range(objects: &HittableList, time0: f64, time1: f64, axis: usize) -> f64 {
|
||||||
|
let (min, max) = objects.iter().fold((f64::MAX, f64::MIN), |(bmin, bmax), hit| {
|
||||||
|
if let Some(aabb) = hit.bounding_box(time0, time1) {
|
||||||
|
(bmin.min(aabb.min()[axis]), bmax.max(aabb.max()[axis]))
|
||||||
|
} else {
|
||||||
|
(bmin, bmax)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
max - min
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut axis_ranges: Vec<(usize, f64)> = (0..3)
|
||||||
|
.map(|a| (a, axis_range(&objects, time0, time1, a)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
axis_ranges.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||||
|
|
||||||
|
let axis = axis_ranges[0].0;
|
||||||
|
|
||||||
|
objects.sort_unstable_by(box_compare(time0, time1, axis));
|
||||||
|
let len = objects.len();
|
||||||
|
match len {
|
||||||
|
0 => panic!("no elements in scene"),
|
||||||
|
1 => {
|
||||||
|
let leaf = objects.pop().unwrap();
|
||||||
|
if let Some(bounding_box) = leaf.bounding_box(time0, time1) {
|
||||||
|
BVH { tree: BVHNode::Leaf(leaf), bounding_box }
|
||||||
|
} else {
|
||||||
|
panic!("no bounding box in bvh node")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let right = BVH::new(objects.drain(len / 2..).collect(), time0, time1);
|
||||||
|
let left = BVH::new(objects, time0, time1);
|
||||||
|
let bounding_box = left.bounding_box.surrounding(&right.bounding_box);
|
||||||
|
BVH { tree: BVHNode::Branch { left: Box::new(left), right: Box::new(right) }, bounding_box }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hittable for BVH {
|
||||||
|
fn hit(&self, ray: &Ray, t_min: f64, mut t_max: f64) -> Option<HitRecord> {
|
||||||
|
if !self.bounding_box.hit(ray, t_min, t_max) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match &self.tree {
|
||||||
|
BVHNode::Leaf(leaf) => leaf.hit(ray, t_min, t_max),
|
||||||
|
BVHNode::Branch { left, right } => {
|
||||||
|
let hit_left = left.hit(ray, t_min, t_max);
|
||||||
|
if let Some(hl) = &hit_left { t_max = hl.t };
|
||||||
|
let hit_right = right.hit(ray, t_min, t_max);
|
||||||
|
if hit_right.is_some() { hit_right } else { hit_left }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn bounding_box(&self, _time0: f64, _time1: f64) -> Option<Aabb> {
|
||||||
|
Some(self.bounding_box)
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
use rand::Rng;
|
||||||
|
|
||||||
use crate::{Point3, Ray, Vec3};
|
use crate::{Point3, Ray, Vec3};
|
||||||
|
|
||||||
pub struct Camera {
|
pub struct Camera {
|
||||||
@ -11,18 +13,22 @@ pub struct Camera {
|
|||||||
lens_radius: f64,
|
lens_radius: f64,
|
||||||
u: Vec3,
|
u: Vec3,
|
||||||
v: Vec3,
|
v: Vec3,
|
||||||
w: Vec3
|
w: Vec3,
|
||||||
|
time0: f64,
|
||||||
|
time1: f64
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Camera {
|
impl Camera {
|
||||||
pub fn get_ray(&self, s: f64, t: f64) -> Ray {
|
pub fn get_ray(&self, s: f64, t: f64) -> Ray {
|
||||||
let rd = self.lens_radius * Vec3::random_unit_disk();
|
let rd = self.lens_radius * Vec3::random_unit_disk();
|
||||||
let offset = self.u * rd.x() + self.v * rd.y();
|
let offset = self.u * rd.x() + self.v * rd.y();
|
||||||
|
let time = rand::thread_rng().gen_range(self.time0..self.time1);
|
||||||
Ray::new(
|
Ray::new(
|
||||||
self.origin + offset,
|
self.origin + offset,
|
||||||
self.lower_left_corner + s*self.horizontal + t*self.vertical - self.origin - offset)
|
self.lower_left_corner + s*self.horizontal + t*self.vertical - self.origin - offset,
|
||||||
|
time)
|
||||||
}
|
}
|
||||||
pub fn new(
|
pub fn still(
|
||||||
look_from: Point3,
|
look_from: Point3,
|
||||||
look_at: Point3,
|
look_at: Point3,
|
||||||
up: Vec3,
|
up: Vec3,
|
||||||
@ -31,6 +37,29 @@ impl Camera {
|
|||||||
aperture: f64,
|
aperture: f64,
|
||||||
focus_dist: f64) -> Self {
|
focus_dist: f64) -> Self {
|
||||||
|
|
||||||
|
Camera::new(
|
||||||
|
look_from,
|
||||||
|
look_at,
|
||||||
|
up,
|
||||||
|
aspect_ratio,
|
||||||
|
vfov,
|
||||||
|
aperture,
|
||||||
|
focus_dist,
|
||||||
|
0.0,
|
||||||
|
0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
look_from: Point3,
|
||||||
|
look_at: Point3,
|
||||||
|
up: Vec3,
|
||||||
|
aspect_ratio: f64,
|
||||||
|
vfov: f64,
|
||||||
|
aperture: f64,
|
||||||
|
focus_dist: f64,
|
||||||
|
time0: f64,
|
||||||
|
time1: f64) -> Self {
|
||||||
|
|
||||||
let theta = vfov.to_radians();
|
let theta = vfov.to_radians();
|
||||||
let h = (theta / 2.0).tan();
|
let h = (theta / 2.0).tan();
|
||||||
let viewport_height = 2.0 * h;
|
let viewport_height = 2.0 * h;
|
||||||
@ -53,7 +82,11 @@ impl Camera {
|
|||||||
lens_radius: aperture / 2.0,
|
lens_radius: aperture / 2.0,
|
||||||
u,
|
u,
|
||||||
v,
|
v,
|
||||||
w
|
w,
|
||||||
|
time0,
|
||||||
|
time1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn aspect_ratio(&self) -> f64 { self.aspect_ratio }
|
||||||
}
|
}
|
||||||
|
88
src/constant_medium.rs
Normal file
88
src/constant_medium.rs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use crate::{Aabb, Color, Material, Ray, Vec3};
|
||||||
|
use crate::hittable::{HitRecord, Hittable};
|
||||||
|
use crate::isotropic::Isotropic;
|
||||||
|
use crate::texture::Texture;
|
||||||
|
|
||||||
|
/*
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct HitRecord<'material> {
|
||||||
|
pub point: Point3,
|
||||||
|
pub normal: Vec3,
|
||||||
|
pub t: f64,
|
||||||
|
pub u: f64,
|
||||||
|
pub v: f64,
|
||||||
|
pub front_face: bool,
|
||||||
|
pub material: &'material Arc<Material>
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub struct ConstantMedium<H: Hittable> {
|
||||||
|
boundary: H,
|
||||||
|
phase_function: Arc<Material>,
|
||||||
|
neg_inv_density: f64
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: Hittable,> ConstantMedium<H> {
|
||||||
|
pub fn new(boundary: H, phase_function: Arc<Material>, density: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
boundary,
|
||||||
|
phase_function,
|
||||||
|
neg_inv_density: -1.0 / density
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn textured(boundary: H, texture: Arc<dyn Texture>, density: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
boundary,
|
||||||
|
phase_function: Arc::new(Material::Isotropic(Isotropic::from(texture))),
|
||||||
|
neg_inv_density: -1.0 / density
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn colored(boundary: H, color: Color, density: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
boundary,
|
||||||
|
phase_function: Arc::new(Material::Isotropic(Isotropic::from(color))),
|
||||||
|
neg_inv_density: -1.0 / density
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: Hittable>Hittable for ConstantMedium<H>{
|
||||||
|
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||||
|
let rec1 = self.boundary.hit(ray, f64::NEG_INFINITY, f64::INFINITY);
|
||||||
|
if rec1.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut rec1 = rec1.unwrap();
|
||||||
|
|
||||||
|
let rec2 = self.boundary.hit(ray, rec1.t+0.0001, f64::INFINITY);
|
||||||
|
if rec2.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut rec2 = rec2.unwrap();
|
||||||
|
if rec1.t < t_min { rec1.t = t_min; }
|
||||||
|
if rec2.t > t_max { rec2.t = t_max; }
|
||||||
|
if rec1.t > rec2.t { return None; }
|
||||||
|
if rec1.t < t_min { rec1.t = 0.0; }
|
||||||
|
|
||||||
|
let ray_length = ray.direction().length();
|
||||||
|
let distance_inside_boundary = (rec2.t - rec1.t) * ray_length;
|
||||||
|
let hit_distance = self.neg_inv_density * rand::random::<f64>().ln();
|
||||||
|
|
||||||
|
if hit_distance > distance_inside_boundary { return None; }
|
||||||
|
let t = rec1.t + hit_distance / ray_length;
|
||||||
|
Some(HitRecord {
|
||||||
|
point: ray.at(t),
|
||||||
|
normal: Vec3::new(1.0, 0.0, 0.0), // arbitrary
|
||||||
|
t,
|
||||||
|
u: 0.0,
|
||||||
|
v: 0.0,
|
||||||
|
front_face: true, // also arbitrary
|
||||||
|
material: &self.phase_function
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, time0: f64, time1: f64) -> Option<Aabb> {
|
||||||
|
self.boundary.bounding_box(time0, time1)
|
||||||
|
}
|
||||||
|
}
|
88
src/cuboid.rs
Normal file
88
src/cuboid.rs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use crate::{Aabb, HittableList, Material, Plane, Point3, Ray, Rect2D};
|
||||||
|
use crate::hittable::{HitRecord, Hittable};
|
||||||
|
|
||||||
|
pub struct Cuboid {
|
||||||
|
minimum: Point3,
|
||||||
|
maximum: Point3,
|
||||||
|
sides: HittableList
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cuboid {
|
||||||
|
pub fn new(p0: Point3, p1: Point3, material: Arc<Material>) -> Self {
|
||||||
|
let mut sides: HittableList = Vec::with_capacity(6);
|
||||||
|
sides.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XY,
|
||||||
|
p0.x(),
|
||||||
|
p1.x(),
|
||||||
|
p0.y(),
|
||||||
|
p1.y(),
|
||||||
|
p1.z(),
|
||||||
|
material.clone()
|
||||||
|
)));
|
||||||
|
sides.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XY,
|
||||||
|
p0.x(),
|
||||||
|
p1.x(),
|
||||||
|
p0.y(),
|
||||||
|
p1.y(),
|
||||||
|
p0.z(),
|
||||||
|
material.clone()
|
||||||
|
)));
|
||||||
|
sides.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XZ,
|
||||||
|
p0.x(),
|
||||||
|
p1.x(),
|
||||||
|
p0.z(),
|
||||||
|
p1.z(),
|
||||||
|
p1.y(),
|
||||||
|
material.clone()
|
||||||
|
)));
|
||||||
|
sides.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XZ,
|
||||||
|
p0.x(),
|
||||||
|
p1.x(),
|
||||||
|
p0.z(),
|
||||||
|
p1.z(),
|
||||||
|
p0.y(),
|
||||||
|
material.clone()
|
||||||
|
)));
|
||||||
|
sides.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::YZ,
|
||||||
|
p0.y(),
|
||||||
|
p1.y(),
|
||||||
|
p0.z(),
|
||||||
|
p1.z(),
|
||||||
|
p1.x(),
|
||||||
|
material.clone()
|
||||||
|
)));
|
||||||
|
sides.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::YZ,
|
||||||
|
p0.y(),
|
||||||
|
p1.y(),
|
||||||
|
p0.z(),
|
||||||
|
p1.z(),
|
||||||
|
p0.x(),
|
||||||
|
material.clone()
|
||||||
|
)));
|
||||||
|
|
||||||
|
Cuboid {
|
||||||
|
minimum: p0,
|
||||||
|
maximum: p1,
|
||||||
|
sides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hittable for Cuboid {
|
||||||
|
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||||
|
ray.hit_world(&self.sides, t_min, t_max)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, _time0: f64, _time1: f64) -> Option<Aabb> {
|
||||||
|
Some(Aabb {
|
||||||
|
minimum: self.minimum,
|
||||||
|
maximum: self.maximum
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
152
src/hittable.rs
152
src/hittable.rs
@ -1,38 +1,86 @@
|
|||||||
use crate::{Point3, Ray, Vec3};
|
use crate::{Point3, Ray, Vec3};
|
||||||
use crate::material::{Material};
|
use crate::material::{Material};
|
||||||
|
use crate::aabb::Aabb;
|
||||||
|
use std::f64::consts::PI;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
pub type HittableObject = Arc<dyn Hittable + Sync + Send>;
|
||||||
|
pub type HittableList = Vec<HittableObject>;
|
||||||
|
|
||||||
|
//#[derive(Debug, Clone, Copy)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub struct HitRecord<'material> {
|
pub struct HitRecord<'material> {
|
||||||
pub point: Point3,
|
pub point: Point3,
|
||||||
pub normal: Vec3,
|
pub normal: Vec3,
|
||||||
pub t: f64,
|
pub t: f64,
|
||||||
|
pub u: f64,
|
||||||
|
pub v: f64,
|
||||||
pub front_face: bool,
|
pub front_face: bool,
|
||||||
pub material: &'material Material
|
pub material: &'material Arc<Material>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'material> HitRecord<'material> {
|
||||||
|
pub fn normalized(&mut self, ray: &Ray) {
|
||||||
|
let dot = ray.direction().dot(&self.normal);
|
||||||
|
let front_face = dot < 0.0;
|
||||||
|
let normal = if front_face { self.normal } else { -self.normal };
|
||||||
|
self.normal = normal;
|
||||||
|
self.front_face = front_face;
|
||||||
|
}
|
||||||
|
/*pub fn normalized(& self) -> Self {
|
||||||
|
let dot = ray.direction().dot(self.normal);
|
||||||
|
let front_face = dot < 0.0;
|
||||||
|
let normal = if front_face { self.normal } else { -self.normal };
|
||||||
|
HitRecord {
|
||||||
|
point: self.point,
|
||||||
|
normal,
|
||||||
|
t: self.t,
|
||||||
|
u: self.u,
|
||||||
|
v: self.v,
|
||||||
|
front_face,
|
||||||
|
material: self.material
|
||||||
|
}
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Hittable {
|
pub trait Hittable {
|
||||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord>;
|
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord>;
|
||||||
|
fn bounding_box(&self, time0: f64, time1: f64) -> Option<Aabb>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#[derive(Clone)]
|
||||||
pub struct Sphere {
|
pub struct Sphere {
|
||||||
pub center: Point3,
|
pub center: Point3,
|
||||||
pub radius: f64,
|
pub radius: f64,
|
||||||
pub material: Material
|
pub material: Arc<Material>
|
||||||
}
|
}
|
||||||
impl Sphere {
|
impl Sphere {
|
||||||
pub fn new(center: Point3, radius: f64, material: Material) -> Sphere {
|
pub fn new(center: Point3, radius: f64, material: Arc<Material>) -> Sphere {
|
||||||
Sphere { center, radius, material }
|
Sphere { center, radius, material }
|
||||||
}
|
}
|
||||||
|
pub fn uv(point: &Point3) -> (f64, f64) {
|
||||||
|
// p: a given point on the sphere of radius one, centered at the origin.
|
||||||
|
// u: returned value [0,1] of angle around the Y axis from X=-1.
|
||||||
|
// v: returned value [0,1] of angle from Y=-1 to Y=+1.
|
||||||
|
// <1 0 0> yields <0.50 0.50> <-1 0 0> yields <0.00 0.50>
|
||||||
|
// <0 1 0> yields <0.50 1.00> < 0 -1 0> yields <0.50 0.00>
|
||||||
|
// <0 0 1> yields <0.25 0.50> < 0 0 -1> yields <0.75 0.50>
|
||||||
|
|
||||||
|
let theta = -(point.y()).acos();
|
||||||
|
let phi = -(point.z()).atan2(point.x()) + PI;
|
||||||
|
|
||||||
|
(phi / (2.0 * PI), theta / PI)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl Default for Sphere {
|
impl Default for Sphere {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Sphere {
|
Sphere {
|
||||||
center: Point3::default(),
|
center: Point3::default(),
|
||||||
radius: 0.0,
|
radius: 0.0,
|
||||||
material: Material::default()
|
material: Arc::new(Material::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hittable for Sphere {
|
impl Hittable for Sphere {
|
||||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||||
let oc = &ray.origin() - &self.center;
|
let oc = &ray.origin() - &self.center;
|
||||||
@ -50,15 +98,105 @@ impl Hittable for Sphere {
|
|||||||
}
|
}
|
||||||
let point = ray.at(root);
|
let point = ray.at(root);
|
||||||
let normal = (point - self.center) / self.radius;
|
let normal = (point - self.center) / self.radius;
|
||||||
|
let (u, v) = Sphere::uv(&normal);
|
||||||
|
let mut rec = HitRecord {
|
||||||
|
point,
|
||||||
|
normal,
|
||||||
|
t: root,
|
||||||
|
u,
|
||||||
|
v,
|
||||||
|
front_face: false, // Will be set during normalied()
|
||||||
|
material: &self.material
|
||||||
|
};
|
||||||
|
rec.normalized(ray);
|
||||||
|
Some(rec)
|
||||||
|
}
|
||||||
|
fn bounding_box(&self, _time0: f64, _time1: f64) -> Option<Aabb> {
|
||||||
|
let s = Vec3::new(self.radius, self.radius, self.radius);
|
||||||
|
Some(Aabb {
|
||||||
|
minimum: self.center - s,
|
||||||
|
maximum: self.center + s,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#[derive(Clone)]
|
||||||
|
pub struct MovableSphere {
|
||||||
|
pub center0: Point3,
|
||||||
|
pub center1: Point3,
|
||||||
|
pub radius: f64,
|
||||||
|
pub material: Arc<Material>,
|
||||||
|
pub time0: f64,
|
||||||
|
pub time1: f64,
|
||||||
|
}
|
||||||
|
impl MovableSphere {
|
||||||
|
pub fn new(
|
||||||
|
center0: Point3,
|
||||||
|
center1: Point3,
|
||||||
|
radius: f64,
|
||||||
|
material: Arc<Material>,
|
||||||
|
time0: f64,
|
||||||
|
time1: f64) -> Self {
|
||||||
|
MovableSphere { center0, center1, radius, material, time0, time1 }
|
||||||
|
}
|
||||||
|
pub fn center(&self, time: f64) -> Point3 {
|
||||||
|
self.center0 + ((time - self.time0) / (self.time1 - self.time0))*(self.center1 - self.center0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Default for MovableSphere {
|
||||||
|
fn default() -> Self {
|
||||||
|
MovableSphere {
|
||||||
|
center0: Point3::default(),
|
||||||
|
center1: Point3::default(),
|
||||||
|
radius: 0.0,
|
||||||
|
material: Arc::new(Material::default()),
|
||||||
|
time0: 0.0,
|
||||||
|
time1: 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hittable for MovableSphere {
|
||||||
|
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().length_squared();
|
||||||
|
let half_b = oc.dot(&ray.direction());
|
||||||
|
let c = oc.length_squared() - &self.radius * &self.radius;
|
||||||
|
let discriminant = half_b * half_b - a * c;
|
||||||
|
if discriminant < 0.0 { return None; }
|
||||||
|
|
||||||
|
let sqrtd = discriminant.sqrt();
|
||||||
|
let mut root = (-half_b - sqrtd) / a;
|
||||||
|
if root < t_min || t_max < root {
|
||||||
|
root = (-half_b + sqrtd) / a;
|
||||||
|
if root < t_min || t_max < root { return None; }
|
||||||
|
}
|
||||||
|
let point = ray.at(root);
|
||||||
|
let normal = (point - self.center(ray.time())) / self.radius;
|
||||||
let dot = ray.direction().dot(&normal);
|
let dot = ray.direction().dot(&normal);
|
||||||
let front_face = dot < 0.0;
|
let front_face = dot < 0.0;
|
||||||
|
let (u, v) = Sphere::uv(&normal);
|
||||||
let normal = if front_face { normal } else { -normal };
|
let normal = if front_face { normal } else { -normal };
|
||||||
Some(HitRecord {
|
Some(HitRecord {
|
||||||
point,
|
point,
|
||||||
normal,
|
normal,
|
||||||
t: root,
|
t: root,
|
||||||
|
u,
|
||||||
|
v,
|
||||||
front_face,
|
front_face,
|
||||||
material: &self.material
|
material: &self.material,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
fn bounding_box(&self, time0: f64, time1: f64) -> Option<Aabb> {
|
||||||
|
let s = Vec3::new(self.radius, self.radius, self.radius);
|
||||||
|
let center0 = self.center(time0);
|
||||||
|
let center1 = self.center(time1);
|
||||||
|
Some(Aabb {
|
||||||
|
minimum: center0 - s,
|
||||||
|
maximum: center0 + s,
|
||||||
|
}.surrounding(&Aabb {
|
||||||
|
minimum: center1 - s,
|
||||||
|
maximum: center1 + s
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
48
src/image_texture.rs
Normal file
48
src/image_texture.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use image::{RgbImage};
|
||||||
|
use crate::{Color, Point3};
|
||||||
|
use crate::texture::Texture;
|
||||||
|
|
||||||
|
pub struct ImageTexture {
|
||||||
|
image: Option<RgbImage>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageTexture {
|
||||||
|
pub fn new(path: &str) -> Self {
|
||||||
|
let image = image::open(path)
|
||||||
|
.map(|i|i.into_rgb8())
|
||||||
|
.ok();
|
||||||
|
Self {
|
||||||
|
image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn value_from_image(image: &RgbImage, u: f64, v: f64) -> Color {
|
||||||
|
// Clamp input texture coordinates to [0,1] x [1,0]
|
||||||
|
let u = u.clamp(0.0, 1.0);
|
||||||
|
let v = v.abs().clamp(0.0, 1.0); // Flip V to image coordinates
|
||||||
|
|
||||||
|
let i = match (u * image.width() as f64) as i32 {
|
||||||
|
i if i >= image.width() as i32 => image.width() as i32 - 1,
|
||||||
|
i => i
|
||||||
|
};
|
||||||
|
let j = match (v * image.height() as f64) as i32 {
|
||||||
|
j if j >= image.height() as i32 => image.height() as i32 - 1,
|
||||||
|
j => j
|
||||||
|
};
|
||||||
|
let color_scale = 1.0 / 255.0;
|
||||||
|
let pixel = image.get_pixel(i as u32, j as u32);
|
||||||
|
let x = pixel.0[0] as f64 * color_scale;
|
||||||
|
let y = pixel.0[1] as f64 * color_scale;
|
||||||
|
let z = pixel.0[2] as f64 * color_scale;
|
||||||
|
Color::new(x, y, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Texture for ImageTexture {
|
||||||
|
fn value(&self, u: f64, v: f64, _point: &Point3) -> Color {
|
||||||
|
match &self.image {
|
||||||
|
Some(i) => ImageTexture::value_from_image(i, u, v),
|
||||||
|
// If we have no texture data, then return solid cyan as a debugging aid.
|
||||||
|
_ => Color::new(0.0, 1.0, 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
src/isotropic.rs
Normal file
33
src/isotropic.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use crate::hittable::HitRecord;
|
||||||
|
use crate::{Color, Ray, Vec3};
|
||||||
|
use crate::texture::{SolidColor, Texture};
|
||||||
|
|
||||||
|
pub struct Isotropic {
|
||||||
|
pub albedo: Arc<dyn Texture>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Isotropic {
|
||||||
|
pub fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
|
||||||
|
Some((
|
||||||
|
Some(
|
||||||
|
Ray::new(
|
||||||
|
hit_record.point,
|
||||||
|
Vec3::random_in_unit_sphere(),
|
||||||
|
ray.time())),
|
||||||
|
self.albedo.value(hit_record.u, hit_record.v, &hit_record.point)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for Isotropic {
|
||||||
|
fn from(albedo: Color) -> Self {
|
||||||
|
let texture = SolidColor::from(albedo);
|
||||||
|
Isotropic { albedo: Arc::new(texture) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Arc<dyn Texture>> for Isotropic {
|
||||||
|
fn from(albedo: Arc<dyn Texture>) -> Self {
|
||||||
|
Isotropic { albedo }
|
||||||
|
}
|
||||||
|
}
|
849
src/main.rs
849
src/main.rs
@ -1,14 +1,28 @@
|
|||||||
use std::sync::Mutex;
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use crate::camera::Camera;
|
use crate::camera::Camera;
|
||||||
use crate::hittable::{Hittable, Sphere};
|
use crate::hittable::{Sphere, HittableList, HittableObject};
|
||||||
use crate::output::{Output, P3};
|
use crate::output::{Output, PNG};
|
||||||
use crate::ray::Ray;
|
use crate::ray::Ray;
|
||||||
use crate::vec3::{Color, Point3, Vec3};
|
use crate::vec3::{Color, Point3, Vec3};
|
||||||
|
use hittable::MovableSphere;
|
||||||
|
use rand::Rng;
|
||||||
use rand::distributions::{Distribution, Uniform};
|
use rand::distributions::{Distribution, Uniform};
|
||||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||||
use crate::material::{Dielectric, Lambertian, Material, Metal};
|
use crate::material::{Dielectric, DiffuseLight, Lambertian, Material, Metal};
|
||||||
|
use crate::aabb::Aabb;
|
||||||
|
use crate::bvh::BVH;
|
||||||
|
use crate::constant_medium::ConstantMedium;
|
||||||
|
use crate::cuboid::Cuboid;
|
||||||
|
use crate::image_texture::ImageTexture;
|
||||||
|
use crate::noise::NoiseTexture;
|
||||||
|
use crate::obj::obj_to_hitable;
|
||||||
|
use crate::perlin::Perlin;
|
||||||
|
use crate::texture::CheckerTexture;
|
||||||
|
use crate::rect::{Plane, Rect2D};
|
||||||
|
use crate::rotate_y::RotateY;
|
||||||
|
use crate::translate::Translate;
|
||||||
|
use crate::triangle::Triangle;
|
||||||
|
|
||||||
mod vec3;
|
mod vec3;
|
||||||
mod ray;
|
mod ray;
|
||||||
@ -16,17 +30,558 @@ mod hittable;
|
|||||||
mod material;
|
mod material;
|
||||||
mod camera;
|
mod camera;
|
||||||
mod output;
|
mod output;
|
||||||
|
mod aabb;
|
||||||
|
mod bvh;
|
||||||
|
mod texture;
|
||||||
|
mod perlin;
|
||||||
|
mod noise;
|
||||||
|
mod image_texture;
|
||||||
|
mod rect;
|
||||||
|
mod cuboid;
|
||||||
|
mod translate;
|
||||||
|
mod rotate_y;
|
||||||
|
mod constant_medium;
|
||||||
|
mod isotropic;
|
||||||
|
mod obj;
|
||||||
|
mod triangle;
|
||||||
|
|
||||||
fn random_scene() -> Vec<Box<dyn Hittable + Sync>> {
|
// Image
|
||||||
let mut world:Vec<Box<dyn Hittable + Sync>> = Vec::new();
|
const DEFAULT_ASPECT_RATIO: f64 = 3.0 / 2.0;
|
||||||
|
const IMAGE_WIDTH: usize = 300;
|
||||||
|
const SAMPLES_PER_PIXEL: i32 = 100;
|
||||||
|
const MAX_DEPTH: i32 = 50;
|
||||||
|
|
||||||
let material_ground = Material::Lambertian(Lambertian::new(Color::new(0.5, 0.5, 0.5)));
|
struct Scene {
|
||||||
|
pub world: HittableList,
|
||||||
|
pub cam: Camera,
|
||||||
|
pub background: Color
|
||||||
|
}
|
||||||
|
|
||||||
|
fn obj(path: &str) -> Scene {
|
||||||
|
let object = obj_to_hitable(&std::path::Path::new(path));
|
||||||
|
print!("Object hitbox: {:#?}", object.bounding_box(0.0, 1.0));
|
||||||
|
|
||||||
|
let difflight = Arc::new(Material::white_light(4.0));
|
||||||
|
|
||||||
|
let world: HittableList = vec![
|
||||||
|
object,
|
||||||
|
Arc::new(Rect2D::new(
|
||||||
|
Plane::XZ,
|
||||||
|
-2.0,
|
||||||
|
2.0,
|
||||||
|
-2.0,
|
||||||
|
2.0,
|
||||||
|
10.0,
|
||||||
|
difflight.clone()
|
||||||
|
)),
|
||||||
|
Arc::new(Sphere::new(
|
||||||
|
Point3::new(-5.0, 5.0, 0.0),
|
||||||
|
1.0,
|
||||||
|
difflight.clone()
|
||||||
|
)),
|
||||||
|
Arc::new(Sphere::new(
|
||||||
|
Point3::new(0.6, 3.5, -0.6),
|
||||||
|
0.4,
|
||||||
|
Arc::new(Material::Metal(Metal::new(Color::new(1.0, 0.0, 0.0), 0.2)))
|
||||||
|
)),
|
||||||
|
Arc::new(Sphere::new(
|
||||||
|
Point3::new(1.7, 0.4, 1.7),
|
||||||
|
0.4,
|
||||||
|
Arc::new(Material::solid(0.2, 0.2, 0.8))
|
||||||
|
)),
|
||||||
|
Arc::new(Sphere::new(
|
||||||
|
Point3::new(0.0, 300.0, 0.0),
|
||||||
|
300.0,
|
||||||
|
Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(NoiseTexture { noise: Perlin::new(), scale: 0.1 }))))
|
||||||
|
))];
|
||||||
|
|
||||||
|
let look_from = Point3::new(8.0, 10.0, 8.0);
|
||||||
|
let look_at = Point3::new(0.0, 1.0, 0.0);
|
||||||
|
let focus_dist = 2.0;
|
||||||
|
|
||||||
|
let cam = Camera::new(
|
||||||
|
look_from,
|
||||||
|
look_at,
|
||||||
|
Vec3::new(0.0, 1.0, 0.0),
|
||||||
|
1.0,
|
||||||
|
30.0,
|
||||||
|
0.0,
|
||||||
|
focus_dist,
|
||||||
|
0.0,
|
||||||
|
1.0);
|
||||||
|
|
||||||
|
Scene {
|
||||||
|
world,
|
||||||
|
cam,
|
||||||
|
background: Color::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cornell_box() -> Scene {
|
||||||
|
let mut world:HittableList = Vec::new();
|
||||||
|
let red = Material::Lambertian(
|
||||||
|
Lambertian::from(Color::new(0.65, 0.05, 0.05)));
|
||||||
|
let white = Arc::new(Material::Lambertian(
|
||||||
|
Lambertian::from(Color::new(0.73, 0.73, 0.73))));
|
||||||
|
let green = Material::Lambertian(
|
||||||
|
Lambertian::from(Color::new(0.12, 0.45, 0.15)));
|
||||||
|
let light = Material::DiffuseLight(
|
||||||
|
DiffuseLight::from(Color::new(15.0, 15.0, 15.0)));
|
||||||
|
|
||||||
|
// Walls
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::YZ,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
555.0,
|
||||||
|
Arc::new(green)
|
||||||
|
)));
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::YZ,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
Arc::new(red)
|
||||||
|
)));
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XZ,
|
||||||
|
213.0,
|
||||||
|
343.0,
|
||||||
|
227.0,
|
||||||
|
332.0,
|
||||||
|
554.0,
|
||||||
|
Arc::new(light)
|
||||||
|
)));
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XZ,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
white.clone()
|
||||||
|
)));
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XZ,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
555.0,
|
||||||
|
white.clone()
|
||||||
|
)));
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XY,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
555.0,
|
||||||
|
white.clone()
|
||||||
|
)));
|
||||||
|
|
||||||
|
// Boxes
|
||||||
|
|
||||||
|
let rotated1 = RotateY::new(
|
||||||
|
Cuboid::new(
|
||||||
|
Point3::new(0.0, 0.0, 0.0),
|
||||||
|
Point3::new(165.0, 330.0, 165.0),
|
||||||
|
white.clone()
|
||||||
|
),
|
||||||
|
15.0
|
||||||
|
);
|
||||||
|
let box1 = Translate::new(
|
||||||
|
rotated1,
|
||||||
|
Vec3::new(265.0, 0.0, 295.0)
|
||||||
|
);
|
||||||
|
world.push(Arc::new(box1));
|
||||||
|
let rotated2 = RotateY::new(
|
||||||
|
Cuboid::new(
|
||||||
|
Point3::new(0.0, 0.0, 0.0),
|
||||||
|
Point3::new(165.0, 165.0, 165.0),
|
||||||
|
white.clone()
|
||||||
|
),
|
||||||
|
-18.0
|
||||||
|
);
|
||||||
|
let box2 = Translate::new(
|
||||||
|
rotated2,
|
||||||
|
Vec3::new(130.0,0.0,65.0)
|
||||||
|
);
|
||||||
|
world.push(Arc::new(box2));
|
||||||
|
|
||||||
|
let look_from = Point3::new(278.0, 278.0, -800.0);
|
||||||
|
let look_at = Point3::new(278.0, 278.0, 0.0);
|
||||||
|
let focus_dist = 2.0;
|
||||||
|
|
||||||
|
let cam = Camera::new(
|
||||||
|
look_from,
|
||||||
|
look_at,
|
||||||
|
Vec3::new(0.0, 1.0, 0.0),
|
||||||
|
1.0,
|
||||||
|
40.0,
|
||||||
|
0.0,
|
||||||
|
focus_dist,
|
||||||
|
0.0,
|
||||||
|
1.0);
|
||||||
|
|
||||||
|
Scene {
|
||||||
|
world,
|
||||||
|
cam,
|
||||||
|
background: Color::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cornell_box_smoke() -> Scene {
|
||||||
|
let mut world:HittableList = Vec::new();
|
||||||
|
let red = Material::Lambertian(
|
||||||
|
Lambertian::from(Color::new(0.65, 0.05, 0.05)));
|
||||||
|
let white = Arc::new(Material::Lambertian(
|
||||||
|
Lambertian::from(Color::new(0.73, 0.73, 0.73))));
|
||||||
|
let green = Material::Lambertian(
|
||||||
|
Lambertian::from(Color::new(0.12, 0.45, 0.15)));
|
||||||
|
let light = Material::DiffuseLight(
|
||||||
|
DiffuseLight::from(Color::new(7.0, 7.0, 7.0)));
|
||||||
|
|
||||||
|
// Walls
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::YZ,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
555.0,
|
||||||
|
Arc::new(green)
|
||||||
|
)));
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::YZ,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
Arc::new(red)
|
||||||
|
)));
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XZ,
|
||||||
|
113.0,
|
||||||
|
443.0,
|
||||||
|
127.0,
|
||||||
|
432.0,
|
||||||
|
554.0,
|
||||||
|
Arc::new(light)
|
||||||
|
)));
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XZ,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
white.clone()
|
||||||
|
)));
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XZ,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
555.0,
|
||||||
|
white.clone()
|
||||||
|
)));
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XY,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
0.0,
|
||||||
|
555.0,
|
||||||
|
555.0,
|
||||||
|
white.clone()
|
||||||
|
)));
|
||||||
|
|
||||||
|
// Boxes
|
||||||
|
|
||||||
|
let rotated1 = RotateY::new(
|
||||||
|
Cuboid::new(
|
||||||
|
Point3::new(0.0, 0.0, 0.0),
|
||||||
|
Point3::new(165.0, 330.0, 165.0),
|
||||||
|
white.clone()
|
||||||
|
),
|
||||||
|
15.0
|
||||||
|
);
|
||||||
|
let box1 = Translate::new(
|
||||||
|
rotated1,
|
||||||
|
Vec3::new(265.0, 0.0, 295.0)
|
||||||
|
);
|
||||||
|
let medium1 = ConstantMedium::colored(
|
||||||
|
box1,
|
||||||
|
Color::new(0.0, 0.0, 0.0),
|
||||||
|
0.01);
|
||||||
|
world.push(Arc::new(medium1));
|
||||||
|
let rotated2 = RotateY::new(
|
||||||
|
Cuboid::new(
|
||||||
|
Point3::new(0.0, 0.0, 0.0),
|
||||||
|
Point3::new(165.0, 165.0, 165.0),
|
||||||
|
white.clone()
|
||||||
|
),
|
||||||
|
-18.0
|
||||||
|
);
|
||||||
|
let box2 = Translate::new(
|
||||||
|
rotated2,
|
||||||
|
Vec3::new(130.0,0.0,65.0)
|
||||||
|
);
|
||||||
|
let medium2 = ConstantMedium::colored(
|
||||||
|
box2,
|
||||||
|
Color::new(1.0, 1.0, 1.0),
|
||||||
|
0.01);
|
||||||
|
world.push(Arc::new(medium2));
|
||||||
|
|
||||||
|
let look_from = Point3::new(278.0, 278.0, -800.0);
|
||||||
|
let look_at = Point3::new(278.0, 278.0, 0.0);
|
||||||
|
let focus_dist = 2.0;
|
||||||
|
|
||||||
|
let cam = Camera::new(
|
||||||
|
look_from,
|
||||||
|
look_at,
|
||||||
|
Vec3::new(0.0, 1.0, 0.0),
|
||||||
|
1.0,
|
||||||
|
40.0,
|
||||||
|
0.0,
|
||||||
|
focus_dist,
|
||||||
|
0.0,
|
||||||
|
1.0);
|
||||||
|
|
||||||
|
Scene {
|
||||||
|
world,
|
||||||
|
cam,
|
||||||
|
background: Color::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simple_light() -> Scene {
|
||||||
|
let mut world:HittableList = Vec::new();
|
||||||
|
|
||||||
|
let noise = NoiseTexture { noise: Perlin::new(), scale: 4.0 };
|
||||||
|
let noise_material = Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(noise))));
|
||||||
|
world.push(Arc::new(Sphere {
|
||||||
|
center: Point3::new(0.0, -1000.0, 0.0),
|
||||||
|
radius: 1000.0,
|
||||||
|
material: Arc::clone(&noise_material)
|
||||||
|
}));
|
||||||
|
world.push(Arc::new(Sphere {
|
||||||
|
center: Point3::new(0.0, 2.0, 0.0),
|
||||||
|
radius: 2.0,
|
||||||
|
material: noise_material
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
let difflight = Material::DiffuseLight(
|
||||||
|
// Brighter than 1.0/1.0/1.0 so it can light things
|
||||||
|
DiffuseLight::from(Color::new(4.0, 4.0, 4.0)));
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XY,
|
||||||
|
3.0,
|
||||||
|
5.0,
|
||||||
|
1.0,
|
||||||
|
3.0,
|
||||||
|
-2.0,
|
||||||
|
Arc::new(difflight)
|
||||||
|
)));
|
||||||
|
|
||||||
|
let look_from = Point3::new(26.0, 3.0, 6.0);
|
||||||
|
let look_at = Point3::new(0.0, 2.0, 0.0);
|
||||||
|
let focus_dist = 2.0;
|
||||||
|
|
||||||
|
let cam = Camera::new(
|
||||||
|
look_from,
|
||||||
|
look_at,
|
||||||
|
Vec3::new(0.0, 1.0, 0.0),
|
||||||
|
DEFAULT_ASPECT_RATIO,
|
||||||
|
20.0,
|
||||||
|
0.0,
|
||||||
|
focus_dist,
|
||||||
|
0.0,
|
||||||
|
1.0);
|
||||||
|
|
||||||
|
Scene {
|
||||||
|
world,
|
||||||
|
cam,
|
||||||
|
background: Color::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sun() -> Scene {
|
||||||
|
let mut world:HittableList = Vec::new();
|
||||||
|
let earth_material = Arc::new(
|
||||||
|
Material::DiffuseLight(DiffuseLight::from(Color::new(1.0, 1.0, 0.0))));
|
||||||
|
world.push(Arc::new(Sphere {
|
||||||
|
center: Point3::new(0.0, 0.0, 0.0),
|
||||||
|
radius: 2.0,
|
||||||
|
material: earth_material
|
||||||
|
}));
|
||||||
|
|
||||||
|
let look_from = Point3::new(13.0, 2.0, 3.0);
|
||||||
|
let look_at = Point3::new(0.0, 0.0, 0.0);
|
||||||
|
let focus_dist = 2.0;
|
||||||
|
|
||||||
|
let cam = Camera::new(
|
||||||
|
look_from,
|
||||||
|
look_at,
|
||||||
|
Vec3::new(0.0, 1.0, 0.0),
|
||||||
|
DEFAULT_ASPECT_RATIO,
|
||||||
|
20.0,
|
||||||
|
0.0,
|
||||||
|
focus_dist,
|
||||||
|
0.0,
|
||||||
|
1.0);
|
||||||
|
|
||||||
|
Scene {
|
||||||
|
world,
|
||||||
|
cam,
|
||||||
|
background: Color::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn earth() -> Scene {
|
||||||
|
let mut world:HittableList = Vec::new();
|
||||||
|
let earth_texture = ImageTexture::new("textures/earthmap.jpg");
|
||||||
|
let earth_material = Arc::new(
|
||||||
|
Material::Lambertian(Lambertian::textured(Arc::new(earth_texture))));
|
||||||
|
world.push(Arc::new(Sphere {
|
||||||
|
center: Point3::new(0.0, 2.0, 1.0),
|
||||||
|
radius: 2.0,
|
||||||
|
material: earth_material
|
||||||
|
}));
|
||||||
|
|
||||||
|
let glass = Arc::new(
|
||||||
|
Material::Dielectric(Dielectric::new(1.5)));
|
||||||
|
world.push(Arc::new(Sphere {
|
||||||
|
center: Point3::new(-0.5,1.0, -1.5),
|
||||||
|
radius: 1.0,
|
||||||
|
material: glass
|
||||||
|
}));
|
||||||
|
|
||||||
|
let noise = NoiseTexture { noise: Perlin::new(), scale: 4.0 };
|
||||||
|
let noise_material = Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(noise))));
|
||||||
|
world.push(Arc::new(Sphere {
|
||||||
|
center: Point3::new(0.0, -1000.0, 0.0),
|
||||||
|
radius: 1000.0,
|
||||||
|
material: noise_material
|
||||||
|
}));
|
||||||
|
|
||||||
|
let look_from = Point3::new(10.0, 6.0, 3.0);
|
||||||
|
let look_at = Point3::new(0.0, 0.0, 0.0);
|
||||||
|
let focus_dist = 2.0;
|
||||||
|
|
||||||
|
let cam = Camera::new(
|
||||||
|
look_from,
|
||||||
|
look_at,
|
||||||
|
Vec3::new(0.0, 1.0, 0.0),
|
||||||
|
DEFAULT_ASPECT_RATIO,
|
||||||
|
40.0,
|
||||||
|
0.0,
|
||||||
|
focus_dist,
|
||||||
|
0.0,
|
||||||
|
1.0);
|
||||||
|
|
||||||
|
Scene {
|
||||||
|
world,
|
||||||
|
cam,
|
||||||
|
background: Color::new(0.70, 0.80, 1.00)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn two_spheres() -> Scene {
|
||||||
|
let mut world:HittableList = Vec::new();
|
||||||
|
let checker = CheckerTexture::colored(
|
||||||
|
Color::new(0.2, 0.3, 0.1),
|
||||||
|
Color::new(0.9, 0.9, 0.9));
|
||||||
|
let checker_material = Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(checker))));
|
||||||
|
world.push(Arc::new(Sphere {
|
||||||
|
center: Point3::new(0.0, -10.0, 0.0),
|
||||||
|
radius: 10.0,
|
||||||
|
material: Arc::clone(&checker_material)
|
||||||
|
}));
|
||||||
|
world.push(Arc::new(Sphere {
|
||||||
|
center: Point3::new(0.0, 10.0, 0.0),
|
||||||
|
radius: 10.0,
|
||||||
|
material: checker_material
|
||||||
|
}));
|
||||||
|
|
||||||
|
let look_from = Point3::new(13.0, 2.0, 3.0);
|
||||||
|
let look_at = Point3::new(0.0, 0.0, 0.0);
|
||||||
|
let focus_dist = 2.0;
|
||||||
|
|
||||||
|
let cam = Camera::new(
|
||||||
|
look_from,
|
||||||
|
look_at,
|
||||||
|
Vec3::new(0.0, 1.0, 0.0),
|
||||||
|
DEFAULT_ASPECT_RATIO,
|
||||||
|
20.0,
|
||||||
|
0.0,
|
||||||
|
focus_dist,
|
||||||
|
0.0,
|
||||||
|
1.0);
|
||||||
|
|
||||||
|
Scene {
|
||||||
|
world,
|
||||||
|
cam,
|
||||||
|
background: Color::new(0.70, 0.80, 1.00)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn two_perlin_spheres() -> Scene {
|
||||||
|
let mut world:HittableList = Vec::new();
|
||||||
|
let noise = NoiseTexture { noise: Perlin::new(), scale: 4.0 };
|
||||||
|
let noise_material = Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(noise))));
|
||||||
|
world.push(Arc::new(Sphere {
|
||||||
|
center: Point3::new(0.0, -1000.0, 0.0),
|
||||||
|
radius: 1000.0,
|
||||||
|
material: Arc::clone(&noise_material)
|
||||||
|
}));
|
||||||
|
world.push(Arc::new(Sphere {
|
||||||
|
center: Point3::new(1.0, 2.0, 1.0),
|
||||||
|
radius: 2.0,
|
||||||
|
material: noise_material
|
||||||
|
}));
|
||||||
|
|
||||||
|
let look_from = Point3::new(13.0, 2.0, 3.0);
|
||||||
|
let look_at = Point3::new(0.0, 0.0, 0.0);
|
||||||
|
let focus_dist = 2.0;
|
||||||
|
|
||||||
|
let cam = Camera::new(
|
||||||
|
look_from,
|
||||||
|
look_at,
|
||||||
|
Vec3::new(0.0, 1.0, 0.0),
|
||||||
|
DEFAULT_ASPECT_RATIO,
|
||||||
|
20.0,
|
||||||
|
0.0,
|
||||||
|
focus_dist,
|
||||||
|
0.0,
|
||||||
|
1.0);
|
||||||
|
|
||||||
|
Scene {
|
||||||
|
world,
|
||||||
|
cam,
|
||||||
|
background: Color::new(0.70, 0.80, 1.00)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random_scene() -> Scene {
|
||||||
|
let mut world: HittableList = Vec::new();
|
||||||
|
|
||||||
|
let checker = CheckerTexture::colored(
|
||||||
|
Color::new(0.2, 0.3, 0.1),
|
||||||
|
Color::new(0.9, 0.9, 0.9));
|
||||||
|
let material_ground = Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(checker))));
|
||||||
let ground = Sphere {
|
let ground = Sphere {
|
||||||
center: Point3::new(0.0, -1000.0, 0.0),
|
center: Point3::new(0.0, -1000.0, 0.0),
|
||||||
radius: 1000.0,
|
radius: 1000.0,
|
||||||
material: material_ground
|
material: material_ground
|
||||||
};
|
};
|
||||||
world.push(Box::new(ground));
|
world.push(Arc::new(ground));
|
||||||
|
|
||||||
let unit_range = Uniform::from(0.0..1.0);
|
let unit_range = Uniform::from(0.0..1.0);
|
||||||
let fuzz_range = Uniform::from(0.0..0.5);
|
let fuzz_range = Uniform::from(0.0..0.5);
|
||||||
@ -43,86 +598,278 @@ fn random_scene() -> Vec<Box<dyn Hittable + Sync>> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let material = match choose_material {
|
let material = match choose_material {
|
||||||
_ if choose_material < 0.8 => Material::Lambertian(Lambertian::new(
|
_ if choose_material < 0.8 => Arc::new(Material::Lambertian(Lambertian::from(
|
||||||
Color::random(0.0, 1.0) * Color::random(0.0, 1.0))),
|
Color::random(0.0, 1.0) * Color::random(0.0, 1.0)))),
|
||||||
_ if choose_material < 0.95 => Material::Metal(Metal::new(
|
_ if choose_material < 0.95 => Arc::new(Material::Metal(Metal::new(
|
||||||
Color::random(0.5, 1.0),
|
Color::random(0.5, 1.0),
|
||||||
fuzz_range.sample(&mut rng))),
|
fuzz_range.sample(&mut rng)))),
|
||||||
_ => Material::Dielectric(Dielectric::new(1.5)),
|
_ => Arc::new(Material::Dielectric(Dielectric::new(1.5))),
|
||||||
|
};
|
||||||
|
let sphere: HittableObject = match rng.gen_bool(1.0 / 3.0) {
|
||||||
|
true => {
|
||||||
|
let center1 = center + Vec3::new(0.0, fuzz_range.sample(&mut rng) / 2.0, 0.0);
|
||||||
|
Arc::new(MovableSphere {
|
||||||
|
center0: center,
|
||||||
|
center1,
|
||||||
|
radius: 0.2,
|
||||||
|
material,
|
||||||
|
time0: 0.0,
|
||||||
|
time1: 1.0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
false => Arc::new(Sphere {
|
||||||
|
center,
|
||||||
|
radius: 0.2,
|
||||||
|
material
|
||||||
|
})
|
||||||
};
|
};
|
||||||
let sphere = Box::new(Sphere {
|
|
||||||
center,
|
|
||||||
radius: 0.2,
|
|
||||||
material
|
|
||||||
});
|
|
||||||
world.push(sphere);
|
world.push(sphere);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let material1 = Material::Dielectric(Dielectric::new(1.5));
|
let material1 = Arc::new(Material::Dielectric(Dielectric::new(1.5)));
|
||||||
world.push(Box::new(Sphere {
|
world.push(Arc::new(Sphere {
|
||||||
center: Point3::new(0.0, 1.0, 0.0),
|
center: Point3::new(0.0, 1.0, 0.0),
|
||||||
radius: 1.0,
|
radius: 1.0,
|
||||||
material: material1
|
material: material1
|
||||||
}));
|
}));
|
||||||
let material2 = Material::Lambertian(Lambertian::new(Color::new(0.4, 0.2, 0.1)));
|
let material2 = Arc::new(Material::Lambertian(Lambertian::from(Color::new(0.4, 0.2, 0.1))));
|
||||||
world.push(Box::new(Sphere {
|
world.push(Arc::new(Sphere {
|
||||||
center: Point3::new(-4.0, 1.0, 0.0),
|
center: Point3::new(-4.0, 1.0, 0.0),
|
||||||
radius: 1.0,
|
radius: 1.0,
|
||||||
material: material2
|
material: material2
|
||||||
}));
|
}));
|
||||||
let material3 = Material::Metal(Metal::new(Color::new(0.7, 0.6, 0.5), 0.0));
|
let material3 = Arc::new(Material::Metal(Metal::new(Color::new(0.7, 0.6, 0.5), 0.0)));
|
||||||
world.push(Box::new(Sphere {
|
world.push(Arc::new(Sphere {
|
||||||
center: Point3::new(4.0, 1.0, 0.0),
|
center: Point3::new(4.0, 1.0, 0.0),
|
||||||
radius: 1.0,
|
radius: 1.0,
|
||||||
material: material3
|
material: material3
|
||||||
}));
|
}));
|
||||||
|
|
||||||
world
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// Image
|
|
||||||
const ASPECT_RATIO: f64 = 3.0 / 2.0;
|
|
||||||
const IMAGE_WIDTH: usize = 1200;
|
|
||||||
const IMAGE_HEIGHT: usize = (IMAGE_WIDTH as f64 / ASPECT_RATIO) as usize;
|
|
||||||
const SAMPLES_PER_PIXEL: i32 = 500;
|
|
||||||
const MAX_DEPTH: i32 = 50;
|
|
||||||
let hh = IMAGE_HEIGHT;
|
|
||||||
|
|
||||||
let look_from = Point3::new(13.0, 2.0, 3.0);
|
let look_from = Point3::new(13.0, 2.0, 3.0);
|
||||||
let look_at = Point3::new(0.0, 0.0, 0.0);
|
let look_at = Point3::new(0.0, 0.0, 0.0);
|
||||||
let focus_dist = 10.0;
|
let focus_dist = 2.0;
|
||||||
// Camera
|
|
||||||
let cam = Camera::new(
|
let cam = Camera::new(
|
||||||
look_from,
|
look_from,
|
||||||
look_at,
|
look_at,
|
||||||
Vec3::new(0.0, 1.0, 0.0),
|
Vec3::new(0.0, 1.0, 0.0),
|
||||||
ASPECT_RATIO,
|
DEFAULT_ASPECT_RATIO,
|
||||||
20.0,
|
20.0,
|
||||||
0.1,
|
0.0,
|
||||||
focus_dist);
|
focus_dist,
|
||||||
|
0.0,
|
||||||
|
1.0);
|
||||||
|
|
||||||
|
Scene {
|
||||||
|
world,
|
||||||
|
cam,
|
||||||
|
background: Color::new(0.70, 0.80, 1.00)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_week_final() -> Scene {
|
||||||
|
let ground = Arc::new(
|
||||||
|
Material::Lambertian(Lambertian::from(Color::new(0.48, 0.84, 0.53))));
|
||||||
|
let boxes_per_side = 20;
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let box_range = Uniform::from(1.0..101.0);
|
||||||
|
let mut boxes1: HittableList = Vec::with_capacity(boxes_per_side*boxes_per_side);
|
||||||
|
for i in 0..boxes_per_side {
|
||||||
|
for j in 0..boxes_per_side {
|
||||||
|
let w = 100.0;
|
||||||
|
let x0 = -1000.0 + (i as f64)*w;
|
||||||
|
let z0 = -1000.0 + (j as f64)*w;
|
||||||
|
let y0 = 0.0;
|
||||||
|
let x1 = x0 + w;
|
||||||
|
let y1 = box_range.sample(&mut rng);
|
||||||
|
let z1 = z0 + w;
|
||||||
|
boxes1.push(Arc::new(Cuboid::new(
|
||||||
|
Vec3::new(x0, y0, z0),
|
||||||
|
Vec3::new(x1, y1, z1),
|
||||||
|
ground.clone()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut world: HittableList = Vec::new();
|
||||||
|
world.push(Arc::new(BVH::new(boxes1, 0.0, 1.0)));
|
||||||
|
|
||||||
|
let light = Arc::new(
|
||||||
|
Material::DiffuseLight(DiffuseLight::from(Color::new(7.0, 7.0, 7.0))));
|
||||||
|
world.push(Arc::new(Rect2D::new(
|
||||||
|
Plane::XZ,
|
||||||
|
123.0,
|
||||||
|
423.0,
|
||||||
|
147.0,
|
||||||
|
412.0,
|
||||||
|
554.0,
|
||||||
|
light
|
||||||
|
)));
|
||||||
|
|
||||||
|
// Moving Sphere
|
||||||
|
let moving_sphere_material = Arc::new(
|
||||||
|
Material::Lambertian(Lambertian::from(Color::new(0.7, 0.3, 0.1))));
|
||||||
|
let center1 = Point3::new(400.0, 400.0, 200.0);
|
||||||
|
world.push(Arc::new(MovableSphere::new(
|
||||||
|
center1,
|
||||||
|
center1 + Vec3::new(30.0, 0.0, 0.0),
|
||||||
|
50.0,
|
||||||
|
moving_sphere_material,
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
)));
|
||||||
|
|
||||||
|
let glass = Arc::new(Material::Dielectric(Dielectric::new(1.5)));
|
||||||
|
|
||||||
|
// Glass
|
||||||
|
world.push(Arc::new(Sphere::new(
|
||||||
|
Point3::new(260.0, 150.0, 45.0),
|
||||||
|
50.0,
|
||||||
|
glass.clone()
|
||||||
|
)));
|
||||||
|
|
||||||
|
// Metal sphere
|
||||||
|
world.push(Arc::new(Sphere::new(
|
||||||
|
Point3::new(0.0, 150.0, 145.0),
|
||||||
|
50.0,
|
||||||
|
Arc::new(
|
||||||
|
Material::Metal(Metal::new(Color::new(0.8, 0.8, 0.9), 1.0)))
|
||||||
|
)));
|
||||||
|
let boundary = Sphere::new(
|
||||||
|
Point3::new(360.0, 150.0, 145.0),
|
||||||
|
70.0,
|
||||||
|
glass.clone()
|
||||||
|
);
|
||||||
|
let boundary_clone = Sphere::new(
|
||||||
|
Point3::new(360.0, 150.0, 145.0),
|
||||||
|
70.0,
|
||||||
|
glass.clone()
|
||||||
|
);
|
||||||
|
world.push(Arc::new(boundary_clone));
|
||||||
|
world.push(Arc::new(
|
||||||
|
ConstantMedium::colored(
|
||||||
|
boundary,
|
||||||
|
Color::new(0.2, 0.4, 0.9),
|
||||||
|
0.2)));
|
||||||
|
let boundary = Sphere::new(
|
||||||
|
Point3::new(0.0, 0.0, 0.0),
|
||||||
|
5000.0,
|
||||||
|
glass.clone()
|
||||||
|
);
|
||||||
|
world.push(Arc::new(
|
||||||
|
ConstantMedium::colored(
|
||||||
|
boundary,
|
||||||
|
Color::new(1.0, 1.0, 1.0),
|
||||||
|
0.0001)));
|
||||||
|
|
||||||
|
// Earth
|
||||||
|
let earth_material = Arc::new(Material::Lambertian(
|
||||||
|
Lambertian::textured(
|
||||||
|
Arc::new(ImageTexture::new("textures/earthmap.jpg")))));
|
||||||
|
world.push(Arc::new(Sphere::new(
|
||||||
|
Point3::new(400.0, 200.0, 400.0),
|
||||||
|
100.0,
|
||||||
|
earth_material
|
||||||
|
)));
|
||||||
|
|
||||||
|
// Gray sphere
|
||||||
|
let pertext = Arc::new(Material::Lambertian(Lambertian::textured(
|
||||||
|
Arc::new(NoiseTexture {
|
||||||
|
noise: Perlin::new(),
|
||||||
|
scale: 0.1
|
||||||
|
}))));
|
||||||
|
world.push(Arc::new(Sphere::new(
|
||||||
|
Point3::new(220.0, 280.0, 300.0),
|
||||||
|
80.0,
|
||||||
|
pertext
|
||||||
|
)));
|
||||||
|
|
||||||
|
let ns = 1000;
|
||||||
|
let mut boxes2: HittableList = Vec::with_capacity(ns);
|
||||||
|
let white = Arc::new(Material::Lambertian(
|
||||||
|
Lambertian::from(Color::new(0.73, 0.73, 0.73))));
|
||||||
|
|
||||||
|
|
||||||
|
for _i in 0..ns {
|
||||||
|
let sphere = Sphere::new(
|
||||||
|
Point3::random(0.0, 165.0),
|
||||||
|
10.0,
|
||||||
|
white.clone()
|
||||||
|
);
|
||||||
|
boxes2.push(Arc::new(sphere));
|
||||||
|
}
|
||||||
|
world.push(
|
||||||
|
Arc::new(
|
||||||
|
Translate::new(
|
||||||
|
RotateY::new(
|
||||||
|
BVH::new(boxes2, 0.0, 1.0),
|
||||||
|
15.0
|
||||||
|
),
|
||||||
|
Vec3::new(-100.0, 270.0, 395.0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
let look_from = Point3::new(478.0, 278.0, -600.0);
|
||||||
|
let look_at = Point3::new(278.0, 278.0, 0.0);
|
||||||
|
let focus_dist = 2.0;
|
||||||
|
|
||||||
|
let cam = Camera::new(
|
||||||
|
look_from,
|
||||||
|
look_at,
|
||||||
|
Vec3::new(0.0, 1.0, 0.0),
|
||||||
|
1.0,
|
||||||
|
40.0,
|
||||||
|
0.0,
|
||||||
|
focus_dist,
|
||||||
|
0.0,
|
||||||
|
1.0);
|
||||||
|
|
||||||
|
Scene {
|
||||||
|
world,
|
||||||
|
cam,
|
||||||
|
background: Color::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
rayon::ThreadPoolBuilder::new().num_threads(1).build_global().unwrap(); // Enable, to reduce load
|
||||||
// World
|
// World
|
||||||
let world= random_scene();
|
let scene: u8 = 8;
|
||||||
|
let scene_setup = match scene {
|
||||||
|
0 => two_spheres(),
|
||||||
|
1 => two_perlin_spheres(),
|
||||||
|
2 => earth(),
|
||||||
|
3 => sun(),
|
||||||
|
4 => simple_light(),
|
||||||
|
5 => cornell_box(),
|
||||||
|
6 => cornell_box_smoke(),
|
||||||
|
7 => next_week_final(),
|
||||||
|
8 => obj("teapot.obj"),
|
||||||
|
_ => random_scene(),
|
||||||
|
};
|
||||||
|
|
||||||
let between = Uniform::from(0.0..1.0);
|
let between = Uniform::from(0.0..1.0);
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut pixels = vec![0; IMAGE_WIDTH * IMAGE_HEIGHT * 3];
|
let image_height: usize = (IMAGE_WIDTH as f64 / scene_setup.cam.aspect_ratio()) as usize;
|
||||||
|
let mut pixels = vec![0; IMAGE_WIDTH * image_height * 3];
|
||||||
let bands: Vec<(usize, &mut [u8])> = pixels.chunks_mut(3).enumerate().collect();
|
let bands: Vec<(usize, &mut [u8])> = pixels.chunks_mut(3).enumerate().collect();
|
||||||
|
|
||||||
let count = Mutex::new(0);
|
let count = Mutex::new(0);
|
||||||
bands.into_par_iter().for_each(|(i, pixel)| {
|
bands.into_par_iter().for_each(|(i, pixel)| {
|
||||||
let row = IMAGE_HEIGHT - (i / IMAGE_WIDTH) - 1;
|
let row = image_height - (i / IMAGE_WIDTH) - 1;
|
||||||
let col = i % IMAGE_WIDTH;
|
let col = i % IMAGE_WIDTH;
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let mut color = Color::default();
|
let mut color = Color::default();
|
||||||
(0..SAMPLES_PER_PIXEL).for_each(|_s| {
|
(0..SAMPLES_PER_PIXEL).for_each(|_s| {
|
||||||
let random_number = between.sample(&mut rng);
|
let random_number = between.sample(&mut rng);
|
||||||
let u = (col as f64 + random_number) / (IMAGE_WIDTH - 1) as f64;
|
let u = (col as f64 + random_number) / (IMAGE_WIDTH - 1) as f64;
|
||||||
let v = (row as f64 + random_number) / (IMAGE_HEIGHT - 1) as f64;
|
let v = (row as f64 + random_number) / (image_height - 1) as f64;
|
||||||
let ray = cam.get_ray(u, v);
|
let ray = scene_setup.cam.get_ray(u, v);
|
||||||
color += ray.pixel_color(&world, MAX_DEPTH);
|
color += ray.pixel_color(scene_setup.background, &scene_setup.world, MAX_DEPTH);
|
||||||
});
|
});
|
||||||
let bytes = color.into_bytes(SAMPLES_PER_PIXEL);
|
let bytes = color.into_bytes(SAMPLES_PER_PIXEL);
|
||||||
pixel[0] = bytes[0];
|
pixel[0] = bytes[0];
|
||||||
@ -130,14 +877,14 @@ fn main() {
|
|||||||
pixel[2] = bytes[2];
|
pixel[2] = bytes[2];
|
||||||
if i % 100 == 0 {
|
if i % 100 == 0 {
|
||||||
let mut rem = count.lock().unwrap();
|
let mut rem = count.lock().unwrap();
|
||||||
let percent_done_before = 100 * *rem / (IMAGE_WIDTH * IMAGE_HEIGHT);
|
let percent_done_before = 100 * *rem / (IMAGE_WIDTH * image_height);
|
||||||
*rem += 100;
|
*rem += 100;
|
||||||
let percent_done_after = 100 * *rem / (IMAGE_WIDTH * IMAGE_HEIGHT);
|
let percent_done_after = 100 * *rem / (IMAGE_WIDTH * image_height);
|
||||||
if percent_done_before != percent_done_after {
|
if percent_done_before != percent_done_after {
|
||||||
eprint!("\rProgress: {}% ", percent_done_after);
|
eprint!("\rProgress: {}% ", percent_done_after);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
P3::write("imc.ppm", &pixels, IMAGE_WIDTH, IMAGE_HEIGHT).expect("Error writing image: {}");
|
PNG::write("imc.png", &pixels, IMAGE_WIDTH, image_height).expect("Error writing image: {}");
|
||||||
eprintln!("\nDone. Time: {}ms", start.elapsed().as_millis());
|
eprintln!("\nDone. Time: {}ms", start.elapsed().as_millis());
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,38 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use crate::hittable::HitRecord;
|
use crate::hittable::HitRecord;
|
||||||
use crate::{Color, Ray, Vec3};
|
use crate::{Color, Point3, Ray, Vec3};
|
||||||
|
use crate::isotropic::Isotropic;
|
||||||
|
use crate::texture::{SolidColor, Texture};
|
||||||
|
|
||||||
pub trait Scatterable {
|
pub trait Scatterable {
|
||||||
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)>;
|
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)>;
|
||||||
|
fn emitted(&self, u: f64, v: f64, point: &Point3) -> Color;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Material {
|
pub enum Material {
|
||||||
Lambertian(Lambertian),
|
Lambertian(Lambertian),
|
||||||
Metal(Metal),
|
Metal(Metal),
|
||||||
Dielectric(Dielectric)
|
Dielectric(Dielectric),
|
||||||
|
DiffuseLight(DiffuseLight),
|
||||||
|
Isotropic(Isotropic)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Material {
|
impl Default for Material {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Material::Lambertian(Lambertian::new(Color::default()))
|
Material::solid(0.8, 0.8, 0.8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Material {
|
||||||
|
pub fn solid(r: f64, g: f64, b: f64) -> Self {
|
||||||
|
Material::Lambertian(Lambertian::from(Color::new(r, g, b)))
|
||||||
|
}
|
||||||
|
pub fn light(r: f64, g: f64, b: f64) -> Self {
|
||||||
|
Material::DiffuseLight(DiffuseLight::from(Color::new(r, g, b)))
|
||||||
|
}
|
||||||
|
pub fn white_light(brightness: f64) -> Self {
|
||||||
|
Material::DiffuseLight(DiffuseLight::from(Color::new(brightness, brightness, brightness)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,29 +41,64 @@ impl Scatterable for Material {
|
|||||||
match self {
|
match self {
|
||||||
Material::Lambertian(l) => l.scatter(ray, hit_record),
|
Material::Lambertian(l) => l.scatter(ray, hit_record),
|
||||||
Material::Metal(m) => m.scatter(ray, hit_record),
|
Material::Metal(m) => m.scatter(ray, hit_record),
|
||||||
Material::Dielectric(d) => d.scatter(ray, hit_record)
|
Material::Dielectric(d) => d.scatter(ray, hit_record),
|
||||||
|
Material::DiffuseLight(l) => l.scatter(ray, hit_record),
|
||||||
|
Material::Isotropic(i) => i.scatter(ray, hit_record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn emitted(&self, u: f64, v: f64, point: &Point3) -> Color {
|
||||||
|
match self {
|
||||||
|
Material::DiffuseLight(l) => l.emitted(u, v, point),
|
||||||
|
_ => Color::new(0.0, 0.0, 0.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
pub struct DiffuseLight {
|
||||||
|
pub emit: Arc<dyn Texture>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for DiffuseLight {
|
||||||
|
fn from(color: Color) -> Self {
|
||||||
|
DiffuseLight {
|
||||||
|
emit: Arc::new(SolidColor::from(color))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffuseLight {
|
||||||
|
pub fn emitted(&self, u: f64, v: f64, point: &Point3) -> Color {
|
||||||
|
self.emit.value(u, v, point)
|
||||||
|
}
|
||||||
|
pub fn scatter(&self, _ray: &Ray, _hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Lambertian {
|
pub struct Lambertian {
|
||||||
pub albedo: Color
|
pub albedo: Arc<dyn Texture>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for Lambertian {
|
||||||
|
fn from(albedo: Color) -> Self {
|
||||||
|
let texture = SolidColor::from(albedo);
|
||||||
|
Lambertian { albedo: Arc::new(texture) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl Lambertian {
|
impl Lambertian {
|
||||||
pub fn new(albedo: Color) -> Self {
|
pub fn textured(albedo: Arc<dyn Texture>) -> Self {
|
||||||
Lambertian { albedo }
|
Lambertian { albedo }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scatterable for Lambertian {
|
impl Lambertian {
|
||||||
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
|
pub fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
|
||||||
let mut direction = hit_record.normal + Vec3::random_unit_vector();
|
let mut direction = hit_record.normal + Vec3::random_unit_vector();
|
||||||
if direction.near_zero() {
|
if direction.near_zero() {
|
||||||
direction = hit_record.normal;
|
direction = hit_record.normal;
|
||||||
}
|
}
|
||||||
let scattered = Ray::new(hit_record.point, direction);
|
let scattered = Ray::new(hit_record.point, direction, ray.time());
|
||||||
Some((Some(scattered), self.albedo))
|
Some((Some(scattered), self.albedo.value(hit_record.u, hit_record.v, &hit_record.point)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,12 +117,12 @@ impl Metal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scatterable for Metal {
|
impl Metal {
|
||||||
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
|
pub fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
|
||||||
let reflected = ray.direction().unit_vector().reflected(&hit_record.normal);
|
let reflected = ray.direction().unit_vector().reflected(&hit_record.normal);
|
||||||
let scattered = Ray::new(
|
let scattered = Ray::new(
|
||||||
hit_record.point,
|
hit_record.point,
|
||||||
reflected + self.fuzz * Vec3::random_in_unit_sphere());
|
reflected + self.fuzz * Vec3::random_in_unit_sphere(), ray.time());
|
||||||
if scattered.direction().dot(&hit_record.normal) > 0.0 {
|
if scattered.direction().dot(&hit_record.normal) > 0.0 {
|
||||||
Some((Some(scattered), self.albedo))
|
Some((Some(scattered), self.albedo))
|
||||||
} else {
|
} else {
|
||||||
@ -95,8 +147,8 @@ impl Dielectric {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scatterable for Dielectric {
|
impl Dielectric {
|
||||||
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
|
pub fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
|
||||||
let color = Color::new(1.0, 1.0, 1.0);
|
let color = Color::new(1.0, 1.0, 1.0);
|
||||||
let refraction_ratio = if hit_record.front_face {
|
let refraction_ratio = if hit_record.front_face {
|
||||||
1.0/self.index_of_refraction
|
1.0/self.index_of_refraction
|
||||||
@ -113,11 +165,11 @@ impl Scatterable for Dielectric {
|
|||||||
|
|
||||||
if cannot_refract || reflectance > rng.gen::<f64>() {
|
if cannot_refract || reflectance > rng.gen::<f64>() {
|
||||||
let reflected = unit_direction.reflected(&hit_record.normal);
|
let reflected = unit_direction.reflected(&hit_record.normal);
|
||||||
let scattered = Ray::new(hit_record.point, reflected);
|
let scattered = Ray::new(hit_record.point, reflected, ray.time());
|
||||||
Some((Some(scattered), color))
|
Some((Some(scattered), color))
|
||||||
} else {
|
} else {
|
||||||
let direction = unit_direction.refract(&hit_record.normal, refraction_ratio);
|
let direction = unit_direction.refract(&hit_record.normal, refraction_ratio);
|
||||||
let scattered = Ray::new(hit_record.point, direction);
|
let scattered = Ray::new(hit_record.point, direction, ray.time());
|
||||||
Some((Some(scattered), color))
|
Some((Some(scattered), color))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
src/noise.rs
Normal file
17
src/noise.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use crate::perlin::Perlin;
|
||||||
|
use crate::{Color, Point3};
|
||||||
|
use crate::texture::Texture;
|
||||||
|
|
||||||
|
pub struct NoiseTexture {
|
||||||
|
pub noise: Perlin,
|
||||||
|
pub scale: f64
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Texture for NoiseTexture {
|
||||||
|
fn value(&self, _u: f64, _v: f64, point: &Point3) -> Color {
|
||||||
|
let sin = self.scale * point.z() + 10.0 * self.noise.default_turbulence(point);
|
||||||
|
Color::new(1.0, 1.0, 1.0)
|
||||||
|
* 0.5
|
||||||
|
* (1.0 + sin.sin())
|
||||||
|
}
|
||||||
|
}
|
51
src/obj.rs
Normal file
51
src/obj.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tobj::LoadOptions;
|
||||||
|
use crate::{BVH, Color, Dielectric, HittableList, HittableObject, Lambertian, Material, Point3, Vec3};
|
||||||
|
use crate::hittable::Hittable;
|
||||||
|
use crate::triangle::Triangle;
|
||||||
|
|
||||||
|
pub fn obj_to_hitable(path: &Path) -> HittableObject {
|
||||||
|
let mut lo = LoadOptions::default();
|
||||||
|
lo.triangulate = true;
|
||||||
|
let (models, materials) = tobj::load_obj(path, &lo)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let default_mat: Arc<Material> = Arc::new(
|
||||||
|
Material::Dielectric(
|
||||||
|
Dielectric::new(1.5)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let mut triangles: HittableList = Vec::with_capacity(models.len());
|
||||||
|
for model in models {
|
||||||
|
let mesh = model.mesh;
|
||||||
|
for f in 0..mesh.indices.len() / 3 {
|
||||||
|
let i0 = mesh.indices[3 * f] as usize;
|
||||||
|
let i1 = mesh.indices[3 * f + 1] as usize;
|
||||||
|
let i2 = mesh.indices[3 * f + 2] as usize;
|
||||||
|
let v0 = Point3::new(
|
||||||
|
mesh.positions[i0 * 3] as f64,
|
||||||
|
mesh.positions[i0 * 3 + 1] as f64,
|
||||||
|
mesh.positions[i0 * 3 + 2] as f64);
|
||||||
|
let v1 = Point3::new(
|
||||||
|
mesh.positions[i1 * 3] as f64,
|
||||||
|
mesh.positions[i1 * 3 + 1] as f64,
|
||||||
|
mesh.positions[i1 * 3 + 2] as f64);
|
||||||
|
let v2 = Point3::new(
|
||||||
|
mesh.positions[i2 * 3] as f64,
|
||||||
|
mesh.positions[i2 * 3 + 1] as f64,
|
||||||
|
mesh.positions[i2 * 3 + 2] as f64);
|
||||||
|
let triangle = if mesh.normals.len() <= i0 * 3 + 2 {
|
||||||
|
Triangle::without_normal(v0, v1, v2, default_mat.clone())
|
||||||
|
} else {
|
||||||
|
let normal = Vec3::new(
|
||||||
|
mesh.normals[i0 * 3] as f64,
|
||||||
|
mesh.normals[i0 * 3 + 1] as f64,
|
||||||
|
mesh.normals[i0 * 3 + 2] as f64);
|
||||||
|
Triangle::new(v0, v1, v2, normal, default_mat.clone())
|
||||||
|
};
|
||||||
|
triangles.push(Arc::new(triangle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Arc::new(BVH::new(triangles, 0.0, 1.0))
|
||||||
|
}
|
@ -1,12 +1,14 @@
|
|||||||
use std::{fs::File, io::{Error, Write}};
|
use std::{fs::File, io::{Error, Write, BufWriter, ErrorKind}};
|
||||||
|
|
||||||
|
use image::ImageOutputFormat;
|
||||||
|
|
||||||
pub trait Output {
|
pub trait Output {
|
||||||
fn write(filename: &str, pixels: &Vec<u8>, width: usize, height: usize) -> Result<File, Error>;
|
fn write(filename: &str, pixels: &Vec<u8>, width: usize, height: usize) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct P3 {}
|
pub struct P3 {}
|
||||||
impl Output for P3 {
|
impl Output for P3 {
|
||||||
fn write(filename: &str, pixels: &Vec<u8>, width: usize, height: usize) -> Result<File, Error> {
|
fn write(filename: &str, pixels: &Vec<u8>, width: usize, height: usize) -> Result<(), Error> {
|
||||||
let mut file = File::create(filename)?;
|
let mut file = File::create(filename)?;
|
||||||
file.write(format!("P3\n{} {}\n255\n", width, height).as_bytes())?;
|
file.write(format!("P3\n{} {}\n255\n", width, height).as_bytes())?;
|
||||||
let lines: Result<Vec<usize>, Error> = pixels
|
let lines: Result<Vec<usize>, Error> = pixels
|
||||||
@ -14,6 +16,22 @@ impl Output for P3 {
|
|||||||
.map(|chunk| format!("{} {} {}\n", chunk[0], chunk[1], chunk[2]))
|
.map(|chunk| format!("{} {} {}\n", chunk[0], chunk[1], chunk[2]))
|
||||||
.map(|line| file.write(line.as_bytes()))
|
.map(|line| file.write(line.as_bytes()))
|
||||||
.collect();
|
.collect();
|
||||||
lines.map(|_v| file)
|
lines.map(|_l|())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PNG {}
|
||||||
|
impl Output for PNG {
|
||||||
|
fn write(filename: &str, pixels: &Vec<u8>, width: usize, height: usize) -> Result<(), Error> {
|
||||||
|
let file = File::create(filename)?;
|
||||||
|
let ref mut writer = BufWriter::new(file);
|
||||||
|
image::write_buffer_with_format(
|
||||||
|
writer,
|
||||||
|
pixels,
|
||||||
|
width as u32,
|
||||||
|
height as u32,
|
||||||
|
image::ColorType::Rgb8,
|
||||||
|
ImageOutputFormat::Png)
|
||||||
|
.map_err(|e| Error::new(ErrorKind::Other, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
96
src/perlin.rs
Normal file
96
src/perlin.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
use rand::Rng;
|
||||||
|
use crate::{Point3, Vec3};
|
||||||
|
|
||||||
|
pub struct Perlin {
|
||||||
|
ranvec: Vec<Vec3>,
|
||||||
|
perm_x: Vec<usize>,
|
||||||
|
perm_y: Vec<usize>,
|
||||||
|
perm_z: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Perlin {
|
||||||
|
const POINT_COUNT: usize = 256;
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut ranvec = Vec::with_capacity(Perlin::POINT_COUNT);
|
||||||
|
(0..Perlin::POINT_COUNT).for_each(|_i|ranvec.push(Vec3::random(-1.0, 1.0)));
|
||||||
|
Perlin {
|
||||||
|
ranvec,
|
||||||
|
perm_x: Perlin::perlin_generate_perm(),
|
||||||
|
perm_y: Perlin::perlin_generate_perm(),
|
||||||
|
perm_z: Perlin::perlin_generate_perm(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_turbulence(&self, point: &Point3) -> f64 {
|
||||||
|
self.turbulence(point, 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn turbulence(&self, point: &Point3, depth: i32) -> f64 {
|
||||||
|
let mut accum = 0.0;
|
||||||
|
let mut temp_p = point.clone();
|
||||||
|
let mut weight = 1.0;
|
||||||
|
for _i in 0..depth {
|
||||||
|
accum += weight * self.noise(&temp_p);
|
||||||
|
weight *= 0.5;
|
||||||
|
temp_p *= 2.0;
|
||||||
|
}
|
||||||
|
accum.abs()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn noise(&self, point: &Point3) -> f64 {
|
||||||
|
let u = point.x() - point.x().floor();
|
||||||
|
let v = point.y() - point.y().floor();
|
||||||
|
let w = point.z() - point.z().floor();
|
||||||
|
let i = point.x().floor() as i32;
|
||||||
|
let j = point.y().floor() as i32;
|
||||||
|
let k = point.z().floor() as i32;
|
||||||
|
let mut c: [[[Vec3; 2]; 2]; 2] = [[[Vec3::default(); 2]; 2]; 2];
|
||||||
|
|
||||||
|
for di in 0..2 {
|
||||||
|
for dj in 0..2 {
|
||||||
|
for dk in 0..2 {
|
||||||
|
let idx = self.perm_x[((i+di) & 255) as usize] ^
|
||||||
|
self.perm_y[((j+dj) & 255) as usize] ^
|
||||||
|
self.perm_z[((k+dk) & 255) as usize];
|
||||||
|
c[di as usize][dj as usize][dk as usize] = self.ranvec[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Perlin::perlin_interp(c, u, v, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perlin_generate_perm() -> Vec<usize> {
|
||||||
|
let mut p = Vec::with_capacity(Perlin::POINT_COUNT);
|
||||||
|
(0..Perlin::POINT_COUNT).for_each(|i| p.push(i));
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
(1..Perlin::POINT_COUNT).rev().for_each(|idx| {
|
||||||
|
let target = rng.gen_range(0..=idx);
|
||||||
|
p.swap(idx, target);
|
||||||
|
});
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perlin_interp(c: [[[Vec3; 2]; 2]; 2], u: f64, v: f64, w: f64) -> f64 {
|
||||||
|
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 accum = 0.0;
|
||||||
|
for i in 0..2 {
|
||||||
|
for j in 0..2 {
|
||||||
|
for k in 0..2 {
|
||||||
|
let ifl = i as f64;
|
||||||
|
let jfl = j as f64;
|
||||||
|
let kfl = k as f64;
|
||||||
|
let weight_v = Vec3::new(u - ifl, v - jfl, w - kfl);
|
||||||
|
accum += (ifl*uu + (1.0-ifl)*(1.0-uu))
|
||||||
|
* (jfl*vv + (1.0-jfl)*(1.0-vv))
|
||||||
|
* (kfl*ww + (1.0-kfl)*(1.0-ww))
|
||||||
|
* (c[i][j][k]).dot(&weight_v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
accum
|
||||||
|
}
|
||||||
|
}
|
56
src/ray.rs
56
src/ray.rs
@ -1,56 +1,56 @@
|
|||||||
use crate::{Color, Hittable, Vec3};
|
use crate::{Color, Vec3};
|
||||||
use crate::hittable::HitRecord;
|
use crate::hittable::{HitRecord, HittableList};
|
||||||
use crate::material::Scatterable;
|
use crate::material::Scatterable;
|
||||||
use crate::vec3::Point3;
|
use crate::vec3::Point3;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Ray {
|
pub struct Ray {
|
||||||
origin: Point3,
|
origin: Point3,
|
||||||
direction: Vec3
|
direction: Vec3,
|
||||||
|
time: f64
|
||||||
}
|
}
|
||||||
impl Default for Ray {
|
impl Default for Ray {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Ray::new(Vec3::default(), Vec3::default())
|
Ray::new(Vec3::default(), Vec3::default(), 0.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ray {
|
impl Ray {
|
||||||
pub fn new(origin: Point3, direction: Point3) -> Ray {
|
pub fn new(origin: Point3, direction: Point3, time: f64) -> Ray {
|
||||||
Ray { origin, direction }
|
Ray { origin, direction, time }
|
||||||
}
|
}
|
||||||
pub fn at(&self, t: f64) -> Point3 {
|
pub fn at(&self, t: f64) -> Point3 {
|
||||||
self.origin + self.direction * t
|
self.origin + self.direction * t
|
||||||
}
|
}
|
||||||
pub fn direction(&self) -> Vec3 { self.direction }
|
pub fn direction(&self) -> Vec3 { self.direction }
|
||||||
pub fn origin(&self) -> Point3 { self.origin }
|
pub fn origin(&self) -> Point3 { self.origin }
|
||||||
pub fn pixel_color(&self, world: &Vec<Box<dyn Hittable + Sync>>, depth: i32) -> Color {
|
pub fn time(&self) -> f64 { self.time }
|
||||||
|
pub fn pixel_color(&self, background: Color, world: &HittableList, depth: i32) -> Color {
|
||||||
if depth <= 0 {
|
if depth <= 0 {
|
||||||
return Color::default();
|
return Color::default();
|
||||||
}
|
}
|
||||||
if let Some(rect) = self.hit_world(world, 0.001, f64::INFINITY) {
|
match self.hit_world(world, 0.001, f64::INFINITY) {
|
||||||
let scattered = rect.material.scatter(self, &rect);
|
Some(rect) => {
|
||||||
return match scattered {
|
let scattered = rect.material.scatter(self, &rect);
|
||||||
Some((scattered_ray, albedo)) => {
|
let emitted = rect.material.emitted(rect.u, rect.v, &rect.point);
|
||||||
match scattered_ray {
|
match scattered {
|
||||||
Some(sr) => {
|
Some((scattered, albedo)) => {
|
||||||
albedo * sr.pixel_color(world, depth-1)
|
match scattered {
|
||||||
},
|
Some(scattered) => {
|
||||||
None => albedo
|
emitted + albedo * scattered.pixel_color(background, world, depth-1)
|
||||||
}
|
},
|
||||||
},
|
None => albedo
|
||||||
None => { return Color::default() }
|
}
|
||||||
};
|
},
|
||||||
|
_ => emitted
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => background
|
||||||
}
|
}
|
||||||
|
|
||||||
//Hot nothing, display sky color
|
|
||||||
let unit_direction = self.direction().unit_vector();
|
|
||||||
let t = 0.5 * (unit_direction.y() + 1.0);
|
|
||||||
(1.0 - t) * Color::new(1.0, 1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0)
|
|
||||||
}
|
}
|
||||||
fn hit_world<'material>(
|
pub fn hit_world<'material>(
|
||||||
&self,
|
&self,
|
||||||
world: &'material Vec<Box<dyn Hittable + Sync>>,
|
world: &'material HittableList,
|
||||||
t_min: f64,
|
t_min: f64,
|
||||||
t_max: f64,
|
t_max: f64,
|
||||||
) -> Option<HitRecord<'material>> {
|
) -> Option<HitRecord<'material>> {
|
||||||
|
88
src/rect.rs
Normal file
88
src/rect.rs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use crate::hittable::{HitRecord, Hittable};
|
||||||
|
use crate::{Aabb, Material, Point3, Ray, Vec3};
|
||||||
|
|
||||||
|
pub enum Plane {
|
||||||
|
XY,
|
||||||
|
XZ,
|
||||||
|
YZ
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Rect2D {
|
||||||
|
plane: Plane,
|
||||||
|
a0: f64,
|
||||||
|
a1: f64,
|
||||||
|
b0: f64,
|
||||||
|
b1: f64,
|
||||||
|
k: f64,
|
||||||
|
material: Arc<Material>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rect2D {
|
||||||
|
pub fn new(plane: Plane, a0: f64, a1: f64, b0: f64, b1: f64, k: f64, material: Arc<Material>) -> Self {
|
||||||
|
Self { plane, a0, a1, b0, b1, k, material }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hittable for Rect2D {
|
||||||
|
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||||
|
let t = match &self.plane {
|
||||||
|
Plane::XY => (self.k - ray.origin().z()) / ray.direction().z(),
|
||||||
|
Plane::XZ => (self.k - ray.origin().y()) / ray.direction().y(),
|
||||||
|
Plane::YZ => (self.k - ray.origin().x()) / ray.direction().x(),
|
||||||
|
};
|
||||||
|
let (a, b) = match &self.plane {
|
||||||
|
Plane::XY => (
|
||||||
|
ray.origin().x() + t * ray.direction().x(),
|
||||||
|
ray.origin().y() + t * ray.direction().y()),
|
||||||
|
Plane::XZ => (
|
||||||
|
ray.origin().x() + t * ray.direction().x(),
|
||||||
|
ray.origin().z() + t * ray.direction().z()),
|
||||||
|
Plane::YZ => (
|
||||||
|
ray.origin().y() + t * ray.direction().y(),
|
||||||
|
ray.origin().z() + t * ray.direction().z()),
|
||||||
|
};
|
||||||
|
if t < t_min || t > t_max {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if a < self.a0 || a > self.a1 || b < self.b0 || b > self.b1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let normal = match &self.plane {
|
||||||
|
Plane::XY => Vec3::new(0.0, 0.0, 1.0),
|
||||||
|
Plane::XZ => Vec3::new(0.0, 1.0, 0.0),
|
||||||
|
Plane::YZ => Vec3::new(1.0, 0.0, 0.0),
|
||||||
|
};
|
||||||
|
let dot = ray.direction().dot(&normal);
|
||||||
|
let front_face = dot < 0.0;
|
||||||
|
let normal = if front_face { normal } else { -normal };
|
||||||
|
Some(HitRecord {
|
||||||
|
point: ray.at(t),
|
||||||
|
normal,
|
||||||
|
t,
|
||||||
|
u: (a-self.a0)/(self.a1 -self.a0),
|
||||||
|
v: (b-self.b0)/(self.b1 -self.b0),
|
||||||
|
front_face,
|
||||||
|
material: &self.material
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, _time0: f64, _time1: f64) -> Option<Aabb> {
|
||||||
|
// The bounding box must have non-zero width in each dimension, so pad the Z
|
||||||
|
// dimension a small amount.
|
||||||
|
match &self.plane {
|
||||||
|
Plane::XY => Some(Aabb {
|
||||||
|
minimum: Point3::new(self.a0, self.b0, self.k-0.0001),
|
||||||
|
maximum: Point3::new(self.a1, self.b1, self.k+0.0001),
|
||||||
|
}),
|
||||||
|
Plane::XZ=> Some(Aabb {
|
||||||
|
minimum: Point3::new(self.a0, self.k-0.0001, self.b0),
|
||||||
|
maximum: Point3::new(self.a1, self.k+0.0001, self.b1),
|
||||||
|
}),
|
||||||
|
Plane::YZ => Some(Aabb {
|
||||||
|
minimum: Point3::new(self.k-0.0001, self.a0, self.b0),
|
||||||
|
maximum: Point3::new(self.k+0.0001, self.a1, self.b1),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
src/rotate_y.rs
Normal file
86
src/rotate_y.rs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
use crate::hittable::{HitRecord, Hittable};
|
||||||
|
use crate::{Aabb, Point3, Ray, Vec3};
|
||||||
|
|
||||||
|
pub struct RotateY<H: Hittable> {
|
||||||
|
hittable: H,
|
||||||
|
sin_theta: f64,
|
||||||
|
cos_theta: f64,
|
||||||
|
bounding_box: Option<Aabb>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: Hittable> RotateY<H> {
|
||||||
|
pub fn new(hittable: H, degrees: f64) -> Self {
|
||||||
|
let radians = degrees.to_radians();
|
||||||
|
let sin_theta = radians.sin();
|
||||||
|
let cos_theta = radians.cos();
|
||||||
|
let mut bounding_box = hittable.bounding_box(0.0, 1.0);
|
||||||
|
|
||||||
|
if let Some(bbox) = bounding_box {
|
||||||
|
let mut min = Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
|
||||||
|
let mut max = Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
|
||||||
|
for i in 0..2 {
|
||||||
|
for j in 0..2 {
|
||||||
|
for k in 0..2 {
|
||||||
|
let x = (i as f64)*bbox.maximum.x() + ((1 - i) as f64)*bbox.minimum.x();
|
||||||
|
let y = (i as f64)*bbox.maximum.y() + ((1 - j) as f64)*bbox.minimum.y();
|
||||||
|
let z = (i as f64)*bbox.maximum.z() + ((1 - k) as f64)*bbox.minimum.z();
|
||||||
|
|
||||||
|
let x = cos_theta * x + sin_theta * z;
|
||||||
|
let z = -sin_theta * x + cos_theta * z;
|
||||||
|
|
||||||
|
let tester = Vec3::new(x, y, z);
|
||||||
|
for c in 0..3 {
|
||||||
|
min[c] = min[c].min(tester[c]);
|
||||||
|
max[c] = max[c].max(tester[c]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bounding_box = Some(Aabb {
|
||||||
|
minimum: min,
|
||||||
|
maximum: max
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Self { hittable, sin_theta, cos_theta, bounding_box }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<H: Hittable> Hittable for RotateY<H> {
|
||||||
|
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||||
|
let mut origin = ray.origin();
|
||||||
|
let mut direction = ray.direction();
|
||||||
|
|
||||||
|
origin[0] = self.cos_theta * ray.origin()[0] - self.sin_theta*ray.origin()[2];
|
||||||
|
origin[2] = self.sin_theta * ray.origin()[0] + self.cos_theta*ray.origin()[2];
|
||||||
|
|
||||||
|
direction[0] = self.cos_theta * ray.direction()[0] - self.sin_theta*ray.direction()[2];
|
||||||
|
direction[2] = self.sin_theta * ray.direction()[0] + self.cos_theta*ray.direction()[2];
|
||||||
|
let rotated = Ray::new(origin, direction, ray.time());
|
||||||
|
match self.hittable.hit(&rotated, t_min, t_max) {
|
||||||
|
Some(rec) => {
|
||||||
|
let mut p = rec.point;
|
||||||
|
let mut normal = rec.normal;
|
||||||
|
p[0] = self.cos_theta*rec.point[0] + self.sin_theta * rec.point[2];
|
||||||
|
p[2] = -self.sin_theta*rec.point[0] + self.cos_theta * rec.point[2];
|
||||||
|
|
||||||
|
normal[0] = self.cos_theta*rec.normal[0] + self.sin_theta * rec.normal[2];
|
||||||
|
normal[2] = -self.sin_theta*rec.normal[0] + self.cos_theta * rec.normal[2];
|
||||||
|
let mut new_rec = HitRecord {
|
||||||
|
point: p,
|
||||||
|
normal,
|
||||||
|
t: rec.t,
|
||||||
|
u: rec.u,
|
||||||
|
v: rec.v,
|
||||||
|
front_face: rec.front_face,
|
||||||
|
material: rec.material,
|
||||||
|
};
|
||||||
|
new_rec.normalized(&rotated);
|
||||||
|
Some(new_rec)
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, _time0: f64, _time1: f64) -> Option<Aabb> {
|
||||||
|
self.bounding_box
|
||||||
|
}
|
||||||
|
}
|
56
src/texture.rs
Normal file
56
src/texture.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use crate::{Point3, Color};
|
||||||
|
|
||||||
|
pub trait Texture: Sync + Send {
|
||||||
|
fn value(&self, u: f64, v: f64, point: &Point3) -> Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SolidColor {
|
||||||
|
color_value: Color
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SolidColor {
|
||||||
|
pub fn new(red: f64, green: f64, blue: f64) -> Self {
|
||||||
|
SolidColor {
|
||||||
|
color_value: Color::new(red, green, blue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for SolidColor {
|
||||||
|
fn from(color: Color) -> Self {
|
||||||
|
SolidColor { color_value: color }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Texture for SolidColor {
|
||||||
|
fn value(&self, _u: f64, _v: f64, _point: &Point3) -> Color {
|
||||||
|
self.color_value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CheckerTexture {
|
||||||
|
pub odd: Box<dyn Texture>,
|
||||||
|
pub even: Box<dyn Texture>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CheckerTexture {
|
||||||
|
pub fn colored(color1: Color, color2: Color) -> Self {
|
||||||
|
CheckerTexture {
|
||||||
|
even: Box::new(SolidColor::from(color1)),
|
||||||
|
odd: Box::new(SolidColor::from(color2)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Texture for CheckerTexture {
|
||||||
|
fn value(&self, u: f64, v: f64, point: &Point3) -> Color {
|
||||||
|
let sines = (10.0 * point.x()).sin() *
|
||||||
|
(10.0 * point.y()).sin() *
|
||||||
|
(10.0 * point.z()).sin();
|
||||||
|
match sines {
|
||||||
|
sines if sines < 0.0 => self.odd.value(u, v, point),
|
||||||
|
_ => self.even.value(u, v, point),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/translate.rs
Normal file
34
src/translate.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use crate::hittable::{HitRecord, Hittable};
|
||||||
|
use crate::{Aabb, Ray, Vec3};
|
||||||
|
|
||||||
|
pub struct Translate<H: Hittable> {
|
||||||
|
hittable: H,
|
||||||
|
offset: Vec3
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: Hittable> Translate<H> {
|
||||||
|
pub fn new(hittable: H, offset: Vec3) -> Self {
|
||||||
|
Self { hittable, offset }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: Hittable> Hittable for Translate<H> {
|
||||||
|
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());
|
||||||
|
self.hittable.hit(&moved_ray, t_min, t_max).map(|mut hr| {
|
||||||
|
hr.point += self.offset;
|
||||||
|
hr
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, time0: f64, time1: f64) -> Option<Aabb> {
|
||||||
|
self.hittable.bounding_box(time0, time1).map(|mut b| {
|
||||||
|
b.minimum += self.offset;
|
||||||
|
b.maximum += self.offset;
|
||||||
|
b
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
78
src/triangle.rs
Normal file
78
src/triangle.rs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use crate::hittable::{HitRecord, Hittable};
|
||||||
|
use crate::{Aabb, Material, Point3, Ray, Vec3};
|
||||||
|
//https://github.com/perliedman/raytracing-in-one-weekend/blob/master/src/geometry/triangle.rs
|
||||||
|
|
||||||
|
pub struct Triangle {
|
||||||
|
a: Point3,
|
||||||
|
b: Point3,
|
||||||
|
c: Point3,
|
||||||
|
normal: Vec3,
|
||||||
|
material: Arc<Material>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Triangle {
|
||||||
|
pub fn new(a: Point3, b: Point3, c: Point3, normal: Vec3, material: Arc<Material>, ) -> Self {
|
||||||
|
Self { a, b, c, normal, material }
|
||||||
|
}
|
||||||
|
pub fn without_normal(a: Point3, b: Point3, c: Point3, material: Arc<Material>, ) -> Self {
|
||||||
|
let normal = (b - a).cross(&(c - a));
|
||||||
|
Self { a, b, c, normal, material }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hittable for Triangle {
|
||||||
|
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||||
|
let ab = self.b - self.a;
|
||||||
|
let ac = self.c - self.a;
|
||||||
|
let pvec = ray.direction().cross(&ac);
|
||||||
|
let det = ab.dot(&pvec);
|
||||||
|
if det.abs() < 1e-4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let inv_det = 1.0 / det;
|
||||||
|
let tvec = ray.origin() - self.a;
|
||||||
|
let u = tvec.dot(&pvec) * inv_det;
|
||||||
|
if u < 0.0 || u > 1.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let qvec = tvec.cross(&ab);
|
||||||
|
let v = ray.direction().dot(&qvec) * inv_det;
|
||||||
|
if v < 0.0 || u + v > 1.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match ac.dot(&qvec) * inv_det {
|
||||||
|
t if t < t_min || t > t_max => None,
|
||||||
|
t => {
|
||||||
|
let mut rec = HitRecord {
|
||||||
|
point: ray.at(t),
|
||||||
|
normal: self.normal,
|
||||||
|
t,
|
||||||
|
u,
|
||||||
|
v,
|
||||||
|
front_face: true,
|
||||||
|
material: &self.material
|
||||||
|
};
|
||||||
|
rec.normalized(ray);
|
||||||
|
Some(rec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, _time0: f64, _time1: f64) -> Option<Aabb> {
|
||||||
|
Some(Aabb {
|
||||||
|
minimum: Point3::new(
|
||||||
|
self.a.x().min(self.b.x()).min(self.c.x()),
|
||||||
|
self.a.y().min(self.b.y()).min(self.c.y()),
|
||||||
|
self.a.z().min(self.b.z()).min(self.c.z()),
|
||||||
|
),
|
||||||
|
maximum: Point3::new(
|
||||||
|
self.a.x().max(self.b.x()).max(self.c.x()),
|
||||||
|
self.a.y().max(self.b.y()).max(self.c.y()),
|
||||||
|
self.a.z().max(self.b.z()).max(self.c.z()),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
61
src/vec3.rs
61
src/vec3.rs
@ -1,5 +1,5 @@
|
|||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub};
|
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, Index, MulAssign, IndexMut};
|
||||||
use rand::distributions::{Distribution, Uniform};
|
use rand::distributions::{Distribution, Uniform};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@ -228,6 +228,32 @@ impl Mul<Vec3> for f64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Mul<f64> for &Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
|
||||||
|
fn mul(self, other: f64) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: self.x * other,
|
||||||
|
y: self.y * other,
|
||||||
|
z: self.z * other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<&Vec3> for f64 {
|
||||||
|
type Output = Vec3;
|
||||||
|
|
||||||
|
fn mul(self, other: &Vec3) -> Vec3 {
|
||||||
|
other * self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MulAssign<f64> for Vec3 {
|
||||||
|
fn mul_assign(&mut self, rhs: f64) {
|
||||||
|
*self = *self * rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Div<Vec3> for Vec3 {
|
impl Div<Vec3> for Vec3 {
|
||||||
type Output = Vec3;
|
type Output = Vec3;
|
||||||
|
|
||||||
@ -257,3 +283,36 @@ impl PartialEq for Vec3 {
|
|||||||
self.x == other.x() && self.y == other.y() && self.z == other.z()
|
self.x == other.x() && self.y == other.y() && self.z == other.z()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Index<i32> for Vec3 {
|
||||||
|
type Output = f64;
|
||||||
|
fn index(&self, index: i32) -> &Self::Output {
|
||||||
|
match index {
|
||||||
|
0 => &self.x,
|
||||||
|
1 => &self.y,
|
||||||
|
2 => &self.z,
|
||||||
|
_ => panic!("Unknown index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl IndexMut<i32> for Vec3 {
|
||||||
|
fn index_mut(&mut self, index: i32) -> &mut f64{
|
||||||
|
match index {
|
||||||
|
0 => &mut self.x,
|
||||||
|
1 => &mut self.y,
|
||||||
|
2 => &mut self.z,
|
||||||
|
_ => panic!("Unknown index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Index<usize> for Vec3 {
|
||||||
|
type Output = f64;
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
match index {
|
||||||
|
0 => &self.x,
|
||||||
|
1 => &self.y,
|
||||||
|
2 => &self.z,
|
||||||
|
_ => panic!("Unknown index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
19814
teapot.obj
Normal file
19814
teapot.obj
Normal file
File diff suppressed because it is too large
Load Diff
BIN
textures/earthmap.jpg
Normal file
BIN
textures/earthmap.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 158 KiB |
Reference in New Issue
Block a user