add assignment 1d
This commit is contained in:
parent
70cba6d253
commit
a84ee1724d
27 changed files with 4244 additions and 0 deletions
10
assignment-1d/.gitignore
vendored
Normal file
10
assignment-1d/.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/target
|
||||||
|
/assignment-1c
|
||||||
|
/raytracer1c
|
||||||
|
/examples/*.png
|
||||||
|
*.ppm
|
||||||
|
*.zip
|
||||||
|
*.pdf
|
||||||
|
perf.data*
|
||||||
|
flamegraph.svg
|
||||||
|
showcase.png
|
0
assignment-1d/ASSIGNMENT.md
Normal file
0
assignment-1d/ASSIGNMENT.md
Normal file
980
assignment-1d/Cargo.lock
generated
Normal file
980
assignment-1d/Cargo.lock
generated
Normal file
|
@ -0,0 +1,980 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.68"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "approx"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "assignment-1d"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"base64",
|
||||||
|
"clap",
|
||||||
|
"derivative",
|
||||||
|
"generator",
|
||||||
|
"itertools",
|
||||||
|
"nalgebra",
|
||||||
|
"num",
|
||||||
|
"ordered-float",
|
||||||
|
"rand",
|
||||||
|
"rayon",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.67"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"clap_derive",
|
||||||
|
"clap_lex",
|
||||||
|
"is-terminal",
|
||||||
|
"once_cell",
|
||||||
|
"strsim",
|
||||||
|
"termcolor",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
|
||||||
|
dependencies = [
|
||||||
|
"os_str_bytes",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-channel"
|
||||||
|
version = "0.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-deque"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-epoch"
|
||||||
|
version = "0.9.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"cfg-if",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"memoffset",
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "either"
|
||||||
|
version = "1.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
|
||||||
|
dependencies = [
|
||||||
|
"errno-dragonfly",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno-dragonfly"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generator"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d266041a359dfa931b370ef684cceb84b166beb14f7f0421f4a6a3d0c446d12e"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"rustversion",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.27.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "io-lifetimes"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"io-lifetimes",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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.139"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matrixmultiply"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84"
|
||||||
|
dependencies = [
|
||||||
|
"rawpointer",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nalgebra"
|
||||||
|
version = "0.32.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6515c882ebfddccaa73ead7320ca28036c4bc84c9bcca3cc0cbba8efe89223a"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"matrixmultiply",
|
||||||
|
"nalgebra-macros",
|
||||||
|
"num-complex",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
"simba",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nalgebra-macros"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d232c68884c0c99810a5a4d333ef7e47689cfd0edc85efc9e54e1e6bf5212766"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-ansi-term"
|
||||||
|
version = "0.46.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||||
|
dependencies = [
|
||||||
|
"overload",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-iter",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.30.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-float"
|
||||||
|
version = "3.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d84eb1409416d254e4a9c8fa56cc24701755025b458f0fcd8e59e1f5f40c23bf"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "overload"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rawpointer"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"rayon-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon-core"
|
||||||
|
version = "1.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"crossbeam-deque",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"num_cpus",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.36.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"io-lifetimes",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "safe_arch"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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.152"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharded-slab"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simba"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50582927ed6f77e4ac020c057f37a268fc6aebc29225050365aacbb9deeeddc4"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"num-complex",
|
||||||
|
"num-traits",
|
||||||
|
"paste",
|
||||||
|
"wide",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing"
|
||||||
|
version = "0.1.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tracing-attributes",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-attributes"
|
||||||
|
version = "0.1.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-core"
|
||||||
|
version = "0.1.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"valuable",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-log"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.3.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
|
||||||
|
dependencies = [
|
||||||
|
"nu-ansi-term",
|
||||||
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
|
"thread_local",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wide"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae41ecad2489a1655c8ef8489444b0b113c0a0c795944a3572a0931cf7d2525c"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"safe_arch",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.39.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_msvc 0.39.0",
|
||||||
|
"windows_i686_gnu 0.39.0",
|
||||||
|
"windows_i686_msvc 0.39.0",
|
||||||
|
"windows_x86_64_gnu 0.39.0",
|
||||||
|
"windows_x86_64_msvc 0.39.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc 0.42.1",
|
||||||
|
"windows_i686_gnu 0.42.1",
|
||||||
|
"windows_i686_msvc 0.42.1",
|
||||||
|
"windows_x86_64_gnu 0.42.1",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc 0.42.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.39.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.39.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.39.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.39.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.39.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
34
assignment-1d/Cargo.toml
Normal file
34
assignment-1d/Cargo.toml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
[package]
|
||||||
|
name = "assignment-1d"
|
||||||
|
authors = ["Michael Zhang <zhan4854@umn.edu>"]
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# For profiling with flamegraphs
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
# Optimize for size when creating handin
|
||||||
|
[profile.release-handin]
|
||||||
|
inherits = "release"
|
||||||
|
strip = true
|
||||||
|
lto = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "raytracer1d"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { version = "1.0.68", features = ["backtrace"] }
|
||||||
|
base64 = "0.21.0"
|
||||||
|
clap = { version = "4.1.4", features = ["cargo", "derive"] }
|
||||||
|
derivative = "2.2.0"
|
||||||
|
generator = "0.7.2"
|
||||||
|
itertools = "0.10.5"
|
||||||
|
nalgebra = "0.32.1"
|
||||||
|
num = { version = "0.4.0", features = ["serde"] }
|
||||||
|
ordered-float = "3.4.0"
|
||||||
|
rand = "0.8.5"
|
||||||
|
rayon = "1.6.1"
|
||||||
|
tracing = "0.1.37"
|
||||||
|
tracing-subscriber = "0.3.16"
|
46
assignment-1d/Makefile
Normal file
46
assignment-1d/Makefile
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
.PRECIOUS: $(EXAMPLES_PPM)
|
||||||
|
|
||||||
|
RAYTRACER_FLAGS :=
|
||||||
|
DOCKER := docker
|
||||||
|
ZIP := zip
|
||||||
|
PANDOC := pandoc
|
||||||
|
CONVERT := convert
|
||||||
|
|
||||||
|
HANDIN := ./hw1d.michael.zhang.zip
|
||||||
|
BINARY := ./raytracer1d
|
||||||
|
SOURCES := Cargo.toml $(shell find -name "*.rs")
|
||||||
|
|
||||||
|
EXAMPLES := $(shell find examples -name "*.txt")
|
||||||
|
EXAMPLES_PPM := $(patsubst %.txt,%.ppm,$(EXAMPLES))
|
||||||
|
EXAMPLES_PNG := $(patsubst %.txt,%.png,$(EXAMPLES))
|
||||||
|
|
||||||
|
all: $(HANDIN)
|
||||||
|
|
||||||
|
$(BINARY): $(SOURCES)
|
||||||
|
mkdir -p target/docker
|
||||||
|
$(DOCKER) run \
|
||||||
|
--rm \
|
||||||
|
-v "$(shell pwd)":/usr/src/myapp \
|
||||||
|
-v cargo-registry:/usr/local/cargo \
|
||||||
|
--user "$(shell id -u)":"$(shell id -g)" \
|
||||||
|
-w /usr/src/myapp \
|
||||||
|
-e CARGO_TARGET_DIR=/usr/src/myapp/target/docker \
|
||||||
|
rust \
|
||||||
|
cargo build --profile release-handin
|
||||||
|
mv target/docker/release-handin/raytracer1d $@
|
||||||
|
|
||||||
|
$(HANDIN): $(BINARY) Makefile Cargo.toml Cargo.lock README.md $(EXAMPLES_PNG) $(EXAMPLES_PPM)
|
||||||
|
$(ZIP) -r $@ src examples $^
|
||||||
|
|
||||||
|
examples/%.ppm: examples/%.txt $(SOURCES)
|
||||||
|
cargo run --release -- -o $@ $(RAYTRACER_FLAGS) $<
|
||||||
|
|
||||||
|
examples/%.png: examples/%.ppm
|
||||||
|
convert $< $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf target/docker \
|
||||||
|
$(HANDIN) $(BINARY) \
|
||||||
|
$(EXAMPLES_PPM) $(EXAMPLES_PNG)
|
29
assignment-1d/README.md
Normal file
29
assignment-1d/README.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# Raycaster
|
||||||
|
|
||||||
|
## Bundle contents
|
||||||
|
|
||||||
|
Writeup is located at `/writeup.pdf`.
|
||||||
|
|
||||||
|
The binary can be found at `/raytracer1b`. Run `./raytracer1b --help` to see
|
||||||
|
how to use it. The binary has been built using the Rust Docker image, which
|
||||||
|
should have an environment similar to CSELabs. If there is trouble running the
|
||||||
|
binary, try building from source, as documented below.
|
||||||
|
|
||||||
|
Examples are found in the `examples` directory. The text files are the input
|
||||||
|
sources, and the ppm files are the corresponding outputs. They have been
|
||||||
|
generated by running this program. For convenience, pngs have also been provided
|
||||||
|
using imagemagick.
|
||||||
|
|
||||||
|
## Showcase image
|
||||||
|
|
||||||
|
The showcase image can be found at `/showcase.png`.
|
||||||
|
|
||||||
|
## Building from source
|
||||||
|
|
||||||
|
The Makefile currently uses Docker to produce a more consistent build. If you
|
||||||
|
have a Rust+Cargo toolchain installed locally, it's also possible to build the
|
||||||
|
source using just:
|
||||||
|
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
The binary will be found in `target/release`.
|
2
assignment-1d/examples/.gitignore
vendored
Normal file
2
assignment-1d/examples/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Necessary files
|
||||||
|
!/earthtexture.ppm
|
17
assignment-1d/examples/Hw1cSample1.txt
Executable file
17
assignment-1d/examples/Hw1cSample1.txt
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
eye 0 0 0
|
||||||
|
viewdir 0 0 -1
|
||||||
|
updir 0 1 0
|
||||||
|
hfov 60
|
||||||
|
imsize 512 256
|
||||||
|
bkgcolor 0.1 0.1 0.1
|
||||||
|
light -2 1 0 1 1 1 1
|
||||||
|
mtlcolor 1 0 1 1 1 1 0.1 0.4 0.4 20
|
||||||
|
|
||||||
|
|
||||||
|
v -2 1 -5
|
||||||
|
v -1 -1 -4
|
||||||
|
v 1 -1 -4
|
||||||
|
v 2 1 -6
|
||||||
|
|
||||||
|
f 1 2 3
|
||||||
|
f 1 3 4
|
21
assignment-1d/examples/Hw1cSample2.txt
Executable file
21
assignment-1d/examples/Hw1cSample2.txt
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
eye 0 0 0
|
||||||
|
viewdir 0 0 -1
|
||||||
|
updir 0 1 0
|
||||||
|
hfov 60
|
||||||
|
imsize 512 256
|
||||||
|
bkgcolor 0.1 0.1 0.1
|
||||||
|
light -2 1 0 1 1 1 1
|
||||||
|
mtlcolor 1 0 1 1 1 1 0.1 0.4 0.4 20
|
||||||
|
|
||||||
|
|
||||||
|
v -2 1 -5
|
||||||
|
v -1 -1 -4
|
||||||
|
v 1 -1 -4
|
||||||
|
v 2 1 -6
|
||||||
|
|
||||||
|
vn -2 1 1
|
||||||
|
vn -1 -1 1
|
||||||
|
vn 2 1 1
|
||||||
|
|
||||||
|
f 1//1 2//2 3//3
|
||||||
|
f 1 3 4
|
10
assignment-1d/examples/Hw1cSample3.txt
Executable file
10
assignment-1d/examples/Hw1cSample3.txt
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
eye 2 -6 1
|
||||||
|
viewdir -1 3 -0.5
|
||||||
|
updir 0 0 1
|
||||||
|
hfov 50
|
||||||
|
imsize 512 512
|
||||||
|
bkgcolor 0.5 0.7 0.9
|
||||||
|
light 0 1 -1 0 1 1 1
|
||||||
|
mtlcolor 0 1 0 1 1 1 0.2 0.8 0.1 20
|
||||||
|
texture earthtexture.ppm
|
||||||
|
sphere 0 0 0 2
|
1081
assignment-1d/examples/earthtexture.ppm
Executable file
1081
assignment-1d/examples/earthtexture.ppm
Executable file
File diff suppressed because one or more lines are too long
27
assignment-1d/examples/smooth.txt
Normal file
27
assignment-1d/examples/smooth.txt
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
eye 0 0 4
|
||||||
|
viewdir 0 0 -1
|
||||||
|
updir 1 1 0
|
||||||
|
hfov 60
|
||||||
|
imsize 640 480
|
||||||
|
|
||||||
|
bkgcolor 0.4 0.4 0.4
|
||||||
|
light -2 1 0 1 1 1 1
|
||||||
|
mtlcolor 1 0.6 1 1 1 1 0.1 0.4 0.4 20
|
||||||
|
|
||||||
|
|
||||||
|
v 0 0 0
|
||||||
|
v 1 0 -1
|
||||||
|
v 0 1 -1
|
||||||
|
v -1 0 -1
|
||||||
|
v 0 -1 -1
|
||||||
|
|
||||||
|
vn 0 0 1
|
||||||
|
vn 1 0 1
|
||||||
|
vn 0 1 1
|
||||||
|
vn -1 0 1
|
||||||
|
vn 0 -1 1
|
||||||
|
|
||||||
|
f 1//1 2//2 3//3
|
||||||
|
f 1//1 3//3 4//4
|
||||||
|
f 1//1 4//4 5//5
|
||||||
|
f 1//1 5//5 2//2
|
45
assignment-1d/examples/soft-shadow-demo.txt
Normal file
45
assignment-1d/examples/soft-shadow-demo.txt
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
imsize 1024 768
|
||||||
|
eye 0 0 15
|
||||||
|
viewdir 0 0 -1
|
||||||
|
hfov 60
|
||||||
|
updir 0 1 0
|
||||||
|
bkgcolor 0.5 0.5 0.5
|
||||||
|
|
||||||
|
depthcueing 0.5 0.5 0.5 1 0.4 60 0
|
||||||
|
|
||||||
|
light 10 10 -10 1 1 1 1
|
||||||
|
|
||||||
|
mtlcolor 0.5 1 0.5 1 1 1 0.2 1 0.1 5
|
||||||
|
sphere 4.5 4.5 -20 4.5
|
||||||
|
sphere -4.5 -4.5 -20 4.5
|
||||||
|
|
||||||
|
mtlcolor 1 0.5 0.5 1 1 1 0.2 0.8 0 5
|
||||||
|
sphere -10 0 -30 4
|
||||||
|
sphere -20 0 -30 4
|
||||||
|
sphere -30 0 -30 4
|
||||||
|
sphere -40 0 -30 4
|
||||||
|
sphere 0 0 -30 4
|
||||||
|
sphere 10 0 -30 4
|
||||||
|
sphere 20 0 -30 4
|
||||||
|
sphere 30 0 -30 4
|
||||||
|
sphere 40 0 -30 4
|
||||||
|
|
||||||
|
sphere -10 -10 -30 4
|
||||||
|
sphere -20 -10 -30 4
|
||||||
|
sphere -30 -10 -30 4
|
||||||
|
sphere -40 -10 -30 4
|
||||||
|
sphere 0 -10 -30 4
|
||||||
|
sphere 10 -10 -30 4
|
||||||
|
sphere 20 -10 -30 4
|
||||||
|
sphere 30 -10 -30 4
|
||||||
|
sphere 40 -10 -30 4
|
||||||
|
|
||||||
|
sphere -10 10 -30 4
|
||||||
|
sphere -20 10 -30 4
|
||||||
|
sphere -30 10 -30 4
|
||||||
|
sphere -40 10 -30 4
|
||||||
|
sphere 0 10 -30 4
|
||||||
|
sphere 10 10 -30 4
|
||||||
|
sphere 20 10 -30 4
|
||||||
|
sphere 30 10 -30 4
|
||||||
|
sphere 40 10 -30 4
|
127
assignment-1d/src/image.rs
Normal file
127
assignment-1d/src/image.rs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{BufRead, BufReader, Read, Write},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use generator::{done, Gn};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use nalgebra::Vector3;
|
||||||
|
|
||||||
|
/// A pixel color represented by a red, green, and blue value in the range 0-1.
|
||||||
|
pub type Color = Vector3<f64>;
|
||||||
|
|
||||||
|
/// A representation of an image
|
||||||
|
#[derive(Derivative)]
|
||||||
|
#[derivative(Debug)]
|
||||||
|
pub struct Image {
|
||||||
|
/// Width in pixels
|
||||||
|
pub width: usize,
|
||||||
|
|
||||||
|
/// Height in pixels
|
||||||
|
pub height: usize,
|
||||||
|
|
||||||
|
/// Pixel data in row-major form.
|
||||||
|
#[derivative(Debug = "ignore")]
|
||||||
|
pub data: Vec<Color>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let file = File::open(path)
|
||||||
|
.with_context(|| format!("Could not open file at {path:?}"))?;
|
||||||
|
Self::read(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse image from a Read
|
||||||
|
pub fn read(r: impl Read + Send) -> Result<Self> {
|
||||||
|
let mut line_reader = BufReader::new(r);
|
||||||
|
|
||||||
|
let mut header = String::new();
|
||||||
|
line_reader
|
||||||
|
.read_line(&mut header)
|
||||||
|
.context("Could not read line")?;
|
||||||
|
let parts = header.trim().split(" ").collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let width = parts[1].parse::<usize>().context("Could not read width")?;
|
||||||
|
let height = parts[2].parse::<usize>().context("Could not read height")?;
|
||||||
|
let max_value = parts[3]
|
||||||
|
.parse::<usize>()
|
||||||
|
.context("Could not read max value")?;
|
||||||
|
|
||||||
|
// Generator for reading numbers
|
||||||
|
let numbers = Gn::<()>::new_scoped(move |mut s| {
|
||||||
|
macro_rules! gen_try {
|
||||||
|
($expr:expr, $str:expr $(, $($arg:expr),* $(,)?)?) => {
|
||||||
|
match $expr {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
s.yield_(
|
||||||
|
Err(anyhow::Error::from(e))
|
||||||
|
.with_context(|| format!($str $(, $($arg,)*)?)),
|
||||||
|
);
|
||||||
|
done!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for line in line_reader.lines() {
|
||||||
|
let line = gen_try!(line, "Could not read line");
|
||||||
|
let parts = line.trim().split_whitespace();
|
||||||
|
|
||||||
|
for part in parts {
|
||||||
|
let int =
|
||||||
|
gen_try!(part.parse::<u64>(), "Could not read int from: {}", part);
|
||||||
|
s.yield_(Ok(int));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done!()
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut data = Vec::with_capacity(width * height);
|
||||||
|
for mut chunk in &(numbers).chunks(3) {
|
||||||
|
let (r, g, b) = match chunk.next_tuple() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => bail!("Not enough elements"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let r = r? as f64 / max_value as f64;
|
||||||
|
let g = g? as f64 / max_value as f64;
|
||||||
|
let b = b? as f64 / max_value as f64;
|
||||||
|
|
||||||
|
let color = Color::new(r, g, b);
|
||||||
|
data.push(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Image {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the image in PPM format to a file.
|
||||||
|
pub fn write(&self, mut w: impl Write) -> Result<()> {
|
||||||
|
// Header
|
||||||
|
let header = format!("P3 {} {} 255\n", self.width, self.height);
|
||||||
|
w.write_all(header.as_bytes())?;
|
||||||
|
|
||||||
|
// Pixel data
|
||||||
|
assert_eq!(self.data.len(), self.width * self.height);
|
||||||
|
|
||||||
|
for pixel in self.data.iter() {
|
||||||
|
let pixel = pixel * 256.0;
|
||||||
|
let red = pixel.x as u8;
|
||||||
|
let green = pixel.y as u8;
|
||||||
|
let blue = pixel.z as u8;
|
||||||
|
let pixel = format!("{red} {green} {blue}\n");
|
||||||
|
w.write_all(pixel.as_bytes())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
17
assignment-1d/src/lib.rs
Normal file
17
assignment-1d/src/lib.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use nalgebra::{Vector2, Vector3};
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate anyhow;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate derivative;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate tracing;
|
||||||
|
|
||||||
|
pub mod image;
|
||||||
|
pub mod ray;
|
||||||
|
pub mod scene;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
pub type Point2 = Vector2<f64>;
|
||||||
|
pub type Point = Vector3<f64>;
|
||||||
|
pub type Vector = Vector3<f64>;
|
137
assignment-1d/src/main.rs
Normal file
137
assignment-1d/src/main.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate tracing;
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use assignment_1d::{image::Image, ray::Ray, scene::Scene};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
|
||||||
|
|
||||||
|
/// Simple raytracer with Blinn-Phong illumination and shadowing.
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[clap(author, version, about, long_about = None)]
|
||||||
|
struct Opt {
|
||||||
|
/// Path to the input file to use.
|
||||||
|
#[clap()]
|
||||||
|
input_path: PathBuf,
|
||||||
|
|
||||||
|
/// Path to the output (defaults to the same file name as the input except
|
||||||
|
/// with an extension of .ppm)
|
||||||
|
#[clap(short = 'o', long = "output")]
|
||||||
|
output_path: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Force parallel projection to be used
|
||||||
|
#[clap(long = "parallel")]
|
||||||
|
force_parallel: bool,
|
||||||
|
|
||||||
|
/// Override distance from eye
|
||||||
|
#[clap(long = "distance", default_value = "1.0")]
|
||||||
|
distance: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let opt = Opt::parse();
|
||||||
|
|
||||||
|
// Set up logging
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_target(false)
|
||||||
|
.with_timer(tracing_subscriber::fmt::time::uptime())
|
||||||
|
.with_level(true)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Rename the output file if it's not provided
|
||||||
|
let out_file = opt
|
||||||
|
.output_path
|
||||||
|
.unwrap_or_else(|| opt.input_path.with_extension("ppm"));
|
||||||
|
|
||||||
|
let mut scene = Scene::from_input_file(&opt.input_path)?;
|
||||||
|
let distance = opt.distance;
|
||||||
|
|
||||||
|
// Force-override parallel projection
|
||||||
|
if opt.force_parallel {
|
||||||
|
scene.parallel_projection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate image pixels to real-world 3d coords
|
||||||
|
let translate_pixel = scene.pixel_translation_function(distance);
|
||||||
|
|
||||||
|
// Generate a parallel iterator for pixels
|
||||||
|
// The iterator preserves order and uses row-major order
|
||||||
|
let pixels_iter = (0..scene.image_height)
|
||||||
|
.into_par_iter()
|
||||||
|
.flat_map(|y| (0..scene.image_width).into_par_iter().map(move |x| (x, y)));
|
||||||
|
|
||||||
|
// Loop through every single pixel of the output file
|
||||||
|
let pixels = pixels_iter
|
||||||
|
.map(|(px, py)| {
|
||||||
|
let pixel_in_space = translate_pixel(px, py);
|
||||||
|
|
||||||
|
let ray_start = if scene.parallel_projection {
|
||||||
|
// For a parallel projection, we'll just take the view direction and
|
||||||
|
// subtract it from the target point. This means every single
|
||||||
|
// ray will be viewed from a point at infinity, rather than a single eye
|
||||||
|
// position.
|
||||||
|
let n = scene.view_dir.normalize();
|
||||||
|
let view_dir = n * distance;
|
||||||
|
pixel_in_space - view_dir
|
||||||
|
} else {
|
||||||
|
scene.eye_pos
|
||||||
|
};
|
||||||
|
|
||||||
|
let ray = Ray::from_endpoints(ray_start, pixel_in_space);
|
||||||
|
|
||||||
|
let intersections = scene
|
||||||
|
.objects
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, object)| {
|
||||||
|
match object.kind.intersects_ray_at(&scene, &ray) {
|
||||||
|
Ok(Some(t)) => {
|
||||||
|
// Return both the t and the sphere, because we want to sort on
|
||||||
|
// the t but later retrieve attributes from the sphere
|
||||||
|
Some(Ok((i, t, object)))
|
||||||
|
}
|
||||||
|
Ok(None) => None,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Error: {err}");
|
||||||
|
Some(Err(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
// Sort the list of intersection times by the lowest one.
|
||||||
|
let earliest_intersection =
|
||||||
|
intersections.into_iter().min_by_key(|(_, t, _)| t.time);
|
||||||
|
|
||||||
|
Ok(match earliest_intersection {
|
||||||
|
// Take the object's material color
|
||||||
|
Some((obj_idx, intersection_context, object)) => {
|
||||||
|
scene.compute_pixel_color(obj_idx, object, intersection_context)?
|
||||||
|
}
|
||||||
|
|
||||||
|
// There was no intersection, so this should default to the scene's
|
||||||
|
// background color
|
||||||
|
None => scene.bkg_color,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
// Construct and emit image
|
||||||
|
let image = Image {
|
||||||
|
width: scene.image_width,
|
||||||
|
height: scene.image_height,
|
||||||
|
data: pixels,
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let file = File::create(out_file)?;
|
||||||
|
image.write(file)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
27
assignment-1d/src/ray.rs
Normal file
27
assignment-1d/src/ray.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use crate::{Point, Vector};
|
||||||
|
|
||||||
|
/// A normalized parametric Ray of the form (origin + direction * time)
|
||||||
|
///
|
||||||
|
/// That means at any time t: f64, the point represented by origin + direction *
|
||||||
|
/// time occurs on the ray.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Ray {
|
||||||
|
pub origin: Point,
|
||||||
|
pub direction: Vector,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ray {
|
||||||
|
/// Construct a ray from endpoints
|
||||||
|
pub fn from_endpoints(start: Point, end: Point) -> Self {
|
||||||
|
let delta = (end - start).normalize();
|
||||||
|
Ray {
|
||||||
|
origin: start,
|
||||||
|
direction: delta,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluate the ray at a certain point in time, yielding a point
|
||||||
|
pub fn eval(&self, time: f64) -> Point {
|
||||||
|
self.origin + self.direction * time
|
||||||
|
}
|
||||||
|
}
|
206
assignment-1d/src/scene/cylinder.rs
Normal file
206
assignment-1d/src/scene/cylinder.rs
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use ordered_float::NotNan;
|
||||||
|
|
||||||
|
use crate::utils::compute_rotation_matrix;
|
||||||
|
use crate::Vector;
|
||||||
|
use crate::{ray::Ray, Point};
|
||||||
|
|
||||||
|
use super::illumination::IntersectionContext;
|
||||||
|
use super::Scene;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Cylinder {
|
||||||
|
pub center: Point,
|
||||||
|
pub direction: Vector,
|
||||||
|
pub radius: f64,
|
||||||
|
pub length: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cylinder {
|
||||||
|
/// Given a cylinder, returns the first time at which this ray intersects the
|
||||||
|
/// cylinder.
|
||||||
|
///
|
||||||
|
/// If there is no intersection point, returns None.
|
||||||
|
pub fn intersects_ray_at(
|
||||||
|
&self,
|
||||||
|
_: &Scene,
|
||||||
|
ray: &Ray,
|
||||||
|
) -> Result<Option<IntersectionContext>> {
|
||||||
|
// Determine rotation matrix for turning the cylinder upright along the
|
||||||
|
// Z-axis
|
||||||
|
let target_direction = Vector::new(0.0, 0.0, 1.0);
|
||||||
|
let rotation_matrix =
|
||||||
|
compute_rotation_matrix(self.direction, target_direction)?;
|
||||||
|
let inverse_rotation_matrix =
|
||||||
|
rotation_matrix.try_inverse().ok_or_else(|| {
|
||||||
|
anyhow!("Rotation matrix for some reason does not have an inverse?")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Transform all parameters according to this rotation matrix
|
||||||
|
let rotated_cylinder_center = rotation_matrix * self.center;
|
||||||
|
let rotated_ray_origin = rotation_matrix * ray.origin;
|
||||||
|
let rotated_ray_direction = rotation_matrix * ray.direction;
|
||||||
|
|
||||||
|
// Now that we know the cylinder is upright, we can start checking against
|
||||||
|
// the formula:
|
||||||
|
//
|
||||||
|
// (ox + t*rx - cx)^2 + (oy + t*ry - cy)^2 = r^2
|
||||||
|
//
|
||||||
|
// where o{xy} is the ray origin, r{xy} is the ray direction, and c{xy} is
|
||||||
|
// the cylinder center. The z will be taken care of after the fact. To
|
||||||
|
// solve, we must put it into the form At^2 + Bt + c = 0. The variables
|
||||||
|
// are:
|
||||||
|
//
|
||||||
|
// A: rx^2 + ry^2
|
||||||
|
// B: 2(rx(ox - cx) + ry(oy - cy))
|
||||||
|
// C: (cx - ox)^2 + (cy - oy)^2 - r^2
|
||||||
|
let (a, b, c) = {
|
||||||
|
let o = rotated_ray_origin;
|
||||||
|
let r = rotated_ray_direction;
|
||||||
|
let c = rotated_cylinder_center;
|
||||||
|
|
||||||
|
(
|
||||||
|
r.x.powi(2) + r.y.powi(2),
|
||||||
|
2.0 * (r.x * (o.x - c.x) + r.y * (o.y - c.y)),
|
||||||
|
(c.x - o.x).powi(2) + (c.y - o.y).powi(2) - self.radius.powi(2),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let discriminant = b * b - 4.0 * a * c;
|
||||||
|
|
||||||
|
let possible_side_solutions = match discriminant {
|
||||||
|
// Discriminant < 0, means the equation has no solutions.
|
||||||
|
d if d < 0.0 => vec![],
|
||||||
|
|
||||||
|
// Discriminant == 0
|
||||||
|
d if d == 0.0 => vec![-b / 2.0 * a],
|
||||||
|
|
||||||
|
// Discriminant > 0, 2 solutions available.
|
||||||
|
d if d > 0.0 => {
|
||||||
|
vec![
|
||||||
|
(-b + discriminant.sqrt()) / (2.0 * a),
|
||||||
|
(-b - discriminant.sqrt()) / (2.0 * a),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probably hit some NaN or Infinity value due to faulty inputs...
|
||||||
|
_ => bail!("Invalid determinant value: {discriminant}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter out solutions that don't have a valid Z position.
|
||||||
|
let side_solutions = possible_side_solutions.into_iter().filter_map(|t| {
|
||||||
|
let ray_point = ray.eval(t);
|
||||||
|
let rotated_ray_point = rotation_matrix * ray_point;
|
||||||
|
let z = rotated_ray_point.z - rotated_cylinder_center.z;
|
||||||
|
|
||||||
|
// Check to see if z is between -len/2 and len/2
|
||||||
|
if z.abs() > self.length / 2.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let time = NotNan::new(t).ok()?;
|
||||||
|
|
||||||
|
// The point on the center of the cylinder that corresponds to the z-axis
|
||||||
|
// point of the intersection
|
||||||
|
let center_at_z = {
|
||||||
|
let mut center_point = rotation_matrix * ray_point;
|
||||||
|
center_point.x = rotated_cylinder_center.x;
|
||||||
|
center_point.y = rotated_cylinder_center.y;
|
||||||
|
|
||||||
|
inverse_rotation_matrix * center_point
|
||||||
|
};
|
||||||
|
let normal = (ray_point - center_at_z).normalize();
|
||||||
|
|
||||||
|
Some(IntersectionContext {
|
||||||
|
time,
|
||||||
|
point: ray_point,
|
||||||
|
normal,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// We also need to add solutions for the two ends of the cylinder, which
|
||||||
|
// uses a similar method except backwards: check intersection points
|
||||||
|
// with the correct z-plane and then see if the points are within the
|
||||||
|
// circle.
|
||||||
|
//
|
||||||
|
// Luckily, this means we only need to care about one dimension at first,
|
||||||
|
// and don't need to perform the quadratic equation method above.
|
||||||
|
//
|
||||||
|
// oz + t * rz = cz +- (len / 2)
|
||||||
|
// t = (-oz + cz +- (len / 2)) / rz
|
||||||
|
let possible_z_intersections = {
|
||||||
|
let o = rotated_ray_origin;
|
||||||
|
let r = rotated_ray_direction;
|
||||||
|
let c = rotated_cylinder_center;
|
||||||
|
|
||||||
|
if r.z == 0.0 {
|
||||||
|
Vec::new() // No solutions here
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
(-o.z + c.z + self.length / 2.0) / r.z,
|
||||||
|
(-o.z + c.z - self.length / 2.0) / r.z,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let end_solutions = possible_z_intersections.into_iter().filter_map(|t| {
|
||||||
|
let ray_point = ray.eval(t);
|
||||||
|
let rotated_point = rotation_matrix * ray_point;
|
||||||
|
|
||||||
|
// Filter out all the solutions where the intersection point does not lie
|
||||||
|
// in the circle
|
||||||
|
if rotated_point.x.powi(2) + rotated_point.y.powi(2) > self.radius.powi(2)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let normal_rotated =
|
||||||
|
Vector::new(0.0, 0.0, rotated_point.z - rotated_cylinder_center.z)
|
||||||
|
.normalize();
|
||||||
|
let normal = inverse_rotation_matrix * normal_rotated;
|
||||||
|
|
||||||
|
let time = NotNan::new(t).ok()?;
|
||||||
|
Some(IntersectionContext {
|
||||||
|
time,
|
||||||
|
point: ray_point,
|
||||||
|
normal,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let solutions = side_solutions
|
||||||
|
.into_iter()
|
||||||
|
.chain(end_solutions.into_iter())
|
||||||
|
// Remove any t < 0, since that means it's behind the viewer and we
|
||||||
|
// can't see it.
|
||||||
|
.filter(|ctx| *ctx.time >= 0.0);
|
||||||
|
|
||||||
|
// Return the minimum solution
|
||||||
|
Ok(solutions.min_by_key(|ctx| ctx.time))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{ray::Ray, scene::Scene, Point, Vector};
|
||||||
|
|
||||||
|
use super::Cylinder;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cylinder() {
|
||||||
|
let cylinder = Cylinder {
|
||||||
|
center: Point::new(0.0, 0.0, 0.0),
|
||||||
|
direction: Vector::new(0.0, 1.0, 0.0),
|
||||||
|
radius: 3.0,
|
||||||
|
length: 4.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let eye = Point::new(0.0, 3.0, 3.0);
|
||||||
|
let end = Point::new(0.0, 2.0, 2.0);
|
||||||
|
let ray = Ray::from_endpoints(eye, end);
|
||||||
|
|
||||||
|
let scene = Scene::default();
|
||||||
|
let _res = cylinder.intersects_ray_at(&scene, &ray);
|
||||||
|
// panic!("Result: {res:?}");
|
||||||
|
}
|
||||||
|
}
|
187
assignment-1d/src/scene/data.rs
Normal file
187
assignment-1d/src/scene/data.rs
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use crate::image::Color;
|
||||||
|
use crate::utils::cross;
|
||||||
|
use crate::Point;
|
||||||
|
|
||||||
|
use super::Scene;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Rect {
|
||||||
|
pub upper_left: Point,
|
||||||
|
pub upper_right: Point,
|
||||||
|
pub lower_left: Point,
|
||||||
|
pub lower_right: Point,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Material {
|
||||||
|
pub diffuse_color: Point,
|
||||||
|
pub specular_color: Point,
|
||||||
|
|
||||||
|
pub k_a: f64,
|
||||||
|
pub k_d: f64,
|
||||||
|
pub k_s: f64,
|
||||||
|
pub exponent: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum LightKind {
|
||||||
|
/// A point light source exists at a point and emits light in all directions
|
||||||
|
Point {
|
||||||
|
location: Point,
|
||||||
|
|
||||||
|
/// Whether light attenuation is enabled for this light
|
||||||
|
attenuation: Option<Attenuation>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A directional light source exists at an infinitely far location but emits
|
||||||
|
/// light in a specific direction
|
||||||
|
Directional { direction: Point },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Light {
|
||||||
|
/// The kind of light source, as well as its associated information
|
||||||
|
pub kind: LightKind,
|
||||||
|
|
||||||
|
/// The color, or intensity, of the light source
|
||||||
|
pub color: Point,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Light {
|
||||||
|
/// Get the unit directional vector pointing from the given point to this
|
||||||
|
/// light source
|
||||||
|
pub fn direction_from(&self, point: Point) -> Point {
|
||||||
|
match self.kind {
|
||||||
|
LightKind::Point { location, .. } => location - point,
|
||||||
|
LightKind::Directional { direction } => -direction,
|
||||||
|
}
|
||||||
|
.normalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DepthCueing {
|
||||||
|
/// The color to tint (should be the same as the background color, to avoid
|
||||||
|
/// bizarre visual effects)
|
||||||
|
pub color: Color,
|
||||||
|
|
||||||
|
/// Proportion of the color influenced by the depth tint when the distance is
|
||||||
|
/// maxed (caps at 1.0)
|
||||||
|
pub a_max: f64,
|
||||||
|
|
||||||
|
/// Proportion of the color influenced by the depth tint when the distance is
|
||||||
|
/// at the minimum (caps at 1.0)
|
||||||
|
pub a_min: f64,
|
||||||
|
|
||||||
|
/// The max distance that should be affected by the depth tint
|
||||||
|
pub dist_max: f64,
|
||||||
|
|
||||||
|
/// The min distance that should be affected by the depth tint
|
||||||
|
pub dist_min: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A default implementation here needs to simulate what would happen if there
|
||||||
|
/// was no depth cueing. In this case, if we have both a_max and a_min be 1.0,
|
||||||
|
/// then the original color will always apply and there will be no need for
|
||||||
|
/// depth color
|
||||||
|
impl Default for DepthCueing {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
color: Default::default(),
|
||||||
|
a_max: 1.0,
|
||||||
|
a_min: 1.0,
|
||||||
|
dist_max: 0.0,
|
||||||
|
dist_min: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Light attenuation dropoff coefficients
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Attenuation {
|
||||||
|
pub c1: f64,
|
||||||
|
pub c2: f64,
|
||||||
|
pub c3: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A default implementation here needs to simulate what would happen if there
|
||||||
|
/// was no light attenuation specified. In this case, c1 would just be a
|
||||||
|
/// constant of 1 and all the coefficients for anything involving distance would
|
||||||
|
/// be zeroed out
|
||||||
|
impl Default for Attenuation {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
c1: 1.0,
|
||||||
|
c2: 0.0,
|
||||||
|
c3: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
/// Determine the boundaries of the viewing window in world coordinates
|
||||||
|
pub fn compute_viewing_window(&self, distance: f64) -> Rect {
|
||||||
|
// Compute viewing directions
|
||||||
|
let u = cross(self.view_dir, self.up_dir).normalize();
|
||||||
|
let v = cross(u, self.view_dir).normalize();
|
||||||
|
|
||||||
|
// Compute dimensions of viewing window based on field of view
|
||||||
|
let viewing_width = {
|
||||||
|
// Divide the angle in 2 since we are trying to use trig rules so we must
|
||||||
|
// get it from a right triangle
|
||||||
|
let half_hfov = self.hfov.to_radians() / 2.0;
|
||||||
|
|
||||||
|
// tan(hfov / 2) = w / 2d
|
||||||
|
let w_over_2d = half_hfov.tan();
|
||||||
|
|
||||||
|
// To find the viewing width we must multiply by 2d now
|
||||||
|
w_over_2d * 2.0 * distance
|
||||||
|
};
|
||||||
|
|
||||||
|
let aspect_ratio = self.image_width as f64 / self.image_height as f64;
|
||||||
|
let viewing_height = viewing_width / aspect_ratio;
|
||||||
|
|
||||||
|
// Compute viewing window corners
|
||||||
|
let n = self.view_dir.normalize();
|
||||||
|
|
||||||
|
#[rustfmt::skip] // Don't format, or else this line wraps over
|
||||||
|
let view_window = Rect {
|
||||||
|
upper_left: self.eye_pos + n * distance - u * (viewing_width / 2.0) + v * (viewing_height / 2.0),
|
||||||
|
upper_right: self.eye_pos + n * distance + u * (viewing_width / 2.0) + v * (viewing_height / 2.0),
|
||||||
|
lower_left: self.eye_pos + n * distance - u * (viewing_width / 2.0) - v * (viewing_height / 2.0),
|
||||||
|
lower_right: self.eye_pos + n * distance + u * (viewing_width / 2.0) - v * (viewing_height / 2.0),
|
||||||
|
};
|
||||||
|
|
||||||
|
view_window
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a pixel translation function based on the viewing window of the
|
||||||
|
/// current scene
|
||||||
|
pub fn pixel_translation_function(
|
||||||
|
&self,
|
||||||
|
distance: f64,
|
||||||
|
) -> impl Fn(usize, usize) -> Point {
|
||||||
|
let view_window = self.compute_viewing_window(distance);
|
||||||
|
|
||||||
|
let dx = view_window.upper_right - view_window.upper_left;
|
||||||
|
let pixel_base_x = dx / self.image_width as f64;
|
||||||
|
|
||||||
|
let dy = view_window.lower_left - view_window.upper_left;
|
||||||
|
let pixel_base_y = dy / self.image_height as f64;
|
||||||
|
|
||||||
|
// The final function to be returned
|
||||||
|
move |px: usize, py: usize| {
|
||||||
|
let x_component = pixel_base_x * px as f64;
|
||||||
|
let y_component = pixel_base_y * py as f64;
|
||||||
|
|
||||||
|
// Without adding this, we would be getting the top-left of the pixel's
|
||||||
|
// rectangle. We want the center, so add half of the pixel size as
|
||||||
|
// well.
|
||||||
|
let center_offset = (pixel_base_x + pixel_base_y) / 2.0;
|
||||||
|
|
||||||
|
view_window.upper_left + x_component + y_component + center_offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
294
assignment-1d/src/scene/illumination.rs
Normal file
294
assignment-1d/src/scene/illumination.rs
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use ordered_float::NotNan;
|
||||||
|
use rand::Rng;
|
||||||
|
use rayon::prelude::{
|
||||||
|
IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator,
|
||||||
|
ParallelIterator,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{image::Color, ray::Ray, utils::dot, Point, Vector};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
data::{DepthCueing, Light, LightKind},
|
||||||
|
object::Object,
|
||||||
|
Scene,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
/// Determine the color that should be used to fill this pixel.
|
||||||
|
///
|
||||||
|
/// - material_idx is the index into the materials list.
|
||||||
|
/// - intersection_context contains information on vectors where the
|
||||||
|
/// intersection occurred
|
||||||
|
///
|
||||||
|
/// Also known as Shade_Ray in the slides.
|
||||||
|
pub fn compute_pixel_color(
|
||||||
|
&self,
|
||||||
|
obj_idx: usize,
|
||||||
|
object: &Object,
|
||||||
|
intersection_context: IntersectionContext,
|
||||||
|
) -> Result<Color> {
|
||||||
|
let material = match self.materials.get(object.material_idx) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => bail!("Material index not found."),
|
||||||
|
};
|
||||||
|
|
||||||
|
let diffuse_color = match object.texture_idx {
|
||||||
|
Some(texture_idx) => {
|
||||||
|
let (u, v) = object
|
||||||
|
.kind
|
||||||
|
.get_texture_coord(&self, &intersection_context)?;
|
||||||
|
|
||||||
|
let texture = match self.textures.get(texture_idx) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => bail!("Texture index not found."),
|
||||||
|
};
|
||||||
|
|
||||||
|
texture.pixel_at(u, v)
|
||||||
|
}
|
||||||
|
None => material.diffuse_color,
|
||||||
|
};
|
||||||
|
|
||||||
|
let ambient_component = material.k_a * diffuse_color;
|
||||||
|
|
||||||
|
// Diffuse and specular lighting for each separate light
|
||||||
|
let diffuse_and_specular: Color = self
|
||||||
|
.lights
|
||||||
|
.par_iter()
|
||||||
|
.map(|light| {
|
||||||
|
// The vector pointing in the direction of the light
|
||||||
|
let light_direction = light.direction_from(intersection_context.point);
|
||||||
|
|
||||||
|
let normal = intersection_context.normal.normalize();
|
||||||
|
let viewer_direction = self.eye_pos - intersection_context.point;
|
||||||
|
let halfway_direction =
|
||||||
|
((light_direction + viewer_direction) / 2.0).normalize();
|
||||||
|
|
||||||
|
let diffuse_component = material.k_d
|
||||||
|
* diffuse_color
|
||||||
|
* dot(normal, light_direction).max(0.0);
|
||||||
|
|
||||||
|
let specular_component = material.k_s
|
||||||
|
* material.specular_color
|
||||||
|
* dot(normal, halfway_direction)
|
||||||
|
.max(0.0)
|
||||||
|
.powf(material.exponent);
|
||||||
|
|
||||||
|
// Shadow coefficient between 0 and 1 to control how bright this pixel
|
||||||
|
// should be from being in the shadow of another object (could be
|
||||||
|
// between 0 and 1 when applying soft shadows)
|
||||||
|
let shadow_coefficient = self.compute_shadow_coefficient(
|
||||||
|
obj_idx,
|
||||||
|
intersection_context.point,
|
||||||
|
light,
|
||||||
|
);
|
||||||
|
|
||||||
|
let attenuation_coefficient = match &light.kind {
|
||||||
|
LightKind::Point {
|
||||||
|
location,
|
||||||
|
attenuation: Some(att),
|
||||||
|
} => {
|
||||||
|
let dist = (location - intersection_context.point).norm();
|
||||||
|
let denom = att.c1 + att.c2 * dist + att.c3 * dist.powi(2);
|
||||||
|
if denom == 0.0 {
|
||||||
|
warn!("Light attenuation coefficients produced a denominator of 0. Check your inputs...");
|
||||||
|
1.0 // Some kind of graceful fallback here
|
||||||
|
} else {
|
||||||
|
1.0 / denom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let diffuse_and_specular = diffuse_component + specular_component;
|
||||||
|
|
||||||
|
attenuation_coefficient
|
||||||
|
* shadow_coefficient
|
||||||
|
* light.color.component_mul(&diffuse_and_specular)
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
let color = ambient_component + diffuse_and_specular;
|
||||||
|
|
||||||
|
// Apply depth cueing to the result
|
||||||
|
let a_dc = {
|
||||||
|
// Distance from the viewer
|
||||||
|
let d_obj = (intersection_context.point - self.eye_pos).norm();
|
||||||
|
let DepthCueing {
|
||||||
|
dist_max,
|
||||||
|
dist_min,
|
||||||
|
a_max,
|
||||||
|
a_min,
|
||||||
|
..
|
||||||
|
} = self.depth_cueing;
|
||||||
|
|
||||||
|
if d_obj < dist_min {
|
||||||
|
a_max
|
||||||
|
} else if d_obj < dist_max {
|
||||||
|
a_min + (a_max - a_min) * (dist_max - d_obj) / (dist_max - dist_min)
|
||||||
|
} else {
|
||||||
|
a_min
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let color = a_dc * color + (1.0 - a_dc) * self.depth_cueing.color;
|
||||||
|
|
||||||
|
// Need to clamp the result so none of the components goes over 1
|
||||||
|
let clamped_result = color.map(|v| v.min(1.0));
|
||||||
|
|
||||||
|
Ok(clamped_result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform another ray casting to see if there are any objects obstructing
|
||||||
|
/// the light source to this particular point
|
||||||
|
pub fn compute_shadow_coefficient(
|
||||||
|
&self,
|
||||||
|
obj_idx: usize,
|
||||||
|
point: Point,
|
||||||
|
light: &Light,
|
||||||
|
) -> f64 {
|
||||||
|
let light_direction = light.direction_from(point);
|
||||||
|
let ray = Ray {
|
||||||
|
origin: point,
|
||||||
|
direction: light_direction.normalize(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Small helper for iterating over all of the objects in the scene except
|
||||||
|
// for the current one
|
||||||
|
let other_objects = self
|
||||||
|
.objects
|
||||||
|
.par_iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(i, _)| *i != obj_idx);
|
||||||
|
|
||||||
|
// Get the list of intersections with all the other objects in the scene
|
||||||
|
// This list will be a set of opacities
|
||||||
|
let intersections = other_objects
|
||||||
|
.filter_map(|(_, object)| {
|
||||||
|
let intersection_context =
|
||||||
|
match object.kind.intersects_ray_at(&self, &ray) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Error while performing shadow casting: {err}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
let intersection_time = *intersection_context.time;
|
||||||
|
|
||||||
|
match light.kind {
|
||||||
|
// In the case of point lights, we must check to see if both t > 0 and
|
||||||
|
// t is less than the time it took to even get to the light.
|
||||||
|
LightKind::Point { location, .. } => {
|
||||||
|
let light_time = (location - ray.origin).norm();
|
||||||
|
|
||||||
|
if intersection_time <= 0.0 || intersection_time >= light_time {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let soft_shadow_coefficient =
|
||||||
|
self.compute_soft_shadow_coefficient(location, point, object);
|
||||||
|
Some(soft_shadow_coefficient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the case of directional lights, only t > 0 needs to be checked,
|
||||||
|
// otherwise
|
||||||
|
LightKind::Directional { .. } => {
|
||||||
|
if intersection_time <= 0.0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(0.0) // complete obstruction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
match intersections.is_empty() {
|
||||||
|
true => 1.0,
|
||||||
|
false => {
|
||||||
|
let average = intersections.iter().cloned().sum::<f64>()
|
||||||
|
/ intersections.len() as f64;
|
||||||
|
average
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_soft_shadow_coefficient(
|
||||||
|
&self,
|
||||||
|
light_location: Point,
|
||||||
|
original_intersection_point: Point,
|
||||||
|
object: &Object,
|
||||||
|
) -> f64 {
|
||||||
|
// Soft shadows: jitter some rays here to somewhere close to the
|
||||||
|
// actual location as well, and measure the proportion
|
||||||
|
// of them that intersect any objects
|
||||||
|
const JITTER_RADIUS: f64 = 1.0;
|
||||||
|
const JITTER_RAYS: usize = 75;
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let locations = iter::repeat_with(|| {
|
||||||
|
let x = rng.gen_range(0.0..JITTER_RADIUS);
|
||||||
|
let y = rng.gen_range(0.0..JITTER_RADIUS);
|
||||||
|
let z = rng.gen_range(0.0..JITTER_RADIUS);
|
||||||
|
let delta = Vector::new(x, y, z);
|
||||||
|
light_location + delta
|
||||||
|
})
|
||||||
|
.take(JITTER_RAYS)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let num_obstructed_rays = locations
|
||||||
|
.into_par_iter()
|
||||||
|
.filter(|location| {
|
||||||
|
let direction = (location - original_intersection_point).normalize();
|
||||||
|
let ray = Ray {
|
||||||
|
origin: original_intersection_point,
|
||||||
|
direction,
|
||||||
|
};
|
||||||
|
|
||||||
|
let intersection_context =
|
||||||
|
match object.kind.intersects_ray_at(&self, &ray) {
|
||||||
|
Ok(Some(v)) => v,
|
||||||
|
Ok(None) => return false,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Error while performing shadow casting: {err}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let light_time = (location - ray.origin).norm();
|
||||||
|
let intersection_time = *intersection_context.time;
|
||||||
|
|
||||||
|
0.0 < intersection_time && intersection_time < light_time
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
|
||||||
|
(JITTER_RAYS - num_obstructed_rays) as f64 / JITTER_RAYS as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about an intersection
|
||||||
|
#[derive(Derivative)]
|
||||||
|
#[derivative(Debug, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct IntersectionContext {
|
||||||
|
/// The time of the intersection in the parametric ray
|
||||||
|
///
|
||||||
|
/// Unfortunately, IEEE floats in Rust don't have total ordering, because
|
||||||
|
/// NaNs violate ordering properties. The way to remedy this is to ensure we
|
||||||
|
/// don't have NaNs by wrapping it into this type, which then implements
|
||||||
|
/// total ordering.
|
||||||
|
pub time: NotNan<f64>,
|
||||||
|
|
||||||
|
/// The intersection point.
|
||||||
|
#[derivative(PartialEq = "ignore", Ord = "ignore")]
|
||||||
|
pub point: Point,
|
||||||
|
|
||||||
|
/// The normal vector protruding from the surface of the object at the
|
||||||
|
/// intersection point
|
||||||
|
#[derivative(PartialEq = "ignore", Ord = "ignore")]
|
||||||
|
pub normal: Vector,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for IntersectionContext {}
|
||||||
|
|
||||||
|
impl IntersectionContext {}
|
398
assignment-1d/src/scene/input_file.rs
Normal file
398
assignment-1d/src/scene/input_file.rs
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use nalgebra::Vector3;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
image::{Color, Image},
|
||||||
|
scene::{
|
||||||
|
cylinder::Cylinder,
|
||||||
|
data::{Attenuation, Light, LightKind, Material},
|
||||||
|
object::{Object, ObjectKind},
|
||||||
|
sphere::Sphere,
|
||||||
|
texture::{Texture, NormalMap},
|
||||||
|
triangle::Triangle,
|
||||||
|
Scene,
|
||||||
|
},
|
||||||
|
Point, Vector,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::data::DepthCueing;
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
/// Parse the input file into a scene
|
||||||
|
pub fn from_input_file(path: impl AsRef<Path>) -> Result<Self> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
// Scope the read so the file is dropped and closed immediately after the
|
||||||
|
// contents have been read to memory
|
||||||
|
let contents = {
|
||||||
|
let mut contents = String::new();
|
||||||
|
let mut file = File::open(path)?;
|
||||||
|
file.read_to_string(&mut contents)?;
|
||||||
|
contents
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut scene = Scene::default();
|
||||||
|
let mut material_idx = None;
|
||||||
|
let mut texture_idx = None;
|
||||||
|
|
||||||
|
for line in contents.lines() {
|
||||||
|
// Split lines into words. `parts' is an iterator, which is consumed upon
|
||||||
|
// iterating, rather than collected into a Vec
|
||||||
|
let mut parts = line.split_whitespace();
|
||||||
|
|
||||||
|
// The keyword is the very first space-separated substring, and tells us
|
||||||
|
// how to interpret the rest
|
||||||
|
let keyword = match parts.next() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Short for "read", macro for reading something from the iterator and
|
||||||
|
/// converting it into the appropriate format given by $ty. For this to
|
||||||
|
/// work, $ty must implement Construct
|
||||||
|
macro_rules! r {
|
||||||
|
($ty:ty) => {
|
||||||
|
<$ty>::construct(&mut parts, ())
|
||||||
|
.with_context(|| format!("Could not parse {}", stringify!($ty)))?
|
||||||
|
};
|
||||||
|
|
||||||
|
($ty:ty, $($ex:expr),* $(,)?) => {
|
||||||
|
<$ty>::construct(&mut parts, $($ex,)*)
|
||||||
|
.with_context(|| format!("Could not parse {}", stringify!($ty)))?
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shortcut for unwrapping one of the state `Option's
|
||||||
|
macro_rules! u {
|
||||||
|
($expr:expr) => {
|
||||||
|
match $expr {
|
||||||
|
Some(v) => v,
|
||||||
|
None => {
|
||||||
|
bail!(
|
||||||
|
"Each object must be preceded by a `{}` line",
|
||||||
|
stringify!($expr)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match keyword {
|
||||||
|
"imsize" => {
|
||||||
|
scene.image_width = r!(usize);
|
||||||
|
scene.image_height = r!(usize);
|
||||||
|
}
|
||||||
|
"projection" => {
|
||||||
|
if let Some("parallel") = parts.next() {
|
||||||
|
scene.parallel_projection = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"eye" => scene.eye_pos = r!(Vector3<f64>),
|
||||||
|
"viewdir" => scene.view_dir = r!(Vector3<f64>),
|
||||||
|
"updir" => scene.up_dir = r!(Vector3<f64>),
|
||||||
|
|
||||||
|
"hfov" => scene.hfov = r!(f64),
|
||||||
|
"bkgcolor" => scene.bkg_color = r!(Color),
|
||||||
|
|
||||||
|
// light x y z w r g b
|
||||||
|
// attlight x y z w r g b c1 c2 c3
|
||||||
|
"light" | "attlight" => {
|
||||||
|
let vec3 = r!(Vector3<f64>);
|
||||||
|
let w = r!(usize);
|
||||||
|
let color = r!(Color);
|
||||||
|
|
||||||
|
let attenuation = match keyword == "attlight" {
|
||||||
|
true => {
|
||||||
|
let c = r!(Vector3<f64>);
|
||||||
|
Some(Attenuation {
|
||||||
|
c1: c.x,
|
||||||
|
c2: c.y,
|
||||||
|
c3: c.z,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
false => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let kind = match w as usize {
|
||||||
|
0 => LightKind::Directional { direction: vec3 },
|
||||||
|
1 => LightKind::Point {
|
||||||
|
location: vec3,
|
||||||
|
attenuation,
|
||||||
|
},
|
||||||
|
_ => bail!("Invalid w; must be either 0 or 1"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let light = Light { kind, color };
|
||||||
|
scene.lights.push(light);
|
||||||
|
}
|
||||||
|
|
||||||
|
// depthcueing dcr dcg dcb amax amin distmax distmin
|
||||||
|
"depthcueing" => {
|
||||||
|
let color = r!(Color);
|
||||||
|
let a_max = r!(f64);
|
||||||
|
let a_min = r!(f64);
|
||||||
|
let dist_max = r!(f64);
|
||||||
|
let dist_min = r!(f64);
|
||||||
|
|
||||||
|
scene.depth_cueing = DepthCueing {
|
||||||
|
color,
|
||||||
|
a_max,
|
||||||
|
a_min,
|
||||||
|
dist_max,
|
||||||
|
dist_min,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// mtlcolor Odr Odg Odb Osr Osg Osb ka kd ks n
|
||||||
|
"mtlcolor" => {
|
||||||
|
let diffuse_color = r!(Color);
|
||||||
|
let specular_color = r!(Color);
|
||||||
|
let k_a = r!(f64);
|
||||||
|
let k_d = r!(f64);
|
||||||
|
let k_s = r!(f64);
|
||||||
|
let exponent = r!(f64);
|
||||||
|
|
||||||
|
let material = Material {
|
||||||
|
diffuse_color,
|
||||||
|
specular_color,
|
||||||
|
k_a,
|
||||||
|
k_d,
|
||||||
|
k_s,
|
||||||
|
exponent,
|
||||||
|
};
|
||||||
|
|
||||||
|
let idx = scene.materials.len();
|
||||||
|
material_idx = Some(idx);
|
||||||
|
scene.materials.push(material);
|
||||||
|
}
|
||||||
|
|
||||||
|
"sphere" => {
|
||||||
|
let center = r!(Point);
|
||||||
|
let radius = r!(f64);
|
||||||
|
|
||||||
|
scene.objects.push(Object {
|
||||||
|
kind: ObjectKind::Sphere(Sphere { center, radius }),
|
||||||
|
material_idx: u!(material_idx),
|
||||||
|
texture_idx,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
"cylinder" => {
|
||||||
|
let center = r!(Point);
|
||||||
|
let direction = r!(Vector);
|
||||||
|
let radius = r!(f64);
|
||||||
|
let length = r!(f64);
|
||||||
|
|
||||||
|
scene.objects.push(Object {
|
||||||
|
kind: ObjectKind::Cylinder(Cylinder {
|
||||||
|
center,
|
||||||
|
direction,
|
||||||
|
radius,
|
||||||
|
length,
|
||||||
|
}),
|
||||||
|
material_idx: u!(material_idx),
|
||||||
|
texture_idx,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assignment 1C: Triangles and textures
|
||||||
|
|
||||||
|
// v x y z
|
||||||
|
"v" => scene.triangle_vertices.push(r!(Vector)),
|
||||||
|
|
||||||
|
// vn nx ny nz
|
||||||
|
"vn" => scene.vertex_normals.push(r!(Vector)),
|
||||||
|
|
||||||
|
// f v1 v2 v3
|
||||||
|
// f v1//n1 v2//n2 v3//n3
|
||||||
|
"f" => {
|
||||||
|
let v1 = r!(TriangleVertex);
|
||||||
|
let v2 = r!(TriangleVertex);
|
||||||
|
let v3 = r!(TriangleVertex);
|
||||||
|
let vs = Vector3::new(v1, v2, v3);
|
||||||
|
|
||||||
|
let vertices = vs.map(|v| v.vertex_idx);
|
||||||
|
|
||||||
|
let normals = vs.map(|v| v.normal_idx);
|
||||||
|
let normals = match normals.iter().filter(|o| o.is_some()).count() {
|
||||||
|
0 => None,
|
||||||
|
n if n == vs.len() => Some(normals.map(|o| o.unwrap())),
|
||||||
|
_ => bail!("Cannot mix and match having a normal index"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let textures = vs.map(|v| v.texture_idx);
|
||||||
|
let textures = match textures.iter().filter(|o| o.is_some()).count() {
|
||||||
|
0 => None,
|
||||||
|
n if n == vs.len() => Some(textures.map(|o| o.unwrap())),
|
||||||
|
_ => bail!("Cannot mix and match having a normal index"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let triangle = Triangle {
|
||||||
|
vertices,
|
||||||
|
normals,
|
||||||
|
textures,
|
||||||
|
};
|
||||||
|
scene.objects.push(Object {
|
||||||
|
kind: ObjectKind::Triangle(triangle),
|
||||||
|
material_idx: u!(material_idx),
|
||||||
|
texture_idx,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
"texture" => {
|
||||||
|
let input_parent = path.parent().unwrap().to_path_buf();
|
||||||
|
let path = match parts.next() {
|
||||||
|
Some(s) => input_parent.join(s),
|
||||||
|
None => bail!("Did not provide path."),
|
||||||
|
};
|
||||||
|
|
||||||
|
let idx = scene.textures.len();
|
||||||
|
texture_idx = Some(idx);
|
||||||
|
|
||||||
|
let image = Image::from_file(path)?;
|
||||||
|
let texture = Texture::new(image);
|
||||||
|
scene.textures.push(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
"bump" => {
|
||||||
|
let input_parent = path.parent().unwrap().to_path_buf();
|
||||||
|
let path = match parts.next() {
|
||||||
|
Some(s) => input_parent.join(s),
|
||||||
|
None => bail!("Did not provide path."),
|
||||||
|
};
|
||||||
|
|
||||||
|
let idx = scene.textures.len();
|
||||||
|
texture_idx = Some(idx);
|
||||||
|
|
||||||
|
let image = Image::from_file(path)?;
|
||||||
|
let normal_map = NormalMap::new(image);
|
||||||
|
scene.normal_maps.push(normal_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => bail!("Unknown keyword {keyword}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(scene)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Construct: Sized {
|
||||||
|
type Args;
|
||||||
|
|
||||||
|
/// Construct an element of this type from an iterator over strings.
|
||||||
|
fn construct<'a, I>(it: &mut I, args: Self::Args) -> Result<Self>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a str>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Construct> Construct for Option<T> {
|
||||||
|
type Args = T::Args;
|
||||||
|
|
||||||
|
fn construct<'a, I>(it: &mut I, args: Self::Args) -> Result<Self>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a str>,
|
||||||
|
{
|
||||||
|
let mut peeker = it.peekable();
|
||||||
|
|
||||||
|
if peeker.peek().is_none() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
T::construct(&mut peeker, args).map(Some)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_construct {
|
||||||
|
($ty:ty) => {
|
||||||
|
impl Construct for $ty {
|
||||||
|
type Args = ();
|
||||||
|
|
||||||
|
fn construct<'a, I>(it: &mut I, _: Self::Args) -> Result<Self>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a str>,
|
||||||
|
{
|
||||||
|
let item = match it.next() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => bail!("Ran out of items"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(item.parse()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_construct!(f64);
|
||||||
|
impl_construct!(usize);
|
||||||
|
|
||||||
|
impl Construct for Vector3<f64> {
|
||||||
|
type Args = ();
|
||||||
|
|
||||||
|
fn construct<'a, I>(it: &mut I, _: Self::Args) -> Result<Self>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a str>,
|
||||||
|
{
|
||||||
|
let (x, y, z) = match it.next_tuple() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => bail!("Expected 3 values"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let x: f64 = x.parse()?;
|
||||||
|
let y: f64 = y.parse()?;
|
||||||
|
let z: f64 = z.parse()?;
|
||||||
|
|
||||||
|
Ok(Vector3::new(x, y, z))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
struct TriangleVertex {
|
||||||
|
vertex_idx: usize,
|
||||||
|
normal_idx: Option<usize>,
|
||||||
|
texture_idx: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Construct for TriangleVertex {
|
||||||
|
type Args = ();
|
||||||
|
|
||||||
|
fn construct<'a, I>(it: &mut I, _: Self::Args) -> Result<Self>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a str>,
|
||||||
|
{
|
||||||
|
let s = match it.next() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => bail!("Waiting on another"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note: indexed by 1 not 0, so we will just do the subtraction
|
||||||
|
// here to avoid having to deal with it later
|
||||||
|
let parts = s.split("/").collect_vec();
|
||||||
|
ensure!(parts.len() >= 1 && parts.len() <= 3);
|
||||||
|
let vertex_idx: usize = parts[0].parse::<usize>()? - 1;
|
||||||
|
|
||||||
|
let texture_idx =
|
||||||
|
match parts.get(1).and_then(|s| (!s.is_empty()).then(|| *s)) {
|
||||||
|
Some(s) => Some(s.parse::<usize>()? - 1),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let normal_idx =
|
||||||
|
match parts.get(2).and_then(|s| (!s.is_empty()).then(|| *s)) {
|
||||||
|
Some(s) => Some(s.parse::<usize>()? - 1),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(TriangleVertex {
|
||||||
|
vertex_idx,
|
||||||
|
texture_idx,
|
||||||
|
normal_idx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
51
assignment-1d/src/scene/mod.rs
Normal file
51
assignment-1d/src/scene/mod.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
pub mod cylinder;
|
||||||
|
pub mod data;
|
||||||
|
pub mod illumination;
|
||||||
|
pub mod input_file;
|
||||||
|
pub mod object;
|
||||||
|
pub mod sphere;
|
||||||
|
pub mod texture;
|
||||||
|
pub mod triangle;
|
||||||
|
|
||||||
|
use crate::image::Color;
|
||||||
|
use crate::{Point, Point2, Vector};
|
||||||
|
|
||||||
|
use self::data::{Attenuation, DepthCueing, Light, Material};
|
||||||
|
use self::object::Object;
|
||||||
|
use self::texture::{Texture, NormalMap};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Scene {
|
||||||
|
pub eye_pos: Point,
|
||||||
|
pub view_dir: Vector,
|
||||||
|
pub up_dir: Vector,
|
||||||
|
|
||||||
|
/// Horizontal field of view (in degrees)
|
||||||
|
pub hfov: f64,
|
||||||
|
pub parallel_projection: bool,
|
||||||
|
|
||||||
|
pub image_width: usize,
|
||||||
|
pub image_height: usize,
|
||||||
|
|
||||||
|
/// Background color
|
||||||
|
pub bkg_color: Color,
|
||||||
|
pub depth_cueing: DepthCueing,
|
||||||
|
pub attenuation: Attenuation,
|
||||||
|
|
||||||
|
pub materials: Vec<Material>,
|
||||||
|
pub lights: Vec<Light>,
|
||||||
|
pub objects: Vec<Object>,
|
||||||
|
|
||||||
|
/// List of textures
|
||||||
|
pub textures: Vec<Texture>,
|
||||||
|
|
||||||
|
/// List of normal maps (Extra credit)
|
||||||
|
pub normal_maps: Vec<NormalMap>,
|
||||||
|
|
||||||
|
/// Coordinates into a texture image
|
||||||
|
pub texture_vertices: Vec<Point2>,
|
||||||
|
|
||||||
|
/// Triangle vertices
|
||||||
|
pub triangle_vertices: Vec<Point>,
|
||||||
|
pub vertex_normals: Vec<Vector>,
|
||||||
|
}
|
61
assignment-1d/src/scene/object.rs
Normal file
61
assignment-1d/src/scene/object.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::ray::Ray;
|
||||||
|
|
||||||
|
use super::cylinder::Cylinder;
|
||||||
|
use super::illumination::IntersectionContext;
|
||||||
|
use super::sphere::Sphere;
|
||||||
|
use super::triangle::Triangle;
|
||||||
|
use super::Scene;
|
||||||
|
|
||||||
|
/// An object in the scene
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Object {
|
||||||
|
pub kind: ObjectKind,
|
||||||
|
|
||||||
|
/// Index into the scene's material color list
|
||||||
|
pub material_idx: usize,
|
||||||
|
|
||||||
|
/// If this object has a texture, this is the index into the texture list
|
||||||
|
pub texture_idx: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ObjectKind {
|
||||||
|
Sphere(Sphere),
|
||||||
|
Cylinder(Cylinder),
|
||||||
|
Triangle(Triangle),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectKind {
|
||||||
|
/// Determine where the ray intersects this object, returning the earliest
|
||||||
|
/// time this happens. Returns None if no intersection occurs.
|
||||||
|
///
|
||||||
|
/// Also known as Trace_Ray in the slides, except not the part where it calls
|
||||||
|
/// Shade_Ray.
|
||||||
|
pub fn intersects_ray_at(
|
||||||
|
&self,
|
||||||
|
scene: &Scene,
|
||||||
|
ray: &Ray,
|
||||||
|
) -> Result<Option<IntersectionContext>> {
|
||||||
|
match self {
|
||||||
|
ObjectKind::Sphere(sphere) => sphere.intersects_ray_at(scene, ray),
|
||||||
|
ObjectKind::Cylinder(cylinder) => cylinder.intersects_ray_at(scene, ray),
|
||||||
|
ObjectKind::Triangle(triangle) => triangle.intersects_ray_at(scene, ray),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the (u, v) coordinates in the texture (between 0 and 1) that
|
||||||
|
/// corresponds to the intersection point
|
||||||
|
pub fn get_texture_coord(
|
||||||
|
&self,
|
||||||
|
scene: &Scene,
|
||||||
|
ctx: &IntersectionContext,
|
||||||
|
) -> Result<(f64, f64)> {
|
||||||
|
match self {
|
||||||
|
ObjectKind::Sphere(sphere) => sphere.get_texture_coord(ctx),
|
||||||
|
ObjectKind::Cylinder(cylinder) => todo!(),
|
||||||
|
ObjectKind::Triangle(triangle) => triangle.get_texture_coord(scene, ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
94
assignment-1d/src/scene/sphere.rs
Normal file
94
assignment-1d/src/scene/sphere.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
use std::f64::consts::PI;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use ordered_float::NotNan;
|
||||||
|
|
||||||
|
use crate::{ray::Ray, utils::min_f64, Point};
|
||||||
|
|
||||||
|
use super::illumination::IntersectionContext;
|
||||||
|
use super::Scene;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Sphere {
|
||||||
|
pub center: Point,
|
||||||
|
pub radius: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sphere {
|
||||||
|
/// Given a sphere, returns the first time at which this ray intersects the
|
||||||
|
/// sphere.
|
||||||
|
///
|
||||||
|
/// If there is no intersection point, returns None.
|
||||||
|
pub fn intersects_ray_at(
|
||||||
|
&self,
|
||||||
|
_: &Scene,
|
||||||
|
ray: &Ray,
|
||||||
|
) -> Result<Option<IntersectionContext>> {
|
||||||
|
let a = ray.direction.norm();
|
||||||
|
let b = 2.0
|
||||||
|
* (ray.direction.x * (ray.origin.x - self.center.x)
|
||||||
|
+ ray.direction.y * (ray.origin.y - self.center.y)
|
||||||
|
+ ray.direction.z * (ray.origin.z - self.center.z));
|
||||||
|
let c = (ray.origin.x - self.center.x).powi(2)
|
||||||
|
+ (ray.origin.y - self.center.y).powi(2)
|
||||||
|
+ (ray.origin.z - self.center.z).powi(2)
|
||||||
|
- self.radius.powi(2);
|
||||||
|
let discriminant = b * b - 4.0 * a * c;
|
||||||
|
|
||||||
|
let time = match discriminant {
|
||||||
|
// Discriminant < 0, means the equation has no solutions.
|
||||||
|
d if d < 0.0 => None,
|
||||||
|
|
||||||
|
// Discriminant == 0
|
||||||
|
d if d == 0.0 => Some(-b / (2.0 * a)),
|
||||||
|
|
||||||
|
d if d > 0.0 => {
|
||||||
|
let solution_1 = (-b + discriminant.sqrt()) / (2.0 * a);
|
||||||
|
let solution_2 = (-b - discriminant.sqrt()) / (2.0 * a);
|
||||||
|
|
||||||
|
let solutions = [solution_1, solution_2]
|
||||||
|
.into_iter()
|
||||||
|
// Remove any t < 0, since that means it's behind the viewer and we
|
||||||
|
// can't see it.
|
||||||
|
.filter(|t| *t >= 0.0);
|
||||||
|
|
||||||
|
// Return the minimum solution
|
||||||
|
min_f64(solutions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probably hit some NaN or Infinity value due to faulty inputs...
|
||||||
|
_ => unreachable!("Invalid determinant value: {discriminant}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let time = match time.and_then(|t| NotNan::new(t).ok()) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let point = ray.eval(*time);
|
||||||
|
let normal = (point - self.center).normalize();
|
||||||
|
|
||||||
|
Ok(Some(IntersectionContext {
|
||||||
|
time,
|
||||||
|
point,
|
||||||
|
normal,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_texture_coord(
|
||||||
|
&self,
|
||||||
|
ctx: &IntersectionContext,
|
||||||
|
) -> Result<(f64, f64)> {
|
||||||
|
// Reverse engineer the angles from the coordinate of the intersection
|
||||||
|
let cosp = (ctx.point.z - self.center.z) / self.radius;
|
||||||
|
let phi = cosp.acos();
|
||||||
|
let theta =
|
||||||
|
(ctx.point.y - self.center.y).atan2(ctx.point.x - self.center.x);
|
||||||
|
|
||||||
|
// Map theta and phi into 0 - 1 range
|
||||||
|
let v = phi / PI;
|
||||||
|
let u = 0.5 + theta / (2.0 * PI);
|
||||||
|
|
||||||
|
Ok((u, v))
|
||||||
|
}
|
||||||
|
}
|
64
assignment-1d/src/scene/texture.rs
Normal file
64
assignment-1d/src/scene/texture.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use crate::{
|
||||||
|
image::{Color, Image},
|
||||||
|
Vector,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Texture(Image);
|
||||||
|
|
||||||
|
impl Texture {
|
||||||
|
pub fn new(image: Image) -> Self {
|
||||||
|
Self(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pixel_at_exact(&self, x: usize, y: usize) -> Color {
|
||||||
|
// TODO: Debug asserts?
|
||||||
|
|
||||||
|
self.0.data[y * self.0.width + x]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a pixel at the given coordinate. For non-lattice coordinates,
|
||||||
|
/// bi-linear interpolation of the image is done.
|
||||||
|
pub fn pixel_at(&self, u: f64, v: f64) -> Color {
|
||||||
|
debug_assert!(0.0 <= u && u <= 1.0, "u must be between 0 and 1");
|
||||||
|
debug_assert!(0.0 <= v && v <= 1.0, "u must be between 0 and 1");
|
||||||
|
|
||||||
|
// Slide 121
|
||||||
|
let x = u * (self.0.width - 1) as f64;
|
||||||
|
let y = v * (self.0.height - 1) as f64;
|
||||||
|
|
||||||
|
let i = x.floor();
|
||||||
|
let j = y.floor();
|
||||||
|
|
||||||
|
let alpha = x - i;
|
||||||
|
let beta = y - j;
|
||||||
|
|
||||||
|
let i = i as usize;
|
||||||
|
let j = j as usize;
|
||||||
|
|
||||||
|
(1.0 - alpha) * (1.0 - beta) * self.pixel_at_exact(i, j)
|
||||||
|
+ (alpha) * (1.0 - beta) * self.pixel_at_exact(i + 1, j)
|
||||||
|
+ (1.0 - alpha) * (beta) * self.pixel_at_exact(i, j + 1)
|
||||||
|
+ (alpha) * (beta) * self.pixel_at_exact(i + 1, j + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NormalMap(Image);
|
||||||
|
|
||||||
|
impl NormalMap {
|
||||||
|
pub fn new(image: Image) -> Self {
|
||||||
|
Self(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normal_vector_at_exact(&self, x: usize, y: usize) -> Vector {
|
||||||
|
let vec = self.0.data[y * self.0.width + x];
|
||||||
|
|
||||||
|
// So, according to the instructions, this should actually be a value
|
||||||
|
// between -1 and 1. However, we're reading this in through an image.
|
||||||
|
// I'm just going to do the lazy thing here (which theoretically
|
||||||
|
// actually saves cycles) by only doing the transformation when loading
|
||||||
|
// out of the image
|
||||||
|
vec.map(|value| 2.0 * value / 255.0 - 1.0)
|
||||||
|
}
|
||||||
|
}
|
184
assignment-1d/src/scene/triangle.rs
Normal file
184
assignment-1d/src/scene/triangle.rs
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
use std::f64::EPSILON;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use nalgebra::{Matrix2, Vector2, Vector3};
|
||||||
|
use ordered_float::NotNan;
|
||||||
|
|
||||||
|
use crate::ray::Ray;
|
||||||
|
use crate::utils::{cross, dot};
|
||||||
|
use crate::{Point, Vector};
|
||||||
|
|
||||||
|
use super::illumination::IntersectionContext;
|
||||||
|
use super::Scene;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Triangle {
|
||||||
|
/// Indexes into the scene's vertex list
|
||||||
|
pub vertices: Vector3<usize>,
|
||||||
|
|
||||||
|
/// Indexes into the scene's normal coordinates list
|
||||||
|
pub normals: Option<Vector3<usize>>,
|
||||||
|
|
||||||
|
/// Indexes into the scene's texture coordinates list
|
||||||
|
pub textures: Option<Vector3<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Triangle {
|
||||||
|
pub fn intersects_ray_at(
|
||||||
|
&self,
|
||||||
|
scene: &Scene,
|
||||||
|
ray: &Ray,
|
||||||
|
) -> Result<Option<IntersectionContext>> {
|
||||||
|
let (p0, e1, e2) = self.basis_vectors(scene);
|
||||||
|
|
||||||
|
// Solve for the plane equation coefficients A, B, C, D such that:
|
||||||
|
//
|
||||||
|
// $$
|
||||||
|
// Ax + By + Cz + D = 0
|
||||||
|
// $$
|
||||||
|
let n = cross(e1, e2);
|
||||||
|
let a = n.x;
|
||||||
|
let b = n.y;
|
||||||
|
let c = n.z;
|
||||||
|
|
||||||
|
// Sub in p0 to solve for D
|
||||||
|
let d = -(a * p0.x + b * p0.y + c * p0.z);
|
||||||
|
|
||||||
|
// Find the intersection point
|
||||||
|
let time = {
|
||||||
|
let (x0, y0, z0, xd, yd, zd) =
|
||||||
|
match (ray.origin.as_slice(), ray.direction.as_slice()) {
|
||||||
|
([x0, y0, z0], [xd, yd, zd]) => (x0, y0, z0, xd, yd, zd),
|
||||||
|
_ => unreachable!("lol rip no tuple interface"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let denom = a * xd + b * yd + c * zd;
|
||||||
|
if denom == 0.0 {
|
||||||
|
// The ray is parallel to the plane, so there is no intersection point.
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
-(a * x0 + b * y0 + c * z0 + d) / denom
|
||||||
|
};
|
||||||
|
|
||||||
|
let time = NotNan::new(time)?;
|
||||||
|
let point = ray.eval(*time);
|
||||||
|
|
||||||
|
// Use barycentric coordinates to determine if the point is inside of the
|
||||||
|
// triangle
|
||||||
|
// p = p0 + beta * e1 + gamma * e2
|
||||||
|
// Using the whack linear algebra approach derived on slide 57
|
||||||
|
let ep = point - p0;
|
||||||
|
let p = Vector2::new(dot(e1, ep), dot(e2, ep));
|
||||||
|
|
||||||
|
let (alpha, beta, gamma) =
|
||||||
|
self.compute_barycentric_coordinates(scene, p)?;
|
||||||
|
|
||||||
|
// Each of alpha, beta, and gamma must be between 0 and 1
|
||||||
|
if ![alpha, beta, gamma]
|
||||||
|
.into_iter()
|
||||||
|
.all(|v| 0.0 - EPSILON <= v && v <= 1.0 + EPSILON)
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let normal = match self.normals {
|
||||||
|
// If surface normals are provided, then interpolate the normals to do
|
||||||
|
// smooth shading
|
||||||
|
Some(normals) => {
|
||||||
|
let n0 = scene.vertex_normals[normals.x];
|
||||||
|
let n1 = scene.vertex_normals[normals.y];
|
||||||
|
let n2 = scene.vertex_normals[normals.z];
|
||||||
|
|
||||||
|
(alpha * n0 + beta * n1 + gamma * n2).normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
None => n.normalize(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(IntersectionContext {
|
||||||
|
time,
|
||||||
|
point,
|
||||||
|
normal,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the (u, v) texture coordinates corresponding to the point provided in
|
||||||
|
/// the intersection context
|
||||||
|
pub fn get_texture_coord(
|
||||||
|
&self,
|
||||||
|
scene: &Scene,
|
||||||
|
ctx: &IntersectionContext,
|
||||||
|
) -> Result<(f64, f64)> {
|
||||||
|
let texture_coordinates = match self.textures {
|
||||||
|
Some(v) => v,
|
||||||
|
None => {
|
||||||
|
bail!("Textured triangle requested without providing coordinates")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let p0 = scene.texture_vertices[texture_coordinates.x];
|
||||||
|
let p1 = scene.texture_vertices[texture_coordinates.y];
|
||||||
|
let p2 = scene.texture_vertices[texture_coordinates.z];
|
||||||
|
|
||||||
|
let p = self.convert_point(scene, ctx.point);
|
||||||
|
let (alpha, beta, gamma) =
|
||||||
|
self.compute_barycentric_coordinates(scene, p)?;
|
||||||
|
|
||||||
|
let u = alpha * p0.x + beta * p1.x + gamma * p2.x;
|
||||||
|
let v = alpha * p0.y + beta * p1.y + gamma * p2.y;
|
||||||
|
|
||||||
|
Ok((u, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_point(&self, scene: &Scene, point: Point) -> Vector2<f64> {
|
||||||
|
let (p0, e1, e2) = self.basis_vectors(scene);
|
||||||
|
|
||||||
|
let ep = point - p0;
|
||||||
|
Vector2::new(dot(e1, ep), dot(e2, ep))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch the corners of the triangles from the scene
|
||||||
|
#[inline]
|
||||||
|
fn corner_coordinates(&self, scene: &Scene) -> (Point, Point, Point) {
|
||||||
|
let p0 = scene.triangle_vertices[self.vertices.x];
|
||||||
|
let p1 = scene.triangle_vertices[self.vertices.y];
|
||||||
|
let p2 = scene.triangle_vertices[self.vertices.z];
|
||||||
|
|
||||||
|
(p0, p1, p2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the new basis vectors using p0 as the origin. Returns (p0, e1, e2)
|
||||||
|
#[inline]
|
||||||
|
fn basis_vectors(&self, scene: &Scene) -> (Vector, Vector, Vector) {
|
||||||
|
let (p0, p1, p2) = self.corner_coordinates(scene);
|
||||||
|
let e1 = p1 - p0;
|
||||||
|
let e2 = p2 - p0;
|
||||||
|
(p0, e1, e2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute barycentric coordinates
|
||||||
|
fn compute_barycentric_coordinates(
|
||||||
|
&self,
|
||||||
|
scene: &Scene,
|
||||||
|
p: Vector2<f64>,
|
||||||
|
) -> Result<(f64, f64, f64)> {
|
||||||
|
let (_, e1, e2) = self.basis_vectors(scene);
|
||||||
|
let d = Matrix2::new(dot(e1, e1), dot(e1, e2), dot(e2, e1), dot(e2, e2));
|
||||||
|
|
||||||
|
let d_inv = match d.try_inverse() {
|
||||||
|
Some(v) => v,
|
||||||
|
// TODO: Whack
|
||||||
|
None => bail!("No inverse..."),
|
||||||
|
};
|
||||||
|
|
||||||
|
let sol = d_inv * p;
|
||||||
|
let beta = sol.x;
|
||||||
|
let gamma = sol.y;
|
||||||
|
|
||||||
|
// Slide 46
|
||||||
|
let alpha = 1.0 - beta - gamma;
|
||||||
|
|
||||||
|
Ok((alpha, beta, gamma))
|
||||||
|
}
|
||||||
|
}
|
95
assignment-1d/src/utils.rs
Normal file
95
assignment-1d/src/utils.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use nalgebra::{Matrix3, Vector3};
|
||||||
|
use ordered_float::NotNan;
|
||||||
|
|
||||||
|
use crate::{Vector};
|
||||||
|
|
||||||
|
/// Finds the minimum of an iterator of f64s, ignoring any NaN values
|
||||||
|
#[inline]
|
||||||
|
pub fn min_f64<I>(i: I) -> Option<f64>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = f64>,
|
||||||
|
{
|
||||||
|
i.filter_map(|i| NotNan::new(i).ok())
|
||||||
|
.min()
|
||||||
|
.map(|i| i.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the minimum of an iterator of f64s using the given predicate, ignoring
|
||||||
|
/// any NaN values
|
||||||
|
#[inline]
|
||||||
|
pub fn min_f64_by_key<I, F>(i: I, f: F) -> Option<f64>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = f64>,
|
||||||
|
F: FnMut(&NotNan<f64>),
|
||||||
|
{
|
||||||
|
i.filter_map(|i| NotNan::new(i).ok())
|
||||||
|
.min_by_key(f)
|
||||||
|
.map(|i| i.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dot-product between two 3D vectors.
|
||||||
|
#[inline]
|
||||||
|
pub fn dot(a: Vector, b: Vector) -> f64 {
|
||||||
|
a.x * b.x + a.y * b.y + a.z * b.z
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cross-product between two 3D vectors.
|
||||||
|
#[inline]
|
||||||
|
pub fn cross(a: Vector, b: Vector) -> Vector {
|
||||||
|
let x = a.y * b.z - a.z * b.y;
|
||||||
|
let y = a.z * b.x - a.x * b.z;
|
||||||
|
let z = a.x * b.y - a.y * b.x;
|
||||||
|
Vector::new(x, y, z)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the rotation matrix between the 2 given vectors
|
||||||
|
///
|
||||||
|
/// Based on the method given [here][1].
|
||||||
|
///
|
||||||
|
/// [1]: https://math.stackexchange.com/a/897677
|
||||||
|
pub fn compute_rotation_matrix(
|
||||||
|
a: Vector3<f64>,
|
||||||
|
b: Vector3<f64>,
|
||||||
|
) -> Result<Matrix3<f64>> {
|
||||||
|
// Special case: if a and b are in the same direction, just return the
|
||||||
|
// identity matrix.
|
||||||
|
if a.normalize() == b.normalize() {
|
||||||
|
return Ok(Matrix3::identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
let cos_t = dot(a, b);
|
||||||
|
let sin_t = cross(a, b).norm();
|
||||||
|
|
||||||
|
let g = Matrix3::new(cos_t, -sin_t, 0.0, sin_t, cos_t, 0.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
// New basis vectors
|
||||||
|
let u = a;
|
||||||
|
let v = (b - cos_t * a).normalize();
|
||||||
|
let w = cross(b, a);
|
||||||
|
|
||||||
|
// Not sure if this is required to be invertible?
|
||||||
|
let f_inverse = Matrix3::from_columns(&[u, v, w]);
|
||||||
|
let f = match f_inverse.try_inverse() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => {
|
||||||
|
// So I ran into this case trying to compute the rotation matrix where one
|
||||||
|
// of the vector endpoints was (0, 0, 0). I'm pretty sure this case makes
|
||||||
|
// no sense in reality, which means if I ever encounter this case, I
|
||||||
|
// probably made a mistake somewhere before. So going to just error
|
||||||
|
// out here and screw recovering.
|
||||||
|
//
|
||||||
|
// println!("Failed to compute inverse matrix.");
|
||||||
|
// println!("- Initial: a = {a}, b = {b}");
|
||||||
|
// println!("- cos(t) = {cos_t}, sin(t) = {sin_t}");
|
||||||
|
// println!("- Basis: u = {u}, v = {v}, w = {w}");
|
||||||
|
bail!("Failed to compute inverse matrix of {f_inverse}\na = {a}\nb = {b}")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// if (f_inverse * g * f).norm() != 1.0 {
|
||||||
|
// bail!("WTF {}", (f_inverse * g * f).norm());
|
||||||
|
// }
|
||||||
|
|
||||||
|
Ok(f_inverse * g * f)
|
||||||
|
}
|
Loading…
Reference in a new issue