Browse Source

wip

master
Stephen 3 weeks ago
commit
6bbc3b1cdb
21 changed files with 2784 additions and 0 deletions
  1. +1
    -0
      .gitignore
  2. +639
    -0
      Cargo.lock
  3. +19
    -0
      Cargo.toml
  4. +93
    -0
      src/aabb.rs
  5. +159
    -0
      src/aarect.rs
  6. +91
    -0
      src/block.rs
  7. +90
    -0
      src/block_texture.rs
  8. +163
    -0
      src/bvh.rs
  9. +60
    -0
      src/camera.rs
  10. +98
    -0
      src/constant_medium.rs
  11. +268
    -0
      src/hittable.rs
  12. +53
    -0
      src/hittable_list.rs
  13. +47
    -0
      src/image.rs
  14. +232
    -0
      src/main.rs
  15. +151
    -0
      src/material.rs
  16. +48
    -0
      src/mc_world.rs
  17. +117
    -0
      src/perlin.rs
  18. +26
    -0
      src/ray.rs
  19. +71
    -0
      src/sphere.rs
  20. +144
    -0
      src/texture.rs
  21. +214
    -0
      src/vector.rs

+ 1
- 0
.gitignore View File

@ -0,0 +1 @@
/target

+ 639
- 0
Cargo.lock View File

@ -0,0 +1,639 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[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 = "aho-corasick"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bit_field"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bytemuck"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12"
dependencies = [
"cfg-if",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
dependencies = [
"autocfg",
"cfg-if",
"lazy_static",
]
[[package]]
name = "deflate"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
dependencies = [
"adler32",
"byteorder",
]
[[package]]
name = "derivative"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dyn-clonable"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4"
dependencies = [
"dyn-clonable-impl",
"dyn-clone",
]
[[package]]
name = "dyn-clonable-impl"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dyn-clone"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "fastanvil"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bced6118a206a80b7e16a6cfdc33f2c346864f2825ee4f67ba6457d6c8a1ddf1"
dependencies = [
"bit_field",
"byteorder",
"fastnbt",
"flate2",
"image",
"lazy_static",
"log",
"num_enum",
"serde",
]
[[package]]
name = "fastnbt"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d0e1114ddb912cbffe6057294ecefd11aeec30417f1e486fbb7fad1e0f0375"
dependencies = [
"byteorder",
"serde",
"thiserror",
]
[[package]]
name = "flate2"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
dependencies = [
"cfg-if",
"crc32fast",
"libc",
"miniz_oxide 0.4.4",
]
[[package]]
name = "getrandom"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gif"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a668f699973d0f573d15749b7002a9ac9e1f9c6b220e7b165601334c173d8de"
dependencies = [
"color_quant",
"weezl",
]
[[package]]
name = "hermit-abi"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
dependencies = [
"libc",
]
[[package]]
name = "image"
version = "0.23.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"gif",
"jpeg-decoder",
"num-iter",
"num-rational",
"num-traits",
"png",
"scoped_threadpool",
"tiff",
]
[[package]]
name = "jpeg-decoder"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
dependencies = [
"rayon",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "memoffset"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d"
dependencies = [
"autocfg",
]
[[package]]
name = "minecraft_raytracer"
version = "0.1.0"
dependencies = [
"dyn-clonable",
"fastanvil",
"fastnbt",
"image",
"rand",
"rayon",
"regex",
]
[[package]]
name = "miniz_oxide"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
dependencies = [
"adler32",
]
[[package]]
name = "miniz_oxide"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
dependencies = [
"adler",
"autocfg",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "num_enum"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "226b45a5c2ac4dd696ed30fa6b94b057ad909c7b7fc2e0d0808192bced894066"
dependencies = [
"derivative",
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c0fd9eba1d5db0994a239e09c1be402d35622277e35468ba891aa5e3188ce7e"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "png"
version = "0.16.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
dependencies = [
"bitflags",
"crc32fast",
"deflate",
"miniz_oxide 0.3.7",
]
[[package]]
name = "ppv-lite86"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
dependencies = [
"toml",
]
[[package]]
name = "proc-macro2"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
dependencies = [
"rand_core",
]
[[package]]
name = "rayon"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"lazy_static",
"num_cpus",
]
[[package]]
name = "regex"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
[[package]]
name = "scoped_threadpool"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tiff"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
dependencies = [
"jpeg-decoder",
"miniz_oxide 0.4.4",
"weezl",
]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "weezl"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e"

+ 19
- 0
Cargo.toml View File

@ -0,0 +1,19 @@
[package]
name = "minecraft_raytracer"
version = "0.1.0"
authors = ["Stephen <stephen@stephendownward.ca>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.release]
debug = true
[dependencies]
fastanvil = "0.17"
fastnbt = "0.17"
image = "0.23"
rand = "0.8"
rayon = "1.5"
dyn-clonable = "0.9.0"
regex = "1"

+ 93
- 0
src/aabb.rs View File

@ -0,0 +1,93 @@
use crate::*;
#[derive(Debug, Copy, Clone)]
pub struct Aabb {
min: Point3,
max: Point3,
}
impl Aabb {
pub fn new(min: Point3, max: Point3) -> Self {
Self { min, max }
}
pub fn min(&self) -> Point3 {
self.min
}
pub fn max(&self) -> Point3 {
self.max
}
#[inline(always)]
pub fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> bool {
// X
let inv_d = 1.0 / r.direction()[0];
let mut t0 = (self.min[0] - r.orig[0]) * inv_d;
let mut t1 = (self.max[0] - r.orig[0]) * inv_d;
if inv_d < 0.0 {
std::mem::swap(&mut t0, &mut t1);
}
let t_min = if t0 > t_min { t0 } else { t_min };
let t_max = if t1 < t_max { t1 } else { t_max };
if t_max <= t_min {
return false;
}
// Y
let inv_d = 1.0 / r.direction()[1];
let mut t0 = (self.min[1] - r.orig[1]) * inv_d;
let mut t1 = (self.max[1] - r.orig[1]) * inv_d;
if inv_d < 0.0 {
std::mem::swap(&mut t0, &mut t1);
}
let t_min = if t0 > t_min { t0 } else { t_min };
let t_max = if t1 < t_max { t1 } else { t_max };
if t_max <= t_min {
return false;
}
// Z
let inv_d = 1.0 / r.direction()[2];
let mut t0 = (self.min[2] - r.orig[2]) * inv_d;
let mut t1 = (self.max[2] - r.orig[2]) * inv_d;
if inv_d < 0.0 {
std::mem::swap(&mut t0, &mut t1);
}
let t_min = if t0 > t_min { t0 } else { t_min };
let t_max = if t1 < t_max { t1 } else { t_max };
if t_max <= t_min {
return false;
}
true
}
pub fn surface_area(&self) -> f64 {
let delta = self.max - self.min;
2.0 * delta[0] * delta[1] + 2.0 * delta[0] * delta[2] + 2.0 * delta[1] * delta[2]
}
pub fn surrounding_box(box0: Aabb, box1: Aabb) -> Self {
let small = Point3::new(
f64::min(box0.min().x(), box1.min().x()),
f64::min(box0.min().y(), box1.min().y()),
f64::min(box0.min().z(), box1.min().z()),
);
let big = Point3::new(
f64::max(box0.max().x(), box1.max().x()),
f64::max(box0.max().y(), box1.max().y()),
f64::max(box0.max().z(), box1.max().z()),
);
Self::new(small, big)
}
}

+ 159
- 0
src/aarect.rs View File

@ -0,0 +1,159 @@
use crate::*;
#[derive(Clone)]
pub struct XyRect<'a> {
mp: &'a dyn Material,
x0: f64,
x1: f64,
y0: f64,
y1: f64,
k: f64,
}
impl<'a> XyRect<'a> {
pub fn new(x0: f64, x1: f64, y0: f64, y1: f64, k: f64, mp: &'a dyn Material) -> Self {
Self {
x0,
x1,
y0,
y1,
k,
mp,
}
}
}
impl<'a> Hittable for XyRect<'a> {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let t = (self.k - r.origin().z()) / r.direction().z();
if t < t_min || t > t_max {
return None;
}
let x = r.origin().x() + t * r.direction().x();
let y = r.origin().y() + t * r.direction().y();
if x < self.x0 || x > self.x1 || y < self.y0 || y > self.y1 {
return None;
}
let u = (x - self.x0) / (self.x1 - self.x0);
let v = (y - self.y0) / (self.y1 - self.y0);
let outward_normal = Vec3::new(0.0, 0.0, 1.0);
let p = r.at(t);
Some(HitRecord::new(p, outward_normal, self.mp, t, u, v, r))
}
fn bounding_box(&self) -> Aabb {
Aabb::new(
Point3::new(self.x0, self.y0, self.k - 0.0001),
Point3::new(self.x1, self.y1, self.k + 0.0001),
)
}
}
#[derive(Clone)]
pub struct XzRect<'a> {
mp: &'a dyn Material,
x0: f64,
x1: f64,
z0: f64,
z1: f64,
k: f64,
}
impl<'a> XzRect<'a> {
pub fn new(x0: f64, x1: f64, z0: f64, z1: f64, k: f64, mp: &'a dyn Material) -> Self {
Self {
x0,
x1,
z0,
z1,
k,
mp,
}
}
}
impl<'a> Hittable for XzRect<'a> {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let t = (self.k - r.origin().y()) / r.direction().y();
if t < t_min || t > t_max {
return None;
}
let x = r.origin().x() + t * r.direction().x();
let z = r.origin().z() + t * r.direction().z();
if x < self.x0 || x > self.x1 || z < self.z0 || z > self.z1 {
return None;
}
let u = (x - self.x0) / (self.x1 - self.x0);
let v = (z - self.z0) / (self.z1 - self.z0);
let outward_normal = Vec3::new(0.0, 1.0, 0.0);
let p = r.at(t);
Some(HitRecord::new(p, outward_normal, self.mp, t, u, v, r))
}
fn bounding_box(&self) -> Aabb {
Aabb::new(
Point3::new(self.x0, self.k - 0.0001, self.z0),
Point3::new(self.x1, self.k + 0.0001, self.z1),
)
}
}
#[derive(Clone)]
pub struct YzRect<'a> {
mp: &'a dyn Material,
y0: f64,
y1: f64,
z0: f64,
z1: f64,
k: f64,
}
impl<'a> YzRect<'a> {
pub fn new(y0: f64, y1: f64, z0: f64, z1: f64, k: f64, mp: &'a dyn Material) -> Self {
Self {
y0,
y1,
z0,
z1,
k,
mp,
}
}
}
impl<'a> Hittable for YzRect<'a> {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let t = (self.k - r.origin().x()) / r.direction().x();
if t < t_min || t > t_max {
return None;
}
let y = r.origin().y() + t * r.direction().y();
let z = r.origin().z() + t * r.direction().z();
if y < self.y0 || y > self.y1 || z < self.z0 || z > self.z1 {
return None;
}
let v = (y - self.y0) / (self.y1 - self.y0);
let u = (z - self.z0) / (self.z1 - self.z0);
let outward_normal = Vec3::new(1.0, 0.0, 0.0);
let p = r.at(t);
Some(HitRecord::new(p, outward_normal, self.mp, t, u, v, r))
}
fn bounding_box(&self) -> Aabb {
Aabb::new(
Point3::new(self.k - 0.0001, self.y0, self.z0),
Point3::new(self.k + 0.0001, self.y1, self.z1),
)
}
}

+ 91
- 0
src/block.rs View File

@ -0,0 +1,91 @@
use crate::*;
#[derive(Clone)]
pub struct Block<'a> {
min: Point3,
max: Point3,
sides: HittableList<'a>,
aabb: Aabb,
}
// TODO need to determine which side is the front somehow
impl<'a> Block<'a> {
pub fn new(p0: Point3, p1: Point3, mat: &'a BlockTexture) -> Self {
let mut sides = HittableList::new();
sides.add(Box::new(XyRect::new(
p0.x(),
p1.x(),
p0.y(),
p1.y(),
p1.z(),
mat.side.as_ref(),
)));
sides.add(Box::new(XyRect::new(
p0.x(),
p1.x(),
p0.y(),
p1.y(),
p0.z(),
mat.front.as_ref(), // arbitrary
)));
sides.add(Box::new(XzRect::new(
p0.x(),
p1.x(),
p0.z(),
p1.z(),
p1.y(),
mat.top.as_ref(),
)));
sides.add(Box::new(XzRect::new(
p0.x(),
p1.x(),
p0.z(),
p1.z(),
p0.y(),
mat.bottom.as_ref(),
)));
sides.add(Box::new(YzRect::new(
p0.y(),
p1.y(),
p0.z(),
p1.z(),
p1.x(),
mat.side.as_ref(),
)));
sides.add(Box::new(YzRect::new(
p0.y(),
p1.y(),
p0.z(),
p1.z(),
p0.x(),
mat.side.as_ref(),
)));
Self {
min: p0,
max: p1,
sides,
aabb: Aabb::new(p0, p1),
}
}
}
impl<'a> Hittable for Block<'a> {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
if !self.aabb.hit(r, t_min, t_max) {
return None;
}
self.sides.hit(r, t_min, t_max)
}
fn bounding_box(&self) -> Aabb {
self.aabb
}
}

+ 90
- 0
src/block_texture.rs View File

@ -0,0 +1,90 @@
use crate::*;
use std::path::Path;
// more of a BlockMaterial - TODO rename this
pub struct BlockTexture {
pub top: Box<dyn Material>,
pub front: Box<dyn Material>,
pub side: Box<dyn Material>,
pub bottom: Box<dyn Material>,
}
impl BlockTexture {
// TODO support more than PNGs
pub fn new(dir: &str, name: &str) -> Option<Self> {
let name = if name == "wall_torch" { "torch" } else { name };
let emit = ["torch", "glowstone", "jack_o_lantern"].contains(&name);
let dir = Path::new(dir);
let mut top = None;
let mut front = None;
let mut side = None;
let mut bottom = None;
if let Some(sides) = ImageTexture::new(&dir.join(format!("{}{}", name, ".png"))) {
top = Some(sides.clone());
front = Some(sides.clone());
side = Some(sides.clone());
bottom = Some(sides);
}
if let Some(mut new_top) = ImageTexture::new(&dir.join(format!("{}{}", name, "_top.png"))) {
if name == "grass_block" {
new_top.tint(Color::new(0.533333, 0.733333, 0.4039));
}
top = Some(new_top);
}
if let Some(new_side) = ImageTexture::new(&dir.join(format!("{}{}", name, "_side.png"))) {
side = Some(new_side.clone());
front = Some(new_side.clone());
bottom = Some(new_side);
}
if let Some(new_bottom) = ImageTexture::new(&dir.join(format!("{}{}", name, "_bottom.png")))
{
bottom = Some(new_bottom);
}
// block-specific exceptions
if name == "grass_block" {
if let Some(new_bottom) = ImageTexture::new(&dir.join("dirt.png")) {
bottom = Some(new_bottom);
}
}
if name == "tnt" {
println!("{:?} {:?} {:?} {:?}", top, front, side, bottom);
}
let (top, front, side, bottom): (
Box<dyn Material>,
Box<dyn Material>,
Box<dyn Material>,
Box<dyn Material>,
) = if emit {
(
Box::new(DiffuseLight::new(Box::new(top?))),
Box::new(DiffuseLight::new(Box::new(front?))),
Box::new(DiffuseLight::new(Box::new(side?))),
Box::new(DiffuseLight::new(Box::new(bottom?))),
)
} else {
(
Box::new(Lambertian::new(Box::new(top?))),
Box::new(Lambertian::new(Box::new(front?))),
Box::new(Lambertian::new(Box::new(side?))),
Box::new(Lambertian::new(Box::new(bottom?))),
)
};
Some(Self {
top,
front,
side,
bottom,
})
}
}

+ 163
- 0
src/bvh.rs View File

@ -0,0 +1,163 @@
use crate::*;
#[derive(Clone)]
enum BvhElement<'a> {
Node(Aabb),
Leaf(Box<dyn Hittable + 'a>),
PlaceHolder,
}
#[derive(Clone)]
pub struct Bvh<'a> {
nodes: Vec<BvhElement<'a>>,
}
impl<'a> Bvh<'a> {
pub fn new(mut list: HittableList<'a>) -> Self {
let mut s = Self { nodes: Vec::new() };
s.nodes_from_objects(0, &mut list.objects);
s
}
fn nodes_from_objects(&mut self, id: usize, objects: &mut [Box<dyn Hittable + 'a>]) -> Aabb {
if objects.len() == 1 {
self.set_node(id, BvhElement::Leaf(objects[0].to_owned()));
return objects[0].bounding_box();
}
let mut best_axis = 0;
let mut best_idx = 0;
let mut best_h = f64::INFINITY;
for axis in 0..=2 {
// sort objects by axis
objects.sort_by(|a, b| box_compare(a.as_ref(), b.as_ref(), axis));
// for each object, try splitting on it
for i in 1..objects.len() {
// left
let left_box = objects[1..i]
.iter()
.fold(objects[0].bounding_box(), |a, b| {
Aabb::surrounding_box(a, b.bounding_box())
});
let left_sa = left_box.surface_area();
// right
let right_box = objects[(i + 1)..]
.iter()
.fold(objects[i].bounding_box(), |a, b| {
Aabb::surrounding_box(a, b.bounding_box())
});
let right_sa = right_box.surface_area();
let h = left_sa * i as f64 + right_sa * (objects.len() - i) as f64;
if h < best_h {
best_axis = axis;
best_idx = i;
best_h = h;
}
}
}
let sa = objects
.iter()
.fold(objects[0].bounding_box(), |a, b| {
Aabb::surrounding_box(a, b.bounding_box())
})
.surface_area();
if best_h >= sa * objects.len() as f64 {
// Not worth splitting further.
let mut lst = HittableList::new();
for obj in objects {
lst.add(obj.to_owned());
}
let aabb = lst.bounding_box();
self.set_node(id, BvhElement::Leaf(Box::new(lst)));
return aabb;
}
let axis = best_axis;
objects.sort_by(|a, b| box_compare(a.as_ref(), b.as_ref(), axis));
let left = self.nodes_from_objects(2 * id + 1, &mut objects[..best_idx]); // left
let right = self.nodes_from_objects(2 * id + 2, &mut objects[best_idx..]); // right
let aabb = Aabb::surrounding_box(left, right);
self.set_node(id, BvhElement::Node(aabb));
aabb
}
fn set_node(&mut self, i: usize, value: BvhElement<'a>) {
while i >= self.nodes.len() {
self.nodes.push(BvhElement::PlaceHolder);
}
if let BvhElement::PlaceHolder = self.nodes[i] {
} else {
panic!("Error while generating BvhNode");
}
self.nodes[i] = value;
}
fn hit_child(&self, r: &Ray, i: usize, t_min: f64, t_max: f64) -> Option<HitRecord> {
match &self.nodes[i] {
BvhElement::Node(aabb) => {
if !aabb.hit(r, t_min, t_max) {
return None;
}
let hit_left = self.hit_child(r, 2 * i + 1, t_min, t_max);
let t_max = match &hit_left {
Some(x) => x.t,
None => t_max,
};
let hit_right = self.hit_child(r, 2 * i + 2, t_min, t_max);
match hit_right {
Some(x) => Some(x),
None => hit_left,
}
}
BvhElement::Leaf(leaf) => leaf.hit(r, t_min, t_max),
BvhElement::PlaceHolder => unreachable!(),
}
}
}
impl<'a> Hittable for Bvh<'a> {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
self.hit_child(r, 0, t_min, t_max)
}
fn bounding_box(&self) -> Aabb {
match &self.nodes[0] {
BvhElement::Node(aabb) => aabb.to_owned(),
BvhElement::Leaf(leaf) => leaf.bounding_box(),
BvhElement::PlaceHolder => unreachable!(),
}
}
}
fn box_compare(a: &dyn Hittable, b: &dyn Hittable, axis: usize) -> std::cmp::Ordering {
let box_a = a.bounding_box();
let box_b = b.bounding_box();
box_a.min()[axis]
.partial_cmp(&box_b.min()[axis])
.expect("Invalid float")
}

+ 60
- 0
src/camera.rs View File

@ -0,0 +1,60 @@
use crate::*;
pub struct Camera {
origin: Point3,
lower_left_corner: Point3,
horizontal: Vec3,
vertical: Vec3,
u: Vec3,
v: Vec3,
w: Vec3,
lens_radius: f64,
}
impl Camera {
pub fn new(
look_from: Point3,
look_at: Point3,
vup: Vec3,
vfov: f64,
aspect_ratio: f64,
aperture: f64,
focus_dist: f64,
) -> Self {
let theta = vfov.to_radians();
let h = (theta / 2.0).tan();
let viewport_height = 2.0 * h;
let viewport_width = viewport_height * aspect_ratio;
let w = (look_from - look_at).unit();
let u = Vec3::cross(vup, w).unit();
let v = Vec3::cross(w, u);
let origin = look_from;
let horizontal = u * viewport_width * focus_dist;
let vertical = v * viewport_height * focus_dist;
let lower_left_corner = origin - horizontal / 2.0 - vertical / 2.0 - w * focus_dist;
let lens_radius = aperture / 2.0;
Self {
origin,
horizontal,
vertical,
lower_left_corner,
u,
v,
w,
lens_radius,
}
}
pub fn get_ray(&self, s: f64, t: f64) -> Ray {
let rd = Vec3::random_in_unit_disk() * self.lens_radius;
let offset = self.u * rd.x() + self.v * rd.y();
Ray::new(
self.origin + offset,
self.lower_left_corner + self.horizontal * s + self.vertical * t - self.origin - offset,
)
}
}

+ 98
- 0
src/constant_medium.rs View File

@ -0,0 +1,98 @@
use crate::*;
#[derive(Clone)]
pub struct ConstantMedium<'a> {
boundary: Box<dyn Hittable + 'a>,
phase_function: Box<dyn Material>,
neg_inv_density: f64,
}
impl<'a> ConstantMedium<'a> {
pub fn new(boundary: Box<dyn Hittable + 'a>, d: f64, texture: Box<dyn Texture>) -> Self {
Self {
boundary,
neg_inv_density: -1.0 / d,
phase_function: Box::new(Isotropic::new(texture)),
}
}
pub fn from_color(boundary: Box<dyn Hittable + 'a>, d: f64, c: Color) -> Self {
Self {
boundary,
neg_inv_density: -1.0 / d,
phase_function: Box::new(Isotropic::from_color(c)),
}
}
}
impl<'a> Hittable for ConstantMedium<'a> {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let mut rec1 = self.boundary.hit(r, -f64::INFINITY, f64::INFINITY)?;
let mut rec2 = self.boundary.hit(r, rec1.t + 0.0001, f64::INFINITY)?;
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 < 0.0 {
rec1.t = 0.0;
}
let ray_length = r.direction().length();
let dist_inside_boundary = (rec2.t - rec1.t) * ray_length;
let hit_distance = self.neg_inv_density * thread_rng().gen::<f64>().ln();
if hit_distance > dist_inside_boundary {
return None;
}
let t = rec1.t + hit_distance / ray_length;
let p = r.at(t);
Some(HitRecord {
p,
normal: Vec3::new(1.0, 0.0, 0.0), // arbitrary
material: self.phase_function.as_ref(),
t,
u: 0.0,
v: 0.0,
front_face: true,
})
}
fn bounding_box(&self) -> Aabb {
self.boundary.bounding_box()
}
}
#[derive(Clone)]
pub struct Isotropic {
albedo: Box<dyn Texture>,
}
impl Isotropic {
pub fn new(albedo: Box<dyn Texture>) -> Self {
Self { albedo }
}
pub fn from_color(color: Color) -> Self {
Self {
albedo: Box::new(SolidColor::new(color)),
}
}
}
impl Material for Isotropic {
fn scatter(&self, ray_in: &Ray, hit_record: &HitRecord) -> Option<ScatterData> {
let scattered = Ray::new(hit_record.p, Vec3::random_in_unit_sphere());
let attenuated = self.albedo.value(hit_record.u, hit_record.v, hit_record.p);
Some(ScatterData::new(attenuated, scattered))
}
}

+ 268
- 0
src/hittable.rs View File

@ -0,0 +1,268 @@
use crate::*;
pub struct HitRecord<'a> {
pub p: Point3,
pub normal: Vec3,
pub material: &'a dyn Material,
pub t: f64,
pub u: f64,
pub v: f64,
pub front_face: bool,
}
impl<'a> HitRecord<'a> {
pub fn new(
p: Point3,
outward_normal: Vec3,
material: &'a dyn Material,
t: f64,
u: f64,
v: f64,
ray: &Ray,
) -> Self {
let front_face = Vec3::dot(ray.direction(), outward_normal) < 0.0;
let normal = if front_face {
outward_normal
} else {
-outward_normal
};
Self {
p,
normal,
material,
t,
u,
v,
front_face,
}
}
}
#[clonable]
pub trait Hittable: Send + Sync + Clone {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord>;
fn bounding_box(&self) -> Aabb;
}
#[derive(Clone)]
pub struct Translate<'a> {
child: Box<dyn Hittable + 'a>,
offset: Vec3,
}
impl<'a> Translate<'a> {
pub fn new(child: Box<dyn Hittable + 'a>, offset: Vec3) -> Self {
Self { child, offset }
}
}
impl<'a> Hittable for Translate<'a> {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let moved_r = Ray::new(r.origin() - self.offset, r.direction());
let rec = self.child.hit(&moved_r, t_min, t_max)?;
let rec = HitRecord::new(
rec.p + self.offset,
rec.normal,
rec.material,
rec.t,
rec.u,
rec.v,
&moved_r,
);
Some(rec)
}
fn bounding_box(&self) -> Aabb {
let res = self.child.bounding_box();
Aabb::new(res.min() + self.offset, res.max() + self.offset)
}
}
#[derive(Clone)]
pub struct RotateX<'a> {
child: Box<dyn Hittable + 'a>,
sin_theta: f64,
cos_theta: f64,
bbox: Aabb,
}
impl<'a> RotateX<'a> {
pub fn new(child: Box<dyn Hittable + 'a>, angle: f64) -> Self {
let radians = angle.to_radians();
let sin_theta = radians.sin();
let cos_theta = radians.cos();
let bbox = child.bounding_box();
let mut min = Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
let mut max = Point3::new(-f64::INFINITY, -f64::INFINITY, -f64::INFINITY);
for i in 0..2 {
for j in 0..2 {
for k in 0..2 {
let i = i as f64;
let j = j as f64;
let k = k as f64;
let x = i * bbox.max().x() + (1.0 - i) * bbox.min().x();
let y = j * bbox.max().y() + (1.0 - j) * bbox.min().y();
let z = k * bbox.max().z() + (1.0 - k) * bbox.min().z();
let new_y = cos_theta * y + sin_theta * z;
let new_z = -sin_theta * y + cos_theta * z;
let tester = Vec3::new(x, new_y, new_z);
for c in 0..3 {
min[c] = f64::min(min[c], tester[c]);
max[c] = f64::max(max[c], tester[c]);
}
}
}
}
let bbox = Aabb::new(min, max);
Self {
child,
sin_theta,
cos_theta,
bbox,
}
}
}
impl<'a> Hittable for RotateX<'a> {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let mut origin = r.origin();
let mut direction = r.direction();
origin[1] = self.cos_theta * r.origin()[1] - self.sin_theta * r.origin()[2];
origin[2] = self.sin_theta * r.origin()[1] + self.cos_theta * r.origin()[2];
direction[1] = self.cos_theta * r.direction()[1] - self.sin_theta * r.direction()[2];
direction[2] = self.sin_theta * r.direction()[1] + self.cos_theta * r.direction()[2];
let rotated_r = Ray::new(origin, direction);
let rec = self.child.hit(&rotated_r, t_min, t_max)?;
let mut p = rec.p;
let mut normal = rec.normal;
p[1] = self.cos_theta * rec.p[1] + self.sin_theta * rec.p[2];
p[2] = -self.sin_theta * rec.p[1] + self.cos_theta * rec.p[2];
normal[1] = self.cos_theta * rec.normal[1] + self.sin_theta * rec.normal[2];
normal[2] = -self.sin_theta * rec.normal[1] + self.cos_theta * rec.normal[2];
Some(HitRecord::new(
p,
normal,
rec.material,
rec.t,
rec.u,
rec.v,
&rotated_r,
))
}
fn bounding_box(&self) -> Aabb {
self.bbox
}
}
#[derive(Clone)]
pub struct RotateY<'a> {
child: Box<dyn Hittable + 'a>,
sin_theta: f64,
cos_theta: f64,
bbox: Aabb,
}
impl<'a> RotateY<'a> {
pub fn new(child: Box<dyn Hittable + 'a>, angle: f64) -> Self {
let radians = angle.to_radians();
let sin_theta = radians.sin();
let cos_theta = radians.cos();
let bbox = child.bounding_box();
let mut min = Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
let mut max = Point3::new(-f64::INFINITY, -f64::INFINITY, -f64::INFINITY);
for i in 0..2 {
for j in 0..2 {
for k in 0..2 {
let i = i as f64;
let j = j as f64;
let k = k as f64;
let x = i * bbox.max().x() + (1.0 - i) * bbox.min().x();
let y = j * bbox.max().y() + (1.0 - j) * bbox.min().y();