Compare commits
No commits in common. "master" and "proc-macro-version" have entirely different histories.
master
...
proc-macro
1
.gitattributes
vendored
|
@ -1 +0,0 @@
|
||||||
assignment-2*/ext/* linguist-vendored
|
|
3
.gitignore
vendored
|
@ -1,4 +1 @@
|
||||||
.direnv
|
.direnv
|
||||||
.pijul
|
|
||||||
|
|
||||||
/target
|
|
||||||
|
|
2
.ignore
|
@ -1,2 +0,0 @@
|
||||||
.git
|
|
||||||
.DS_Store
|
|
1288
Cargo.lock
generated
20
Cargo.toml
|
@ -1,20 +0,0 @@
|
||||||
[workspace]
|
|
||||||
members = [
|
|
||||||
"assignment-0",
|
|
||||||
"assignment-1a",
|
|
||||||
"assignment-1b",
|
|
||||||
"assignment-1c",
|
|
||||||
"assignment-1d",
|
|
||||||
"assignment-2a-rust",
|
|
||||||
]
|
|
||||||
|
|
||||||
# For profiling with flamegraphs
|
|
||||||
[profile.release]
|
|
||||||
debug = true
|
|
||||||
|
|
||||||
# Optimize for size when creating handin
|
|
||||||
[profile.release-handin]
|
|
||||||
inherits = "release"
|
|
||||||
strip = true
|
|
||||||
lto = true
|
|
||||||
|
|
|
@ -4,6 +4,16 @@ authors = ["Michael Zhang <zhan4854@umn.edu>"]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
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]]
|
[[bin]]
|
||||||
name = "raytracer1b"
|
name = "raytracer1b"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
4
assignment-1c/.gitignore
vendored
|
@ -1,6 +1,6 @@
|
||||||
/target
|
/target
|
||||||
/assignment-1c
|
/assignment-1b
|
||||||
/raytracer1c
|
/raytracer1b
|
||||||
/examples/*.png
|
/examples/*.png
|
||||||
*.ppm
|
*.ppm
|
||||||
*.zip
|
*.zip
|
||||||
|
|
171
assignment-1c/Cargo.lock
generated
|
@ -2,29 +2,11 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
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]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.68"
|
version = "1.0.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
|
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||||
dependencies = [
|
|
||||||
"backtrace",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "approx"
|
name = "approx"
|
||||||
|
@ -42,9 +24,8 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
"clap",
|
"clap",
|
||||||
|
"csci5607-macros",
|
||||||
"derivative",
|
"derivative",
|
||||||
"generator",
|
|
||||||
"itertools",
|
|
||||||
"nalgebra",
|
"nalgebra",
|
||||||
"num",
|
"num",
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
|
@ -60,21 +41,6 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "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]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
|
@ -185,6 +151,16 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "csci5607-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derivative"
|
name = "derivative"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
@ -223,19 +199,6 @@ dependencies = [
|
||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
|
@ -247,12 +210,6 @@ dependencies = [
|
||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gimli"
|
|
||||||
version = "0.27.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -290,15 +247,6 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.10.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -335,12 +283,6 @@ dependencies = [
|
||||||
"rawpointer",
|
"rawpointer",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memoffset"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -350,15 +292,6 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "miniz_oxide"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
|
|
||||||
dependencies = [
|
|
||||||
"adler",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nalgebra"
|
name = "nalgebra"
|
||||||
version = "0.32.1"
|
version = "0.32.1"
|
||||||
|
@ -485,15 +418,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "object"
|
|
||||||
version = "0.30.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
|
@ -565,9 +489,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.50"
|
version = "1.0.51"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
|
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
@ -639,12 +563,6 @@ dependencies = [
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc-demangle"
|
|
||||||
version = "0.1.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.36.7"
|
version = "0.36.7"
|
||||||
|
@ -659,12 +577,6 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustversion"
|
|
||||||
version = "1.0.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "safe_arch"
|
name = "safe_arch"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -879,19 +791,6 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
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]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.42.0"
|
version = "0.42.0"
|
||||||
|
@ -899,12 +798,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm",
|
"windows_aarch64_gnullvm",
|
||||||
"windows_aarch64_msvc 0.42.1",
|
"windows_aarch64_msvc",
|
||||||
"windows_i686_gnu 0.42.1",
|
"windows_i686_gnu",
|
||||||
"windows_i686_msvc 0.42.1",
|
"windows_i686_msvc",
|
||||||
"windows_x86_64_gnu 0.42.1",
|
"windows_x86_64_gnu",
|
||||||
"windows_x86_64_gnullvm",
|
"windows_x86_64_gnullvm",
|
||||||
"windows_x86_64_msvc 0.42.1",
|
"windows_x86_64_msvc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -913,48 +812,24 @@ version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.39.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.42.1"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.39.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.42.1"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.39.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.42.1"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
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]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.42.1"
|
version = "0.42.1"
|
||||||
|
@ -967,12 +842,6 @@ version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
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]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.42.1"
|
version = "0.42.1"
|
||||||
|
|
|
@ -4,17 +4,29 @@ authors = ["Michael Zhang <zhan4854@umn.edu>"]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["csci5607-macros"]
|
||||||
|
|
||||||
|
# For profiling with flamegraphs
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
# Optimize for size when creating handin
|
||||||
|
[profile.release-handin]
|
||||||
|
inherits = "release"
|
||||||
|
strip = true
|
||||||
|
lto = true
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "raytracer1c"
|
name = "raytracer1c"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0.68", features = ["backtrace"] }
|
anyhow = "1.0.68"
|
||||||
base64 = "0.21.0"
|
base64 = "0.21.0"
|
||||||
clap = { version = "4.1.4", features = ["cargo", "derive"] }
|
clap = { version = "4.1.4", features = ["cargo", "derive"] }
|
||||||
|
csci5607-macros = { path = "./csci5607-macros" }
|
||||||
derivative = "2.2.0"
|
derivative = "2.2.0"
|
||||||
generator = "0.7.2"
|
|
||||||
itertools = "0.10.5"
|
|
||||||
nalgebra = "0.32.1"
|
nalgebra = "0.32.1"
|
||||||
num = { version = "0.4.0", features = ["serde"] }
|
num = { version = "0.4.0", features = ["serde"] }
|
||||||
ordered-float = "3.4.0"
|
ordered-float = "3.4.0"
|
||||||
|
|
|
@ -10,6 +10,8 @@ CONVERT := convert
|
||||||
|
|
||||||
HANDIN := ./hw1c.michael.zhang.zip
|
HANDIN := ./hw1c.michael.zhang.zip
|
||||||
BINARY := ./raytracer1c
|
BINARY := ./raytracer1c
|
||||||
|
WRITEUP := ./writeup.pdf
|
||||||
|
SHOWCASE := ./showcase.png
|
||||||
SOURCES := Cargo.toml $(shell find -name "*.rs")
|
SOURCES := Cargo.toml $(shell find -name "*.rs")
|
||||||
|
|
||||||
EXAMPLES := $(shell find examples -name "*.txt")
|
EXAMPLES := $(shell find examples -name "*.txt")
|
||||||
|
@ -31,16 +33,22 @@ $(BINARY): $(SOURCES)
|
||||||
cargo build --profile release-handin
|
cargo build --profile release-handin
|
||||||
mv target/docker/release-handin/raytracer1c $@
|
mv target/docker/release-handin/raytracer1c $@
|
||||||
|
|
||||||
$(HANDIN): $(BINARY) Makefile Cargo.toml Cargo.lock README.md $(EXAMPLES_PNG) $(EXAMPLES_PPM)
|
$(HANDIN): $(BINARY) $(WRITEUP) Makefile Cargo.toml Cargo.lock README.md $(EXAMPLES_PNG) $(EXAMPLES_PPM) $(SHOWCASE)
|
||||||
$(ZIP) -r $@ src examples $^
|
$(ZIP) -r $@ src examples $^
|
||||||
|
|
||||||
|
$(SHOWCASE): examples/soft-shadow-demo.png
|
||||||
|
cp $< $@
|
||||||
|
|
||||||
examples/%.ppm: examples/%.txt $(SOURCES)
|
examples/%.ppm: examples/%.txt $(SOURCES)
|
||||||
cargo run --release -- -o $@ $(RAYTRACER_FLAGS) $<
|
cargo run --release -- -o $@ $(RAYTRACER_FLAGS) $<
|
||||||
|
|
||||||
examples/%.png: examples/%.ppm
|
examples/%.png: examples/%.ppm
|
||||||
convert $< $@
|
convert $< $@
|
||||||
|
|
||||||
|
writeup.pdf: writeup.md $(EXAMPLES_PNG)
|
||||||
|
$(PANDOC) -o $@ $<
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf target/docker \
|
rm -rf target/docker \
|
||||||
$(HANDIN) $(BINARY) \
|
$(HANDIN) $(BINARY) $(WRITEUP) $(SHOWCASE) \
|
||||||
$(EXAMPLES_PPM) $(EXAMPLES_PNG)
|
$(EXAMPLES_PPM) $(EXAMPLES_PNG)
|
||||||
|
|
13
assignment-1c/csci5607-macros/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "csci5607-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.69"
|
||||||
|
proc-macro2 = "1.0.51"
|
||||||
|
quote = "1.0.23"
|
||||||
|
syn = "1.0.107"
|
65
assignment-1c/csci5607-macros/src/lib.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate anyhow;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{Data, DataStruct, DeriveInput, Field};
|
||||||
|
|
||||||
|
#[proc_macro_derive(Csci5607Parser)]
|
||||||
|
pub fn derive_answer_fn(tokens: TokenStream) -> TokenStream {
|
||||||
|
let tokens: TokenStream2 = tokens.into();
|
||||||
|
match derive_answer_fn_impl(tokens) {
|
||||||
|
Ok(v) => v.into(),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error: {e}");
|
||||||
|
panic!("{e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_answer_fn_impl(tokens: TokenStream2) -> Result<TokenStream2> {
|
||||||
|
let derive_input: DeriveInput = syn::parse2(tokens)?;
|
||||||
|
|
||||||
|
// Only defined on structs
|
||||||
|
let struct_input: DataStruct = match derive_input.data {
|
||||||
|
Data::Struct(v) => v,
|
||||||
|
_ => bail!("Only defined on structs."),
|
||||||
|
};
|
||||||
|
let struct_ident = derive_input.ident;
|
||||||
|
|
||||||
|
// Gather struct fields
|
||||||
|
let mut initializers = TokenStream2::default();
|
||||||
|
let mut final_constructor = TokenStream2::default();
|
||||||
|
for field in struct_input.fields {
|
||||||
|
let field_ident = field.ident;
|
||||||
|
initializers.extend(quote! { let #field_ident = todo!(); });
|
||||||
|
final_constructor.extend(quote! { #field_ident, });
|
||||||
|
|
||||||
|
// Check if this field is a vec
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
quote! {
|
||||||
|
impl std::str::FromStr for #struct_ident {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
#initializers
|
||||||
|
|
||||||
|
for line in contents.lines() {
|
||||||
|
let mut parts = line.split_whitespace();
|
||||||
|
let keyword = match parts.next() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(#struct_ident { #final_constructor })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
2
assignment-1c/examples/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
||||||
# Necessary files
|
|
||||||
!/earthtexture.ppm
|
|
|
@ -14,4 +14,4 @@ v 1 -1 -4
|
||||||
v 2 1 -6
|
v 2 1 -6
|
||||||
|
|
||||||
f 1 2 3
|
f 1 2 3
|
||||||
f 1 3 4
|
f 1 3 4
|
Before Width: | Height: | Size: 9.9 KiB |
|
@ -1,14 +0,0 @@
|
||||||
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 0 1 0 1 1 1 0.2 0.6 0.2 20
|
|
||||||
v 0 1 -4
|
|
||||||
v -1 -1 -4
|
|
||||||
v 1 -1 -4
|
|
||||||
v 2 1 -6
|
|
||||||
f 1 2 3
|
|
||||||
f 1 3 4
|
|
Before Width: | Height: | Size: 12 KiB |
|
@ -1,18 +0,0 @@
|
||||||
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 0 0 1 1 1 1 0.2 0.6 0.2 20
|
|
||||||
v -1 1 -4
|
|
||||||
v -1 -1 -4
|
|
||||||
v 1 -1 -4
|
|
||||||
v 1 1 -4
|
|
||||||
vn -1 1 1
|
|
||||||
vn -1 -1 1
|
|
||||||
vn 1 -1 1
|
|
||||||
vn 1 1 1
|
|
||||||
f 1//1 2//2 3//3
|
|
||||||
f 1//1 3//3 4//4
|
|
Before Width: | Height: | Size: 105 KiB |
|
@ -1,10 +0,0 @@
|
||||||
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
|
|
Before Width: | Height: | Size: 28 KiB |
|
@ -1,19 +0,0 @@
|
||||||
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 0 0 1 1 1 1 0.2 0.6 0.2 20
|
|
||||||
texture umn.ppm
|
|
||||||
v -1 1 -4
|
|
||||||
v -1 -1 -4
|
|
||||||
v 1 -1 -4
|
|
||||||
v 1 1 -4
|
|
||||||
vt 0 0
|
|
||||||
vt 0 1
|
|
||||||
vt 1 1
|
|
||||||
vt 1 0
|
|
||||||
f 1/1 2/2 3/3
|
|
||||||
f 1/1 3/3 4/4
|
|
Before Width: | Height: | Size: 25 KiB |
|
@ -1,25 +0,0 @@
|
||||||
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 0 0 1 1 1 1 0.2 0.6 0.2 20
|
|
||||||
texture umn.ppm
|
|
||||||
v -1 1 -4
|
|
||||||
v -1 -1 -4
|
|
||||||
v 1 -1 -4
|
|
||||||
v 1 1 -4
|
|
||||||
vn -1 1 1
|
|
||||||
vn -1 -1 1
|
|
||||||
vn 1 -1 1
|
|
||||||
vn 1 1 1
|
|
||||||
vt 0 0
|
|
||||||
vt 0 1
|
|
||||||
vt 1 1
|
|
||||||
vt 1 0
|
|
||||||
f 1/1/1 2/2/2 3/3/3
|
|
||||||
f 1/1/1 3/3/3 4/4/4
|
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 640 KiB |
|
@ -1,56 +0,0 @@
|
||||||
eye 12.5 5 2.5
|
|
||||||
viewdir -2 -0.5 2
|
|
||||||
updir 0 1 0
|
|
||||||
hfov 60
|
|
||||||
imsize 900 600
|
|
||||||
bkgcolor 0.5 0.7 0.9
|
|
||||||
light 1 -1 1 0 1 1 1
|
|
||||||
mtlcolor 0 1 0 1 1 1 0.2 0.8 0 20
|
|
||||||
v 10 0 5
|
|
||||||
v -10 0 5
|
|
||||||
v -10 0 25
|
|
||||||
v 10 0 25
|
|
||||||
v 5 0 12.5
|
|
||||||
v -5 0 12.5
|
|
||||||
v -5 5 12.5
|
|
||||||
v 5 5 12.5
|
|
||||||
v 5 0 17.5
|
|
||||||
v -5 0 17.5
|
|
||||||
v -5 5 17.5
|
|
||||||
v 5 5 17.5
|
|
||||||
v 5 7.5 15
|
|
||||||
v -5 7.5 15
|
|
||||||
v 5 4.5 12
|
|
||||||
v -5 4.5 12
|
|
||||||
v 5 4.5 18
|
|
||||||
v -5 4.5 18
|
|
||||||
vt 0 0
|
|
||||||
vt 0 1
|
|
||||||
vt 1 1
|
|
||||||
vt 1 0
|
|
||||||
vt 2 0
|
|
||||||
vt 2 1
|
|
||||||
vt 0.5 0
|
|
||||||
vt 4 0
|
|
||||||
vt 4 1
|
|
||||||
texture grass.ppm
|
|
||||||
f 1/2 2/3 3/4
|
|
||||||
f 1/2 3/4 4/1
|
|
||||||
texture wood.ppm
|
|
||||||
f 5/2 6/6 7/5
|
|
||||||
f 5/2 7/5 8/1
|
|
||||||
f 9/2 11/5 10/6
|
|
||||||
f 9/2 12/1 11/5
|
|
||||||
f 6/2 10/3 11/4
|
|
||||||
f 6/2 11/4 7/1
|
|
||||||
f 5/3 12/1 9/2
|
|
||||||
f 5/3 8/4 12/1
|
|
||||||
f 7/2 11/3 14/7
|
|
||||||
f 8/3 13/7 12/2
|
|
||||||
texture redwood.ppm
|
|
||||||
f 13/1 15/2 16/9
|
|
||||||
f 13/1 16/9 14/8
|
|
||||||
f 13/8 14/1 18/2
|
|
||||||
f 13/8 18/2 17/9
|
|
||||||
texture soccerball.ppm
|
|
||||||
sphere -2.5 0.5 9 0.5
|
|
Before Width: | Height: | Size: 46 KiB |
|
@ -1,20 +0,0 @@
|
||||||
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 1 1 1 1 1 0.2 0.6 0.2 20 1 0
|
|
||||||
bump normalmap.ppm
|
|
||||||
sphere -1.6 0 -4 0.5
|
|
||||||
v -1 1 -4
|
|
||||||
v -1 -1 -4
|
|
||||||
v 1 -1 -4
|
|
||||||
v 1 1 -4
|
|
||||||
vt 0 0
|
|
||||||
vt 0 1
|
|
||||||
vt 1 1
|
|
||||||
vt 1 0
|
|
||||||
f 1/1 2/2 3/3
|
|
||||||
f 1/1 3/3 4/4
|
|
|
@ -1,2 +0,0 @@
|
||||||
#! /bin/sh
|
|
||||||
PROGRAM_NAME="raytracer1c"; echo "-------- Running Test1.txt --------"; ./$PROGRAM_NAME Test1.txt; echo "-------- Running Test2.txt --------";./$PROGRAM_NAME Test2.txt; echo "-------- Running Test3.txt --------";./$PROGRAM_NAME Test3.txt; echo "-------- Running Test4.txt --------";./$PROGRAM_NAME Test4.txt; echo "-------- Running Test5.txt --------";./$PROGRAM_NAME Test5.txt; echo "-------- Running Test6.txt --------";./$PROGRAM_NAME Test6.txt; echo "-------- Running TestE.txt --------";./$PROGRAM_NAME TestE.txt;
|
|
|
@ -1,27 +0,0 @@
|
||||||
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
|
|
|
@ -1,45 +0,0 @@
|
||||||
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
|
|
|
@ -1,49 +0,0 @@
|
||||||
CSci 5607, Spring 2023
|
|
||||||
Assignment 1c: Triangles and Texture
|
|
||||||
Due: Friday March 3rd
|
|
||||||
|
|
||||||
- [x] The program robustly accepts extended scene description files that include
|
|
||||||
texture images, texture coordinates and surface normal vectors. The
|
|
||||||
program is able to robustly handle triangle definitions that either
|
|
||||||
include or do not include per-vertex normal directions and include or do
|
|
||||||
not include per-vertex texture coordinates, in addition to vertex
|
|
||||||
locations. The syntax used to define textured and/or smooth-shaded
|
|
||||||
triangles follows the classic .obj format as described in class. (5 pts)
|
|
||||||
|
|
||||||
- [x] The program correctly computes ray/plane intersections, and correctly
|
|
||||||
performs point-in-triangle testing using barycentric coordinates, enabling
|
|
||||||
the correct rendering of scenes containing triangles as well as spheres.
|
|
||||||
(20 pts)
|
|
||||||
|
|
||||||
- [x] The program is capable of rendering triangles using flat shading, in which
|
|
||||||
every pixel in a triangle is assigned the same color, obtained by
|
|
||||||
correctly evaluating the Phong illumination equation using the unit length
|
|
||||||
normal of the plane in which the triangle lies. (10 pts)
|
|
||||||
|
|
||||||
- [x] The program is capable of rendering triangles using smooth shading, in
|
|
||||||
which every pixel within a triangle is assigned a unique color, obtained
|
|
||||||
by evaluating the Phong illumination equation using a unit length normal
|
|
||||||
direction correctly interpolated from the three normal directions defined
|
|
||||||
at the three triangle vertices. (15 pts)
|
|
||||||
|
|
||||||
- [x] The program is capable of rendering textured spheres. An appropriate
|
|
||||||
texture coordinate is computed at each ray/sphere intersection point. This
|
|
||||||
can be done at runtime using a pre-defined, hard-coded algorithm. The
|
|
||||||
computed texture coordinate is used to retrieve a correctly corresponding
|
|
||||||
color from the associated texture map, which replaces the object’s diffuse
|
|
||||||
color in the Phong illumination model when it is evaluated at that point.
|
|
||||||
(25 pts)
|
|
||||||
|
|
||||||
- [x] The program is capable of rendering textured triangles. The texture
|
|
||||||
coordinate at the ray/triangle intersection point is correctly
|
|
||||||
interpolated from the texture coordinates defined at each of the three
|
|
||||||
triangle vertices. The interpolated texture coordinate is used to retrieve
|
|
||||||
a correctly corresponding color from the associated texture map, which
|
|
||||||
replaces the object’s diffuse color in the Phong illumination model when
|
|
||||||
it is evaluated at that point. (25 pts)
|
|
||||||
|
|
||||||
- [ ] Extra credit: The program is capable of reading a normal map from a file
|
|
||||||
and correctly using the values in that map to appropriately vary the
|
|
||||||
surface normal direction used when calculating the Phone illumination
|
|
||||||
equation at each point across the surface. The normal mapping is capable
|
|
||||||
of being applied to both triangles and spheres. (7 pts)
|
|
|
@ -1,20 +1,11 @@
|
||||||
use std::{
|
use std::io::{Result, Write};
|
||||||
fs::File,
|
|
||||||
io::{BufRead, BufReader, Read, Write},
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use generator::{done, Gn};
|
|
||||||
use itertools::Itertools;
|
|
||||||
use nalgebra::Vector3;
|
use nalgebra::Vector3;
|
||||||
|
|
||||||
/// A pixel color represented by a red, green, and blue value in the range 0-1.
|
/// A pixel color represented by a red, green, and blue value in the range 0-1.
|
||||||
pub type Color = Vector3<f64>;
|
pub type Color = Vector3<f64>;
|
||||||
|
|
||||||
/// A representation of an image
|
/// A representation of an image
|
||||||
#[derive(Derivative)]
|
|
||||||
#[derivative(Debug)]
|
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
/// Width in pixels
|
/// Width in pixels
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
|
@ -23,87 +14,10 @@ pub struct Image {
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
|
|
||||||
/// Pixel data in row-major form.
|
/// Pixel data in row-major form.
|
||||||
#[derivative(Debug = "ignore")]
|
|
||||||
pub data: Vec<Color>,
|
pub data: Vec<Color>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
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.
|
/// Write the image in PPM format to a file.
|
||||||
pub fn write(&self, mut w: impl Write) -> Result<()> {
|
pub fn write(&self, mut w: impl Write) -> Result<()> {
|
||||||
// Header
|
// Header
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use nalgebra::{Vector2, Vector3};
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
|
use nalgebra::Vector3;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate anyhow;
|
extern crate anyhow;
|
||||||
|
@ -12,6 +14,4 @@ pub mod ray;
|
||||||
pub mod scene;
|
pub mod scene;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
pub type Point2 = Vector2<f64>;
|
|
||||||
pub type Point = Vector3<f64>;
|
pub type Point = Vector3<f64>;
|
||||||
pub type Vector = Vector3<f64>;
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ extern crate tracing;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use assignment_1c::image::Image;
|
use assignment_1c::image::Image;
|
||||||
use assignment_1c::ray::Ray;
|
use assignment_1c::ray::Ray;
|
||||||
use assignment_1c::scene::Scene;
|
use assignment_1c::scene::Scene;
|
||||||
|
@ -91,7 +91,7 @@ fn main() -> Result<()> {
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(i, object)| {
|
.filter_map(|(i, object)| {
|
||||||
match object.kind.intersects_ray_at(&scene, &ray) {
|
match object.kind.intersects_ray_at(&ray) {
|
||||||
Ok(Some(t)) => {
|
Ok(Some(t)) => {
|
||||||
// Return both the t and the sphere, because we want to sort on
|
// Return both the t and the sphere, because we want to sort on
|
||||||
// the t but later retrieve attributes from the sphere
|
// the t but later retrieve attributes from the sphere
|
||||||
|
@ -113,8 +113,7 @@ fn main() -> Result<()> {
|
||||||
Ok(match earliest_intersection {
|
Ok(match earliest_intersection {
|
||||||
// Take the object's material color
|
// Take the object's material color
|
||||||
Some((obj_idx, intersection_context, object)) => scene
|
Some((obj_idx, intersection_context, object)) => scene
|
||||||
.compute_pixel_color(obj_idx, object, intersection_context)
|
.compute_pixel_color(obj_idx, object.material, intersection_context),
|
||||||
.context("Could not compute pixel color.")?,
|
|
||||||
|
|
||||||
// There was no intersection, so this should default to the scene's
|
// There was no intersection, so this should default to the scene's
|
||||||
// background color
|
// background color
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Point, Vector};
|
use crate::Point;
|
||||||
|
|
||||||
/// A normalized parametric Ray of the form (origin + direction * time)
|
/// A normalized parametric Ray of the form (origin + direction * time)
|
||||||
///
|
///
|
||||||
|
@ -7,7 +7,7 @@ use crate::{Point, Vector};
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Ray {
|
pub struct Ray {
|
||||||
pub origin: Point,
|
pub origin: Point,
|
||||||
pub direction: Vector,
|
pub direction: Point,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ray {
|
impl Ray {
|
||||||
|
|
|
@ -3,16 +3,14 @@ use anyhow::Result;
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
|
|
||||||
use crate::utils::compute_rotation_matrix;
|
use crate::utils::compute_rotation_matrix;
|
||||||
use crate::Vector;
|
|
||||||
use crate::{ray::Ray, Point};
|
use crate::{ray::Ray, Point};
|
||||||
|
|
||||||
use super::illumination::IntersectionContext;
|
use super::illumination::IntersectionContext;
|
||||||
use super::Scene;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Cylinder {
|
pub struct Cylinder {
|
||||||
pub center: Point,
|
pub center: Point,
|
||||||
pub direction: Vector,
|
pub direction: Point,
|
||||||
pub radius: f64,
|
pub radius: f64,
|
||||||
pub length: f64,
|
pub length: f64,
|
||||||
}
|
}
|
||||||
|
@ -24,12 +22,11 @@ impl Cylinder {
|
||||||
/// If there is no intersection point, returns None.
|
/// If there is no intersection point, returns None.
|
||||||
pub fn intersects_ray_at(
|
pub fn intersects_ray_at(
|
||||||
&self,
|
&self,
|
||||||
_: &Scene,
|
|
||||||
ray: &Ray,
|
ray: &Ray,
|
||||||
) -> Result<Option<IntersectionContext>> {
|
) -> Result<Option<IntersectionContext>> {
|
||||||
// Determine rotation matrix for turning the cylinder upright along the
|
// Determine rotation matrix for turning the cylinder upright along the
|
||||||
// Z-axis
|
// Z-axis
|
||||||
let target_direction = Vector::new(0.0, 0.0, 1.0);
|
let target_direction = Point::new(0.0, 0.0, 1.0);
|
||||||
let rotation_matrix =
|
let rotation_matrix =
|
||||||
compute_rotation_matrix(self.direction, target_direction)?;
|
compute_rotation_matrix(self.direction, target_direction)?;
|
||||||
let inverse_rotation_matrix =
|
let inverse_rotation_matrix =
|
||||||
|
@ -156,7 +153,7 @@ impl Cylinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
let normal_rotated =
|
let normal_rotated =
|
||||||
Vector::new(0.0, 0.0, rotated_point.z - rotated_cylinder_center.z)
|
Point::new(0.0, 0.0, rotated_point.z - rotated_cylinder_center.z)
|
||||||
.normalize();
|
.normalize();
|
||||||
let normal = inverse_rotation_matrix * normal_rotated;
|
let normal = inverse_rotation_matrix * normal_rotated;
|
||||||
|
|
||||||
|
@ -182,7 +179,7 @@ impl Cylinder {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{ray::Ray, scene::Scene, Point, Vector};
|
use crate::{ray::Ray, Point};
|
||||||
|
|
||||||
use super::Cylinder;
|
use super::Cylinder;
|
||||||
|
|
||||||
|
@ -190,7 +187,7 @@ mod tests {
|
||||||
fn test_cylinder() {
|
fn test_cylinder() {
|
||||||
let cylinder = Cylinder {
|
let cylinder = Cylinder {
|
||||||
center: Point::new(0.0, 0.0, 0.0),
|
center: Point::new(0.0, 0.0, 0.0),
|
||||||
direction: Vector::new(0.0, 1.0, 0.0),
|
direction: Point::new(0.0, 1.0, 0.0),
|
||||||
radius: 3.0,
|
radius: 3.0,
|
||||||
length: 4.0,
|
length: 4.0,
|
||||||
};
|
};
|
||||||
|
@ -199,8 +196,7 @@ mod tests {
|
||||||
let end = Point::new(0.0, 2.0, 2.0);
|
let end = Point::new(0.0, 2.0, 2.0);
|
||||||
let ray = Ray::from_endpoints(eye, end);
|
let ray = Ray::from_endpoints(eye, end);
|
||||||
|
|
||||||
let scene = Scene::default();
|
let res = cylinder.intersects_ray_at(&ray);
|
||||||
let _res = cylinder.intersects_ray_at(&scene, &ray);
|
panic!("Result: {res:?}");
|
||||||
// panic!("Result: {res:?}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,49 @@
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::image::Color;
|
use crate::image::Color;
|
||||||
|
use crate::ray::Ray;
|
||||||
use crate::utils::cross;
|
use crate::utils::cross;
|
||||||
use crate::Point;
|
use crate::Point;
|
||||||
|
|
||||||
|
use super::cylinder::Cylinder;
|
||||||
|
use super::illumination::IntersectionContext;
|
||||||
|
use super::sphere::Sphere;
|
||||||
use super::Scene;
|
use super::Scene;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ObjectKind {
|
||||||
|
Sphere(Sphere),
|
||||||
|
Cylinder(Cylinder),
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
ray: &Ray,
|
||||||
|
) -> Result<Option<IntersectionContext>> {
|
||||||
|
match self {
|
||||||
|
ObjectKind::Sphere(sphere) => sphere.intersects_ray_at(ray),
|
||||||
|
ObjectKind::Cylinder(cylinder) => cylinder.intersects_ray_at(ray),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An object in the scene
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Object {
|
||||||
|
pub kind: ObjectKind,
|
||||||
|
|
||||||
|
/// Index into the scene's material color list
|
||||||
|
pub material: usize,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
pub upper_left: Point,
|
pub upper_left: Point,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rayon::prelude::{
|
use rayon::prelude::{
|
||||||
|
@ -8,11 +7,10 @@ use rayon::prelude::{
|
||||||
ParallelIterator,
|
ParallelIterator,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{image::Color, ray::Ray, utils::dot, Point, Vector};
|
use crate::{image::Color, ray::Ray, utils::dot, Point};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
data::{DepthCueing, Light, LightKind},
|
data::{DepthCueing, Light, LightKind, Object},
|
||||||
object::Object,
|
|
||||||
Scene,
|
Scene,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,34 +25,20 @@ impl Scene {
|
||||||
pub fn compute_pixel_color(
|
pub fn compute_pixel_color(
|
||||||
&self,
|
&self,
|
||||||
obj_idx: usize,
|
obj_idx: usize,
|
||||||
object: &Object,
|
material_idx: usize,
|
||||||
intersection_context: IntersectionContext,
|
intersection_context: IntersectionContext,
|
||||||
) -> Result<Color> {
|
) -> Color {
|
||||||
let material = match self.materials.get(object.material_idx) {
|
// TODO: Does it make sense to make this function fallible from an API
|
||||||
|
// design standpoint?
|
||||||
|
let material = match self.materials.get(material_idx) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => bail!("Material index not found."),
|
None => return self.bkg_color,
|
||||||
};
|
};
|
||||||
|
|
||||||
let diffuse_color = match object.texture_idx {
|
let ambient_component = material.k_a * material.diffuse_color;
|
||||||
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 {texture_idx} 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
|
// Diffuse and specular lighting for each separate light
|
||||||
let diffuse_and_specular: Color = self
|
let diffuse_and_specular: Point = self
|
||||||
.lights
|
.lights
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.map(|light| {
|
.map(|light| {
|
||||||
|
@ -67,7 +51,7 @@ impl Scene {
|
||||||
((light_direction + viewer_direction) / 2.0).normalize();
|
((light_direction + viewer_direction) / 2.0).normalize();
|
||||||
|
|
||||||
let diffuse_component = material.k_d
|
let diffuse_component = material.k_d
|
||||||
* diffuse_color
|
* material.diffuse_color
|
||||||
* dot(normal, light_direction).max(0.0);
|
* dot(normal, light_direction).max(0.0);
|
||||||
|
|
||||||
let specular_component = material.k_s
|
let specular_component = material.k_s
|
||||||
|
@ -138,7 +122,7 @@ impl Scene {
|
||||||
// Need to clamp the result so none of the components goes over 1
|
// Need to clamp the result so none of the components goes over 1
|
||||||
let clamped_result = color.map(|v| v.min(1.0));
|
let clamped_result = color.map(|v| v.min(1.0));
|
||||||
|
|
||||||
Ok(clamped_result)
|
clamped_result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform another ray casting to see if there are any objects obstructing
|
/// Perform another ray casting to see if there are any objects obstructing
|
||||||
|
@ -167,14 +151,13 @@ impl Scene {
|
||||||
// This list will be a set of opacities
|
// This list will be a set of opacities
|
||||||
let intersections = other_objects
|
let intersections = other_objects
|
||||||
.filter_map(|(_, object)| {
|
.filter_map(|(_, object)| {
|
||||||
let intersection_context =
|
let intersection_context = match object.kind.intersects_ray_at(&ray) {
|
||||||
match object.kind.intersects_ray_at(&self, &ray) {
|
Ok(v) => v,
|
||||||
Ok(v) => v,
|
Err(err) => {
|
||||||
Err(err) => {
|
error!("Error while performing shadow casting: {err}");
|
||||||
error!("Error while performing shadow casting: {err}");
|
None
|
||||||
None
|
}
|
||||||
}
|
}?;
|
||||||
}?;
|
|
||||||
let intersection_time = *intersection_context.time;
|
let intersection_time = *intersection_context.time;
|
||||||
|
|
||||||
match light.kind {
|
match light.kind {
|
||||||
|
@ -205,13 +188,12 @@ impl Scene {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let average =
|
||||||
|
intersections.iter().cloned().sum::<f64>() / intersections.len() as f64;
|
||||||
|
|
||||||
match intersections.is_empty() {
|
match intersections.is_empty() {
|
||||||
true => 1.0,
|
true => 1.0,
|
||||||
false => {
|
false => average,
|
||||||
let average = intersections.iter().cloned().sum::<f64>()
|
|
||||||
/ intersections.len() as f64;
|
|
||||||
average
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,7 +214,7 @@ impl Scene {
|
||||||
let x = rng.gen_range(0.0..JITTER_RADIUS);
|
let x = rng.gen_range(0.0..JITTER_RADIUS);
|
||||||
let y = 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 z = rng.gen_range(0.0..JITTER_RADIUS);
|
||||||
let delta = Vector::new(x, y, z);
|
let delta = Point::new(x, y, z);
|
||||||
light_location + delta
|
light_location + delta
|
||||||
})
|
})
|
||||||
.take(JITTER_RAYS)
|
.take(JITTER_RAYS)
|
||||||
|
@ -246,15 +228,14 @@ impl Scene {
|
||||||
direction,
|
direction,
|
||||||
};
|
};
|
||||||
|
|
||||||
let intersection_context =
|
let intersection_context = match object.kind.intersects_ray_at(&ray) {
|
||||||
match object.kind.intersects_ray_at(&self, &ray) {
|
Ok(Some(v)) => v,
|
||||||
Ok(Some(v)) => v,
|
Ok(None) => return false,
|
||||||
Ok(None) => return false,
|
Err(err) => {
|
||||||
Err(err) => {
|
error!("Error while performing shadow casting: {err}");
|
||||||
error!("Error while performing shadow casting: {err}");
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
let light_time = (location - ray.origin).norm();
|
let light_time = (location - ray.origin).norm();
|
||||||
let intersection_time = *intersection_context.time;
|
let intersection_time = *intersection_context.time;
|
||||||
|
@ -286,7 +267,7 @@ pub struct IntersectionContext {
|
||||||
/// The normal vector protruding from the surface of the object at the
|
/// The normal vector protruding from the surface of the object at the
|
||||||
/// intersection point
|
/// intersection point
|
||||||
#[derivative(PartialEq = "ignore", Ord = "ignore")]
|
#[derivative(PartialEq = "ignore", Ord = "ignore")]
|
||||||
pub normal: Vector,
|
pub normal: Point,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for IntersectionContext {}
|
impl Eq for IntersectionContext {}
|
||||||
|
|
|
@ -1,420 +1,189 @@
|
||||||
use std::fs::File;
|
use std::{fs::File, io::Read, path::Path};
|
||||||
use std::io::Read;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use itertools::Itertools;
|
|
||||||
use nalgebra::{Vector2, Vector3};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
image::{Color, Image},
|
|
||||||
scene::{
|
scene::{
|
||||||
cylinder::Cylinder,
|
cylinder::Cylinder,
|
||||||
data::{Attenuation, Light, LightKind, Material},
|
data::{Attenuation, Light, LightKind, Material, Object},
|
||||||
object::{Object, ObjectKind},
|
|
||||||
sphere::Sphere,
|
sphere::Sphere,
|
||||||
texture::Texture,
|
|
||||||
triangle::Triangle,
|
|
||||||
Scene,
|
Scene,
|
||||||
},
|
},
|
||||||
Point, Vector,
|
Point,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::data::DepthCueing;
|
use super::data::{DepthCueing, ObjectKind};
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
/// Parse the input file into a scene
|
/// Parse the input file into a scene
|
||||||
pub fn from_input_file(path: impl AsRef<Path>) -> Result<Self> {
|
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
|
// Scope the read so the file is dropped and closed immediately after the
|
||||||
// contents have been read to memory
|
// contents have been read to memory
|
||||||
let contents = {
|
let contents = {
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
let mut file = File::open(path)?;
|
let mut file = File::open(path.as_ref())?;
|
||||||
file.read_to_string(&mut contents)?;
|
file.read_to_string(&mut contents)?;
|
||||||
contents
|
contents
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut scene = Scene::default();
|
let mut scene = Scene::default();
|
||||||
let mut material_idx = None;
|
let mut material_color = None;
|
||||||
let mut texture_idx = None;
|
|
||||||
|
|
||||||
for line in contents.lines() {
|
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();
|
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() {
|
let keyword = match parts.next() {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Short for "read", macro for reading something from the iterator and
|
if keyword == "imsize" {
|
||||||
/// converting it into the appropriate format given by $ty. For this to
|
let parts = parts
|
||||||
/// work, $ty must implement Construct
|
.map(|s| s.parse::<usize>().map_err(|e| e.into()))
|
||||||
macro_rules! r {
|
.collect::<Result<Vec<_>>>()?;
|
||||||
($ty:ty) => {
|
if let [width, height] = parts[..] {
|
||||||
<$ty>::construct(&mut parts, ())
|
scene.image_width = width;
|
||||||
.with_context(|| format!("Could not parse {}", stringify!($ty)))?
|
scene.image_height = height;
|
||||||
};
|
}
|
||||||
|
} else if keyword == "projection" {
|
||||||
($ty:ty, $($ex:expr),* $(,)?) => {
|
if let Some("parallel") = parts.next() {
|
||||||
<$ty>::construct(&mut parts, $($ex,)*)
|
scene.parallel_projection = true;
|
||||||
.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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
// Do float parsing instead
|
||||||
|
else {
|
||||||
|
let parts = parts
|
||||||
|
.map(|s| s.parse::<f64>().map_err(|e| e.into()))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
match keyword {
|
let read_vec3 = |start: usize| {
|
||||||
"imsize" => {
|
ensure!(parts.len() >= start + 3, "Vec3 requires 3 components.");
|
||||||
scene.image_width = r!(usize);
|
|
||||||
scene.image_height = r!(usize);
|
Ok(Point::new(parts[start], parts[start + 1], parts[start + 2]))
|
||||||
}
|
};
|
||||||
"projection" => {
|
|
||||||
if let Some("parallel") = parts.next() {
|
match keyword {
|
||||||
scene.parallel_projection = true;
|
"eye" => scene.eye_pos = read_vec3(0)?,
|
||||||
|
"viewdir" => scene.view_dir = read_vec3(0)?,
|
||||||
|
"updir" => scene.up_dir = read_vec3(0)?,
|
||||||
|
|
||||||
|
"hfov" => scene.hfov = parts[0],
|
||||||
|
"bkgcolor" => scene.bkg_color = read_vec3(0)?,
|
||||||
|
|
||||||
|
// light x y z w r g b
|
||||||
|
"light" => {
|
||||||
|
ensure!(parts.len() == 7, "Light requires 7 params");
|
||||||
|
|
||||||
|
let kind = match parts[3] as usize {
|
||||||
|
0 => LightKind::Directional {
|
||||||
|
direction: read_vec3(0)?,
|
||||||
|
},
|
||||||
|
1 => LightKind::Point {
|
||||||
|
location: read_vec3(0)?,
|
||||||
|
attenuation: None,
|
||||||
|
},
|
||||||
|
_ => bail!("Invalid w; must be either 0 or 1"),
|
||||||
|
};
|
||||||
|
let light = Light {
|
||||||
|
kind,
|
||||||
|
color: read_vec3(4)?,
|
||||||
|
};
|
||||||
|
scene.lights.push(light);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
"eye" => scene.eye_pos = r!(Vector3<f64>),
|
// attlight x y z w r g b c1 c2 c3
|
||||||
"viewdir" => scene.view_dir = r!(Vector3<f64>),
|
"attlight" => {
|
||||||
"updir" => scene.up_dir = r!(Vector3<f64>),
|
ensure!(parts.len() == 10, "Attenuated light requires 10 params");
|
||||||
|
|
||||||
"hfov" => scene.hfov = r!(f64),
|
let kind = match parts[3] as usize {
|
||||||
"bkgcolor" => scene.bkg_color = r!(Color),
|
// TODO: Is this even defined? Pending TA answer
|
||||||
|
0 => LightKind::Directional {
|
||||||
|
direction: read_vec3(0)?,
|
||||||
|
},
|
||||||
|
1 => LightKind::Point {
|
||||||
|
location: read_vec3(0)?,
|
||||||
|
attenuation: Some(Attenuation {
|
||||||
|
c1: parts[7],
|
||||||
|
c2: parts[8],
|
||||||
|
c3: parts[9],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
_ => bail!("Invalid w; must be either 0 or 1"),
|
||||||
|
};
|
||||||
|
let light = Light {
|
||||||
|
kind,
|
||||||
|
color: read_vec3(4)?,
|
||||||
|
};
|
||||||
|
scene.lights.push(light);
|
||||||
|
}
|
||||||
|
|
||||||
// light x y z w r g b
|
// depthcueing dcr dcg dcb amax amin distmax distmin
|
||||||
// attlight x y z w r g b c1 c2 c3
|
"depthcueing" => {
|
||||||
"light" | "attlight" => {
|
ensure!(parts.len() == 7, "Depth cueing requires 7 params");
|
||||||
let vec3 = r!(Vector3<f64>);
|
|
||||||
let w = r!(usize);
|
|
||||||
let color = r!(Color);
|
|
||||||
|
|
||||||
let attenuation = match keyword == "attlight" {
|
let color = read_vec3(0)?;
|
||||||
true => {
|
scene.depth_cueing = DepthCueing {
|
||||||
let c = r!(Vector3<f64>);
|
color,
|
||||||
Some(Attenuation {
|
a_max: parts[3],
|
||||||
c1: c.x,
|
a_min: parts[4],
|
||||||
c2: c.y,
|
dist_max: parts[5],
|
||||||
c3: c.z,
|
dist_min: parts[6],
|
||||||
})
|
};
|
||||||
}
|
}
|
||||||
false => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let kind = match w as usize {
|
// mtlcolor Odr Odg Odb Osr Osg Osb ka kd ks n
|
||||||
0 => LightKind::Directional { direction: vec3 },
|
"mtlcolor" => {
|
||||||
1 => LightKind::Point {
|
ensure!(parts.len() == 10, "Material color requires 10 params");
|
||||||
location: vec3,
|
|
||||||
attenuation,
|
|
||||||
},
|
|
||||||
_ => bail!("Invalid w; must be either 0 or 1"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let light = Light { kind, color };
|
let diffuse_color = read_vec3(0)?;
|
||||||
scene.lights.push(light);
|
let specular_color = read_vec3(3)?;
|
||||||
}
|
|
||||||
|
|
||||||
// depthcueing dcr dcg dcb amax amin distmax distmin
|
let material = Material {
|
||||||
"depthcueing" => {
|
diffuse_color,
|
||||||
let color = r!(Color);
|
specular_color,
|
||||||
let a_max = r!(f64);
|
k_a: parts[6],
|
||||||
let a_min = r!(f64);
|
k_d: parts[7],
|
||||||
let dist_max = r!(f64);
|
k_s: parts[8],
|
||||||
let dist_min = r!(f64);
|
exponent: parts[9],
|
||||||
|
};
|
||||||
|
|
||||||
scene.depth_cueing = DepthCueing {
|
let idx = scene.materials.len();
|
||||||
color,
|
material_color = Some(idx);
|
||||||
a_max,
|
scene.materials.push(material);
|
||||||
a_min,
|
}
|
||||||
dist_max,
|
|
||||||
dist_min,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// mtlcolor Odr Odg Odb Osr Osg Osb ka kd ks n
|
"sphere" => scene.objects.push(Object {
|
||||||
"mtlcolor" => {
|
kind: ObjectKind::Sphere(Sphere {
|
||||||
let diffuse_color = r!(Color);
|
center: read_vec3(0)?,
|
||||||
let specular_color = r!(Color);
|
radius: parts[3],
|
||||||
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),
|
material: match material_color {
|
||||||
texture_idx,
|
Some(v) => v,
|
||||||
});
|
None => {
|
||||||
|
bail!("Each sphere must be preceded by a `mtlcolor` line")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
"cylinder" => scene.objects.push(Object {
|
||||||
|
kind: ObjectKind::Cylinder(Cylinder {
|
||||||
|
center: read_vec3(0)?,
|
||||||
|
direction: read_vec3(3)?,
|
||||||
|
radius: parts[6],
|
||||||
|
length: parts[7],
|
||||||
|
}),
|
||||||
|
material: match material_color {
|
||||||
|
Some(v) => v,
|
||||||
|
None => {
|
||||||
|
bail!("Each sphere must be preceded by a `mtlcolor` line")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
_ => bail!("Unknown keyword {keyword}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)),
|
|
||||||
|
|
||||||
// vt u v
|
|
||||||
"vt" => scene.texture_vertices.push(r!(Vector2<f64>)),
|
|
||||||
|
|
||||||
// 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 texture 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, false);
|
|
||||||
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 = Texture::new(image, true);
|
|
||||||
scene.textures.push(normal_map);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => bail!("Unknown keyword {keyword}"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(scene)
|
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 Vector2<f64> {
|
|
||||||
type Args = ();
|
|
||||||
|
|
||||||
fn construct<'a, I>(it: &mut I, _: Self::Args) -> Result<Self>
|
|
||||||
where
|
|
||||||
I: Iterator<Item = &'a str>,
|
|
||||||
{
|
|
||||||
let (x, y) = match it.next_tuple() {
|
|
||||||
Some(v) => v,
|
|
||||||
None => bail!("Expected 2 values"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let x: f64 = x.parse()?;
|
|
||||||
let y: f64 = y.parse()?;
|
|
||||||
|
|
||||||
Ok(Vector2::new(x, y))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,25 +2,20 @@ pub mod cylinder;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod illumination;
|
pub mod illumination;
|
||||||
pub mod input_file;
|
pub mod input_file;
|
||||||
pub mod object;
|
|
||||||
pub mod sphere;
|
pub mod sphere;
|
||||||
pub mod texture;
|
|
||||||
pub mod triangle;
|
|
||||||
|
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector3;
|
||||||
|
use csci5607_macros::Csci5607Parser;
|
||||||
|
|
||||||
use crate::image::Color;
|
use crate::{image::Color, Point};
|
||||||
use crate::{Point, Point2, Vector};
|
|
||||||
|
|
||||||
use self::data::{Attenuation, DepthCueing, Light, Material};
|
use self::data::{Attenuation, DepthCueing, Light, Material, Object};
|
||||||
use self::object::Object;
|
|
||||||
use self::texture::{Texture};
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, Csci5607Parser)]
|
||||||
pub struct Scene {
|
pub struct Scene {
|
||||||
pub eye_pos: Point,
|
pub eye_pos: Vector3<f64>,
|
||||||
pub view_dir: Vector,
|
pub view_dir: Vector3<f64>,
|
||||||
pub up_dir: Vector,
|
pub up_dir: Vector3<f64>,
|
||||||
|
|
||||||
/// Horizontal field of view (in degrees)
|
/// Horizontal field of view (in degrees)
|
||||||
pub hfov: f64,
|
pub hfov: f64,
|
||||||
|
@ -38,16 +33,5 @@ pub struct Scene {
|
||||||
pub lights: Vec<Light>,
|
pub lights: Vec<Light>,
|
||||||
pub objects: Vec<Object>,
|
pub objects: Vec<Object>,
|
||||||
|
|
||||||
/// List of textures
|
pub vertices: Vec<Point>,
|
||||||
pub textures: Vec<Texture>,
|
|
||||||
|
|
||||||
/// List of normal maps (Extra credit)
|
|
||||||
pub normal_maps: Vec<Texture>,
|
|
||||||
|
|
||||||
/// Coordinates into a texture image
|
|
||||||
pub texture_vertices: Vec<Point2>,
|
|
||||||
|
|
||||||
/// Triangle vertices
|
|
||||||
pub triangle_vertices: Vec<Point>,
|
|
||||||
pub vertex_normals: Vec<Vector>,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +1,9 @@
|
||||||
use std::f64::consts::PI;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
|
|
||||||
use crate::{ray::Ray, utils::min_f64, Point};
|
use crate::{ray::Ray, utils::min_f64, Point};
|
||||||
|
|
||||||
use super::illumination::IntersectionContext;
|
use super::illumination::IntersectionContext;
|
||||||
use super::Scene;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Sphere {
|
pub struct Sphere {
|
||||||
|
@ -21,7 +18,6 @@ impl Sphere {
|
||||||
/// If there is no intersection point, returns None.
|
/// If there is no intersection point, returns None.
|
||||||
pub fn intersects_ray_at(
|
pub fn intersects_ray_at(
|
||||||
&self,
|
&self,
|
||||||
_: &Scene,
|
|
||||||
ray: &Ray,
|
ray: &Ray,
|
||||||
) -> Result<Option<IntersectionContext>> {
|
) -> Result<Option<IntersectionContext>> {
|
||||||
let a = ray.direction.norm();
|
let a = ray.direction.norm();
|
||||||
|
@ -74,21 +70,4 @@ impl Sphere {
|
||||||
normal,
|
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
use crate::{
|
|
||||||
image::{Color, Image},
|
|
||||||
Vector,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Texture {
|
|
||||||
image: Image,
|
|
||||||
is_normal_map: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Texture {
|
|
||||||
pub fn new(image: Image, is_normal_map: bool) -> Self {
|
|
||||||
Self {
|
|
||||||
image,
|
|
||||||
is_normal_map,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pixel_at_exact(&self, x: usize, y: usize) -> Color {
|
|
||||||
// TODO: Debug asserts?
|
|
||||||
|
|
||||||
let x = match x {
|
|
||||||
n if n < self.image.width => n,
|
|
||||||
_ => self.image.width - 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let y = match y {
|
|
||||||
n if n < self.image.height => n,
|
|
||||||
_ => self.image.height - 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.image.data[y * self.image.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.image.width - 1) as f64;
|
|
||||||
let y = v * (self.image.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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,184 +0,0 @@
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
||||||
use nalgebra::{Matrix3, Vector3};
|
use nalgebra::{Matrix3, Vector3};
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
|
|
||||||
use crate::{Vector};
|
use crate::Point;
|
||||||
|
|
||||||
/// Finds the minimum of an iterator of f64s, ignoring any NaN values
|
/// Finds the minimum of an iterator of f64s, ignoring any NaN values
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -30,17 +30,17 @@ where
|
||||||
|
|
||||||
/// Dot-product between two 3D vectors.
|
/// Dot-product between two 3D vectors.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn dot(a: Vector, b: Vector) -> f64 {
|
pub fn dot(a: Point, b: Point) -> f64 {
|
||||||
a.x * b.x + a.y * b.y + a.z * b.z
|
a.x * b.x + a.y * b.y + a.z * b.z
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cross-product between two 3D vectors.
|
/// Cross-product between two 3D vectors.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cross(a: Vector, b: Vector) -> Vector {
|
pub fn cross(a: Point, b: Point) -> Point {
|
||||||
let x = a.y * b.z - a.z * b.y;
|
let x = a.y * b.z - a.z * b.y;
|
||||||
let y = a.z * b.x - a.x * b.z;
|
let y = a.z * b.x - a.x * b.z;
|
||||||
let z = a.x * b.y - a.y * b.x;
|
let z = a.x * b.y - a.y * b.x;
|
||||||
Vector::new(x, y, z)
|
Point::new(x, y, z)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the rotation matrix between the 2 given vectors
|
/// Calculate the rotation matrix between the 2 given vectors
|
||||||
|
|
173
assignment-1c/writeup.md
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
---
|
||||||
|
geometry: margin=2cm
|
||||||
|
output: pdf_document
|
||||||
|
---
|
||||||
|
|
||||||
|
# Raytracer part B
|
||||||
|
|
||||||
|
This project implements a raytracer with Blinn-Phong illumination and shadows
|
||||||
|
implemented. The primary formula that is used by this implementation is:
|
||||||
|
|
||||||
|
\begin{equation}
|
||||||
|
I_{\lambda} =
|
||||||
|
k_a O_{d\lambda} +
|
||||||
|
\sum_{i=1}^{n_\textrm{lights}} \left(
|
||||||
|
f_\textrm{att} \cdot
|
||||||
|
S_i \cdot
|
||||||
|
IL_{i\lambda} \left[
|
||||||
|
k_d O_{d\lambda} \max ( 0, \vec{N} \cdot \vec{L_i} ) +
|
||||||
|
k_s O_{s\lambda} \max ( 0, \vec{N} \cdot \vec{H_i} )^n
|
||||||
|
\right]
|
||||||
|
\right)
|
||||||
|
\end{equation}
|
||||||
|
|
||||||
|
Where:
|
||||||
|
|
||||||
|
- $I_{\lambda}$ is the final illumination of the pixel on an object
|
||||||
|
- $k_a$ is the material's ambient reflectivity
|
||||||
|
- $k_d$ is the material's diffuse reflectivity
|
||||||
|
- $k_s$ is the material's specular reflectivity
|
||||||
|
- $n_\textrm{lights}$ is the number of lights
|
||||||
|
- $f_\textrm{att}$ is the light attenuation factor (1.0 if attenuation is not on)
|
||||||
|
- $S_i$ is the shadow coefficient for light $i$
|
||||||
|
- $IL_{i\lambda}$ is the intensity of light $i$
|
||||||
|
- $O_{d\lambda}$ is the object's diffuse color
|
||||||
|
- $O_{s\lambda}$ is the object's specular color
|
||||||
|
- $\vec{N}$ is the normal vector to the object's surface
|
||||||
|
- $\vec{L_i}$ is the direction from the intersection point to the light $i$
|
||||||
|
- $\vec{H_i}$ is halfway between the direction to the light $i$ and the
|
||||||
|
direction to the viewer
|
||||||
|
- $n$ is the exponent for the specular component
|
||||||
|
|
||||||
|
In this report we will look through how these various factors influence the
|
||||||
|
rendering of the scene. All the images along with their source `.txt` files,
|
||||||
|
rendered `.ppm` files, and converted `.png` files can be found in the `examples`
|
||||||
|
directory of this handin.
|
||||||
|
|
||||||
|
## Varying $k_a$
|
||||||
|
|
||||||
|
$k_a$ is the strength of ambient light. It's used as a coefficient for the
|
||||||
|
object's diffuse color, which keeps a constant value independent of the
|
||||||
|
positions of the object, light, and the viewer. In the image below, I varied
|
||||||
|
$k_a$ between 0.2 and 1. Note how the overall color of the ball increases or
|
||||||
|
decreases in brightness when all other factors remain constant.
|
||||||
|
|
||||||
|
![Varying $k_a$](examples/ka-demo.png){width=360px}
|
||||||
|
\
|
||||||
|
|
||||||
|
## Varying $k_d$
|
||||||
|
|
||||||
|
$k_d$ is the strength of the diffuse component. It also affects an object's
|
||||||
|
diffuse color, but at a strength that's affected by how much of it faces the
|
||||||
|
light. Much like the dark side of the moon, the parts of the object that aren't
|
||||||
|
pointed at the light will not receive as much of the light's influence. In the
|
||||||
|
image below, I varied $k_d$ between 0.2 and 1. Note how the part pointed to the
|
||||||
|
light changes the strength of the brightness as all other factors remain
|
||||||
|
constant.
|
||||||
|
|
||||||
|
![Varying $k_d$](examples/kd-demo.png){width=360px}
|
||||||
|
\
|
||||||
|
|
||||||
|
## Varying $k_s$
|
||||||
|
|
||||||
|
$k_s$ is the specular strength. It uses the object's specular color, which is
|
||||||
|
like its reflective component. When there is a large specular $k_s$, there's a
|
||||||
|
shine that appears on the object with a greater intensity. In the image below, I
|
||||||
|
varied $k_s$ between 0.2 and 1. Note how the whiteness of the light is more
|
||||||
|
reflective in higher $k_s$ values as other factors remain constant.
|
||||||
|
|
||||||
|
![Varying $k_s$](examples/ks-demo.png){width=360px}
|
||||||
|
\
|
||||||
|
|
||||||
|
## Varying $n$
|
||||||
|
|
||||||
|
$n$ is the exponent saying how big the radius of the specular highlight should
|
||||||
|
be. In the equation, increasing the exponent usually leads to smaller shines. In
|
||||||
|
the image below, I varied $n$ between 2 and 100. Note how the size of the shine
|
||||||
|
is the same intensity, but more focused but covers a smaller area as $n$
|
||||||
|
increases.
|
||||||
|
|
||||||
|
![Varying $n$](examples/n-demo.png){width=360px}
|
||||||
|
\
|
||||||
|
|
||||||
|
## Multiple lights
|
||||||
|
|
||||||
|
Multiple lights are handled by multiplying each light against an intensity
|
||||||
|
level, and then added together. Unfortunately, this means that the intensity of
|
||||||
|
each light can't be too bright. We rely on the image to not use lights that are
|
||||||
|
too bright. Because this may result in color values above 1.0, the final value
|
||||||
|
is clamped against 1.0. Below is an example of a scene with two lights; one to
|
||||||
|
the left and one to the right:
|
||||||
|
|
||||||
|
![Multiple lights](examples/multiple-lights-demo.png){width=360px}
|
||||||
|
\
|
||||||
|
|
||||||
|
## Shadows
|
||||||
|
|
||||||
|
Shadows are implemented by pointing a second ray between the intersection point
|
||||||
|
of the original view ray and each light. If the light has something obstructing
|
||||||
|
it in the middle, the light's effect is not used.
|
||||||
|
|
||||||
|
The soft shadow effect is realized by jittering rays across an area. In my
|
||||||
|
implementation, a jitter radius of about 1.0 is used, and 75 rays are shot into
|
||||||
|
uniformly sampled points within that radius. This also has the side effect that
|
||||||
|
rays that are closer to the original ray are sampled more frequently. Each of
|
||||||
|
these rays produces either 0 or 1 depending on if it was obstructed by the
|
||||||
|
object. Taking the proportion of rays that hit as a coefficient for the shadow,
|
||||||
|
we can get some soft shadow effects like this:
|
||||||
|
|
||||||
|
![Soft shadows](examples/soft-shadow-demo.png){width=360px}
|
||||||
|
\
|
||||||
|
|
||||||
|
## Light attenuation
|
||||||
|
|
||||||
|
Light attenuation is when more of the light is applied for objects that are
|
||||||
|
closer to a particular light source. The function that's applied is an inverse
|
||||||
|
quadratic formula with respect to the distance the object is from the light:
|
||||||
|
|
||||||
|
\begin{equation}
|
||||||
|
f_\textrm{att}(d) = \frac{1}{c_1 + c_2 d + c_3 d^2}
|
||||||
|
\end{equation}
|
||||||
|
|
||||||
|
Where:
|
||||||
|
|
||||||
|
- $f_\textrm{att}$ is the attenuation factor
|
||||||
|
- $d$ is the distance the object is from the light
|
||||||
|
- $c_1$, $c_2$, and $c_3$ are user-supplied coefficients
|
||||||
|
|
||||||
|
As you can see below, the effect of the light drops off with the distance from
|
||||||
|
the light (light coming from the left):
|
||||||
|
|
||||||
|
![Light attenuation](examples/attenuation-demo.png){width=360px}
|
||||||
|
\
|
||||||
|
|
||||||
|
## Depth Cueing
|
||||||
|
|
||||||
|
Depth cueing is when the objects further from the viewer have a lower opacity to
|
||||||
|
"fade" into the background in some sense. A good example of this can be seen in
|
||||||
|
the image below; note how the objects are less and less bright the further they
|
||||||
|
are away from the eye.
|
||||||
|
|
||||||
|
![Depth cueing](examples/depth-cueing-demo.png){width=360px}
|
||||||
|
\
|
||||||
|
|
||||||
|
## Shortcomings of the model
|
||||||
|
|
||||||
|
The Phong formula is just a model of how light works, and doesn't actually
|
||||||
|
represent reality. There's not actually rays physically escaping our eyes and
|
||||||
|
hitting objects; it's actually the other way around, but computing it that way
|
||||||
|
would not be efficient since we would be factoring in a lot of rays that don't
|
||||||
|
ever get rendered.
|
||||||
|
|
||||||
|
Also, one needs to take care to use reasonable constants. For example, if using
|
||||||
|
a different specular light color than the diffuse color, then it may produce
|
||||||
|
some bizarre lighting effects that may not actually look right compare to
|
||||||
|
reality.
|
||||||
|
|
||||||
|
# Arbitrary Objects
|
||||||
|
|
||||||
|
Here is an example scene with some objects that demonstrates some of the
|
||||||
|
features of the raytracer.
|
||||||
|
|
||||||
|
![Objects in the scene](examples/objects.png){width=360px}
|
||||||
|
\
|
|
@ -1,2 +0,0 @@
|
||||||
[registries.crates-io]
|
|
||||||
protocol = "sparse"
|
|
11
assignment-1d/.gitignore
vendored
|
@ -1,11 +0,0 @@
|
||||||
/target
|
|
||||||
/assignment-1*
|
|
||||||
/raytracer1*
|
|
||||||
/examples/*.png
|
|
||||||
*.ppm
|
|
||||||
*.zip
|
|
||||||
*.pdf
|
|
||||||
perf.data*
|
|
||||||
flamegraph.svg
|
|
||||||
showcase.png
|
|
||||||
/out.log
|
|
1068
assignment-1d/Cargo.lock
generated
|
@ -1,30 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "assignment-1d"
|
|
||||||
authors = ["Michael Zhang <zhan4854@umn.edu>"]
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
release-handin = ["tracing/release_max_level_info"]
|
|
||||||
|
|
||||||
[[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"] }
|
|
||||||
contracts = "0.6.3"
|
|
||||||
derivative = "2.2.0"
|
|
||||||
either = "1.8.1"
|
|
||||||
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-appender = "0.2.2"
|
|
||||||
tracing-subscriber = { version = "0.3.16", features = ["json"] }
|
|
|
@ -1,53 +0,0 @@
|
||||||
.PHONY: all clean
|
|
||||||
|
|
||||||
.PRECIOUS: $(EXAMPLES_PPM)
|
|
||||||
|
|
||||||
DEBUG :=
|
|
||||||
CARGO_FLAGS := --release
|
|
||||||
RAYTRACER_FLAGS :=
|
|
||||||
DOCKER := docker
|
|
||||||
ZIP := zip
|
|
||||||
PANDOC := pandoc
|
|
||||||
CONVERT := convert
|
|
||||||
|
|
||||||
ifeq ($(DEBUG),1)
|
|
||||||
RAYTRACER_FLAGS += -vvvv
|
|
||||||
else
|
|
||||||
endif
|
|
||||||
|
|
||||||
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 --features 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 $(CARGO_FLAGS) -- -o $@ $(RAYTRACER_FLAGS) $<
|
|
||||||
|
|
||||||
examples/%.png: examples/%.ppm
|
|
||||||
convert $< $@
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf target/docker \
|
|
||||||
$(HANDIN) $(BINARY) \
|
|
||||||
$(EXAMPLES_PPM) $(EXAMPLES_PNG)
|
|
|
@ -1,29 +0,0 @@
|
||||||
# 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
|
@ -1,2 +0,0 @@
|
||||||
# Necessary files
|
|
||||||
!/earthtexture.ppm
|
|
|
@ -1,22 +0,0 @@
|
||||||
eye 0 5 0
|
|
||||||
viewdir 0 0 1
|
|
||||||
updir 0 1 0
|
|
||||||
hfov 45
|
|
||||||
imsize 1080 1080
|
|
||||||
bkgcolor 0.5 0.7 0.9 1
|
|
||||||
light 0 -1 0 0 1 1 1
|
|
||||||
mtlcolor 1 1 1 1 1 1 0.2 0.4 0.6 60 0.2 1.5
|
|
||||||
sphere 1.25 8 15 1
|
|
||||||
sphere 0 6 15 1
|
|
||||||
sphere 1.5 4 15 1
|
|
||||||
mtlcolor 1 1 1 1 1 1 0.2 0.4 0.6 60 1 0
|
|
||||||
sphere -1.5 4 15 1
|
|
||||||
|
|
||||||
mtlcolor 1 1 1 1 1 1 0.2 0.8 0 20 1 0
|
|
||||||
v 10 0 5
|
|
||||||
v -10 0 5
|
|
||||||
v -10 0 25
|
|
||||||
v 10 0 25
|
|
||||||
|
|
||||||
f 1 2 3
|
|
||||||
f 1 3 4
|
|
|
@ -1,12 +0,0 @@
|
||||||
eye 0 0 0
|
|
||||||
viewdir 1 0 0
|
|
||||||
updir 0 0 1
|
|
||||||
hfov 60
|
|
||||||
imsize 1080 1080
|
|
||||||
bkgcolor 0.5 0.7 0.9 1
|
|
||||||
light 0 94820000 -28450000 1 0 0 0
|
|
||||||
mtlcolor 1 1 1 1 1 1 0 0.05 0.1 80 1 30
|
|
||||||
sphere 3 0 0 1
|
|
||||||
mtlcolor 0 1 0 1 1 1 1 0 0 1 1 0
|
|
||||||
texture harbor.ppm
|
|
||||||
sphere 0 0 0 100000000
|
|
|
@ -1,18 +0,0 @@
|
||||||
eye 0 5 0
|
|
||||||
viewdir 0 0 1
|
|
||||||
updir 0 1 0
|
|
||||||
hfov 45
|
|
||||||
imsize 1080 1080
|
|
||||||
bkgcolor 0.5 0.7 0.9 1
|
|
||||||
light 0 -1 0 0 1 1 1
|
|
||||||
mtlcolor 1 1 1 1 1 1 0.2 0.4 0.6 60 1 0
|
|
||||||
sphere -1.5 4 15 1
|
|
||||||
|
|
||||||
mtlcolor 1 1 1 1 1 1 0.2 0.8 0 20 1 0
|
|
||||||
v 10 0 5
|
|
||||||
v -10 0 5
|
|
||||||
v -10 0 25
|
|
||||||
v 10 0 25
|
|
||||||
|
|
||||||
f 1 2 3
|
|
||||||
f 1 3 4
|
|
|
@ -1,18 +0,0 @@
|
||||||
eye 0 5 0
|
|
||||||
viewdir 0 0 1
|
|
||||||
updir 0 1 0
|
|
||||||
hfov 45
|
|
||||||
imsize 128 128
|
|
||||||
bkgcolor 0.5 0.7 0.9 1
|
|
||||||
light 0 -1 0 0 1 1 1
|
|
||||||
mtlcolor 1 1 1 1 1 1 0.2 0.4 0.6 60 0.2 1.5
|
|
||||||
sphere 0 6 15 3
|
|
||||||
|
|
||||||
mtlcolor 1 1 1 1 1 1 0.2 0.8 0 20 1 0
|
|
||||||
v 10 0 5
|
|
||||||
v -10 0 5
|
|
||||||
v -10 0 25
|
|
||||||
v 10 0 25
|
|
||||||
|
|
||||||
f 1 2 3
|
|
||||||
f 1 3 4
|
|
|
@ -1,45 +0,0 @@
|
||||||
imsize 640 480
|
|
||||||
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 0.5 1
|
|
||||||
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 0.8 1
|
|
||||||
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
|
|
|
@ -1,29 +0,0 @@
|
||||||
imsize 1366 768
|
|
||||||
eye 0 5 -2
|
|
||||||
viewdir 0 -0.2 1
|
|
||||||
hfov 60
|
|
||||||
updir 0 1 0
|
|
||||||
bkgcolor 0.4 0.5 0.6
|
|
||||||
depthcueing 0.5 0.5 0.5 1 0.4 60 0
|
|
||||||
light 0 -1 0 0 1 1 1
|
|
||||||
|
|
||||||
mtlcolor 1 0.6 0.6 1 1 1 0.2 0.4 0.6 60 0.9 2
|
|
||||||
sphere -1.5 4 15 1
|
|
||||||
|
|
||||||
mtlcolor 0.6 0.6 1 1 1 1 0.2 0.4 0.6 60 0.9 2
|
|
||||||
sphere 0 -1 12 2
|
|
||||||
|
|
||||||
mtlcolor 0.6 1 0.6 1 1 1 0.2 0.4 0.6 60 1 2
|
|
||||||
sphere 6 8 20 3
|
|
||||||
|
|
||||||
mtlcolor 1 1 0.6 1 1 1 0.2 0.4 0.6 60 0.9 2
|
|
||||||
sphere -6 -8 20 4
|
|
||||||
|
|
||||||
mtlcolor 0.7 0.6 0.8 0.5 0.5 0.5 0.2 0.8 0.1 20 0.5 1.5
|
|
||||||
v 10 0 5
|
|
||||||
v -10 0 5
|
|
||||||
v -10 0 25
|
|
||||||
v 10 0 25
|
|
||||||
|
|
||||||
f 1 2 3
|
|
||||||
f 1 3 4
|
|
|
@ -1,11 +0,0 @@
|
||||||
eye 0 0 10
|
|
||||||
viewdir 0 0 -1
|
|
||||||
updir 0 1 0
|
|
||||||
hfov 60
|
|
||||||
imsize 512 512
|
|
||||||
bkgcolor 0.5 0.7 0.9 1
|
|
||||||
light 0 -1 0 0 1 1 1
|
|
||||||
|
|
||||||
mtlcolor 0.7 0.2 0.7 1 1 1 0 0.05 0.1 80 0.5 1.5
|
|
||||||
sphere 1 1 -6 3
|
|
||||||
sphere -1 -1 1 3
|
|
|
@ -1,127 +0,0 @@
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
use nalgebra::{Vector2, Vector3};
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate anyhow;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate contracts;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate derivative;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate tracing;
|
|
||||||
|
|
||||||
pub mod image;
|
|
||||||
pub mod ray;
|
|
||||||
pub mod scene;
|
|
||||||
pub mod utils;
|
|
||||||
|
|
||||||
// Creating a bunch of aliases here to make it more obvious which one I'm
|
|
||||||
// expecting a variable to be
|
|
||||||
|
|
||||||
pub type Point2 = Vector2<f64>;
|
|
||||||
pub type Point = Vector3<f64>;
|
|
||||||
pub type Vector = Vector3<f64>;
|
|
|
@ -1,204 +0,0 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate anyhow;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate tracing;
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::{fs::File, str::FromStr};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use assignment_1d::{image::Image, ray::Ray, scene::Scene};
|
|
||||||
|
|
||||||
use clap::{ArgAction, Parser};
|
|
||||||
|
|
||||||
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
|
|
||||||
use tracing::metadata::LevelFilter;
|
|
||||||
use tracing_appender::non_blocking::WorkerGuard;
|
|
||||||
use tracing_subscriber::{
|
|
||||||
fmt::Layer, prelude::__tracing_subscriber_SubscriberExt,
|
|
||||||
util::SubscriberInitExt,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// 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>,
|
|
||||||
|
|
||||||
/// Log output in json
|
|
||||||
#[clap(long = "json")]
|
|
||||||
use_json: bool,
|
|
||||||
|
|
||||||
/// Which file to send logs to (stderr by default)
|
|
||||||
#[clap(long = "log-output")]
|
|
||||||
log_output: 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,
|
|
||||||
|
|
||||||
/// Verbosity
|
|
||||||
#[clap(short, long, action = ArgAction::Count)]
|
|
||||||
verbosity: u8,
|
|
||||||
|
|
||||||
/// Evaluate at a single pixel
|
|
||||||
#[clap(short, long = "render-pixel")]
|
|
||||||
render_pixel: Option<RenderPixel>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let opt = Opt::parse();
|
|
||||||
|
|
||||||
let _guard = setup_logging(&opt);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
let evaluate_at_pixel = |px, py| {
|
|
||||||
let span = trace_span!("main_loop", px = px, py = py);
|
|
||||||
let _enter = span.enter();
|
|
||||||
|
|
||||||
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 res= rayon::spawn(|| scene.trace_single_ray(ray, 0));
|
|
||||||
scene.trace_single_ray(scene.eye_pos, ray, 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
// For debugging purposes!
|
|
||||||
if let Some(RenderPixel(px, py)) = opt.render_pixel {
|
|
||||||
let pixel_color = evaluate_at_pixel(px, py)?;
|
|
||||||
println!("Pixel color: {pixel_color}");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)| evaluate_at_pixel(px, py))
|
|
||||||
.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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct RenderPixel(usize, usize);
|
|
||||||
|
|
||||||
impl FromStr for RenderPixel {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let parts = s.split(",").collect::<Vec<_>>();
|
|
||||||
ensure!(parts.len() == 2, "must be a pair");
|
|
||||||
|
|
||||||
let x = parts[0].parse::<usize>()?;
|
|
||||||
let y = parts[1].parse::<usize>()?;
|
|
||||||
|
|
||||||
Ok(RenderPixel(x, y))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A little bit of engineering to make it easy to write conditional builders
|
|
||||||
/// for logging setup because the tracing-subscriber crate for some reason
|
|
||||||
/// decided it would be a good idea to have all of its builders be polymorphic?
|
|
||||||
macro_rules! logsetup_if {
|
|
||||||
($ident:ident , $cond:expr , $iftrue:expr , $iffalse:expr => { $($body:tt)* }) => {
|
|
||||||
if ($cond) {
|
|
||||||
let $ident = $iftrue;
|
|
||||||
$($body)*
|
|
||||||
} else {
|
|
||||||
let $ident = $iffalse;
|
|
||||||
$($body)*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_logging(opt: &Opt) -> Option<WorkerGuard> {
|
|
||||||
let mut result = None;
|
|
||||||
|
|
||||||
let level_filter = match opt.verbosity {
|
|
||||||
0 => LevelFilter::ERROR,
|
|
||||||
1 => LevelFilter::WARN,
|
|
||||||
2 => LevelFilter::INFO,
|
|
||||||
3 => LevelFilter::DEBUG,
|
|
||||||
_ => LevelFilter::TRACE,
|
|
||||||
};
|
|
||||||
|
|
||||||
let layer = Layer::default();
|
|
||||||
|
|
||||||
logsetup_if! (layer, opt.use_json, layer.json(), layer => {
|
|
||||||
let layer = layer
|
|
||||||
.with_target(false)
|
|
||||||
.with_timer(tracing_subscriber::fmt::time::uptime())
|
|
||||||
.with_level(true);
|
|
||||||
|
|
||||||
logsetup_if! (layer, opt.log_output.is_some(), {
|
|
||||||
let log_output = opt.log_output.clone().unwrap();
|
|
||||||
let file_appender = tracing_appender::rolling::never(".", log_output);
|
|
||||||
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
|
|
||||||
result = Some(guard);
|
|
||||||
layer.with_writer(non_blocking)
|
|
||||||
}, layer => {
|
|
||||||
tracing_subscriber::registry()
|
|
||||||
.with(layer)
|
|
||||||
.with(level_filter)
|
|
||||||
.init();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
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. This is pretty much a (time -> point) function.
|
|
||||||
pub struct Ray {
|
|
||||||
/// The point in space where the ray started
|
|
||||||
pub origin: Point,
|
|
||||||
|
|
||||||
/// The direction the ray is headed
|
|
||||||
pub direction: Vector,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Ray {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"({:.2}, {:.2}, {:.2}) + t * ({:.2}, {:.2}, {:.2})",
|
|
||||||
self.origin.x,
|
|
||||||
self.origin.y,
|
|
||||||
self.origin.z,
|
|
||||||
self.direction.x,
|
|
||||||
self.direction.y,
|
|
||||||
self.direction.z,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ray {
|
|
||||||
pub fn new(origin: Point, direction: Vector) -> Self {
|
|
||||||
Ray { origin, direction }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if any of the components is NaN
|
|
||||||
pub fn has_nan(&self) -> bool {
|
|
||||||
self.origin.x.is_nan()
|
|
||||||
|| self.origin.y.is_nan()
|
|
||||||
|| self.origin.z.is_nan()
|
|
||||||
|| self.direction.x.is_nan()
|
|
||||||
|| self.direction.y.is_nan()
|
|
||||||
|| self.direction.z.is_nan()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,208 +0,0 @@
|
||||||
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,
|
|
||||||
exiting: todo!(),
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
exiting: todo!(),
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
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:?}");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,193 +0,0 @@
|
||||||
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,
|
|
||||||
|
|
||||||
/// Opacity
|
|
||||||
pub alpha: f64,
|
|
||||||
|
|
||||||
/// Index of refraction
|
|
||||||
pub eta: 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,519 +0,0 @@
|
||||||
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::{
|
|
||||||
compute_reflection_ray, compute_refraction_lengths, dot, RefractionResult,
|
|
||||||
},
|
|
||||||
Point, Vector,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
data::{DepthCueing, Light, LightKind, Material},
|
|
||||||
object::Object,
|
|
||||||
Scene,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Is this a good constant?
|
|
||||||
const JITTER_CONST: f64 = 0.05;
|
|
||||||
const ZERO_COLOR: Color = Color::new(0.0, 0.0, 0.0);
|
|
||||||
|
|
||||||
// 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 SOFT_SHADOW_JITTER_RADIUS: f64 = 1.0;
|
|
||||||
const JITTER_RAYS: usize = 75;
|
|
||||||
|
|
||||||
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,
|
|
||||||
origin: Point,
|
|
||||||
incident_ray: Ray,
|
|
||||||
intersection_context: IntersectionContext,
|
|
||||||
depth: usize,
|
|
||||||
) -> Result<Color> {
|
|
||||||
let span = trace_span!("compute_pixel_color", intersection = ?intersection_context, incident_ray=?incident_ray);
|
|
||||||
let _enter = span.enter();
|
|
||||||
|
|
||||||
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(); // reflection_normal();
|
|
||||||
|
|
||||||
// Viewer direction is no longer towards the eye, but to the last origin point, so that
|
|
||||||
// transmitted rays reflect properly
|
|
||||||
// let viewer_direction = self.eye_pos - intersection_context.point;
|
|
||||||
let incoming_ray_direction = (intersection_context.point - origin).normalize();
|
|
||||||
|
|
||||||
let halfway_direction =
|
|
||||||
((light_direction + incoming_ray_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 (eta_i, eta_t) = match intersection_context.exiting {
|
|
||||||
// true => (material.eta, 1.0),
|
|
||||||
_ => (1.0, material.eta),
|
|
||||||
};
|
|
||||||
|
|
||||||
let specular_reflection_component = if material.k_s == 0.0 {
|
|
||||||
ZERO_COLOR
|
|
||||||
} else {
|
|
||||||
self.compute_specular_reflection(
|
|
||||||
&intersection_context,
|
|
||||||
&incident_ray,
|
|
||||||
depth,
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let transparency_component = if eta_t < 1.0 || material.alpha == 1.0 {
|
|
||||||
ZERO_COLOR
|
|
||||||
} else {
|
|
||||||
self.compute_transparency(
|
|
||||||
&intersection_context,
|
|
||||||
&incident_ray,
|
|
||||||
eta_i,
|
|
||||||
eta_t,
|
|
||||||
depth,
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let fresnel_coefficient = self.compute_fresnel_coefficient(
|
|
||||||
material,
|
|
||||||
&incident_ray.direction,
|
|
||||||
intersection_context.normal,
|
|
||||||
);
|
|
||||||
|
|
||||||
// This is the result of the Phong illumination equation.
|
|
||||||
let color = (ambient_component + diffuse_and_specular) + {
|
|
||||||
// This part is all the transparency + reflection stuff
|
|
||||||
fresnel_coefficient * specular_reflection_component
|
|
||||||
+ (1.0 - fresnel_coefficient)
|
|
||||||
* (1.0 - material.alpha)
|
|
||||||
* transparency_component
|
|
||||||
};
|
|
||||||
debug!(
|
|
||||||
last_time_component = ?(ambient_component + diffuse_and_specular),
|
|
||||||
?specular_reflection_component,
|
|
||||||
?transparency_component,
|
|
||||||
?fresnel_coefficient,
|
|
||||||
?color,
|
|
||||||
"color result"
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct ShadowResult {
|
|
||||||
transparent_coefficient: f64,
|
|
||||||
shadow_opacity: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
let material = &self.materials[object.material_idx];
|
|
||||||
|
|
||||||
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 {
|
|
||||||
Some(ShadowResult {
|
|
||||||
transparent_coefficient: material.alpha,
|
|
||||||
shadow_opacity: self
|
|
||||||
.compute_soft_shadow_coefficient(location, point, object),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the case of directional lights, only t > 0 needs to be checked
|
|
||||||
LightKind::Directional { .. } => {
|
|
||||||
if intersection_time <= 0.0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
// The object obstructed the directional light, which means (1 -
|
|
||||||
// alpha) amount of light passes through
|
|
||||||
Some(ShadowResult {
|
|
||||||
transparent_coefficient: material.alpha,
|
|
||||||
|
|
||||||
// Opacity is 0 because there's no jitter from an infinitely far
|
|
||||||
// away light source
|
|
||||||
shadow_opacity: 0.0,
|
|
||||||
}) // complete obstruction
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
match intersections.is_empty() {
|
|
||||||
true => 1.0,
|
|
||||||
false => {
|
|
||||||
// let average =
|
|
||||||
// intersections.iter().map(|s| s.shadow_opacity).sum::<f64>()
|
|
||||||
// / intersections.len() as f64;
|
|
||||||
|
|
||||||
// (1 - a_0) * (1 - a_1) * (...)
|
|
||||||
let transparency = intersections
|
|
||||||
.iter()
|
|
||||||
.map(|s| 1.0 - s.transparent_coefficient)
|
|
||||||
.product::<f64>();
|
|
||||||
|
|
||||||
// debug!(
|
|
||||||
// "average {average}, transparency {transparency} = {}",
|
|
||||||
// average * transparency
|
|
||||||
// );
|
|
||||||
transparency
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_soft_shadow_coefficient(
|
|
||||||
&self,
|
|
||||||
light_location: Point,
|
|
||||||
original_intersection_point: Point,
|
|
||||||
object: &Object,
|
|
||||||
) -> f64 {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
let locations = iter::repeat_with(|| {
|
|
||||||
let x = rng.gen_range(0.0..SOFT_SHADOW_JITTER_RADIUS);
|
|
||||||
let y = rng.gen_range(0.0..SOFT_SHADOW_JITTER_RADIUS);
|
|
||||||
let z = rng.gen_range(0.0..SOFT_SHADOW_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
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_fresnel_coefficient(
|
|
||||||
&self,
|
|
||||||
material: &Material,
|
|
||||||
incident_ray: &Vector,
|
|
||||||
normal: Vector,
|
|
||||||
) -> f64 {
|
|
||||||
let mut cos_theta_i = dot(*incident_ray, normal);
|
|
||||||
|
|
||||||
if cos_theta_i < 0.0 {
|
|
||||||
cos_theta_i = dot(*incident_ray, -normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
let f0 = ((material.eta - 1.0) / (material.eta + 1.0)).powi(2);
|
|
||||||
let fr = f0 * 1.0 + (1.0 - f0) * (1.0 - cos_theta_i).powi(5);
|
|
||||||
|
|
||||||
if fr < 0.0 || fr > 1.0 {
|
|
||||||
warn!(
|
|
||||||
eta = material.eta,
|
|
||||||
cos_theta_i, f0, fr, "fresnel coefficient outside of 0 - 1"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fr
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_specular_reflection(
|
|
||||||
&self,
|
|
||||||
intersection_context: &IntersectionContext,
|
|
||||||
incident_ray: &Ray,
|
|
||||||
depth: usize,
|
|
||||||
) -> Result<Color> {
|
|
||||||
let reflection_ray = compute_reflection_ray(
|
|
||||||
incident_ray.direction.clone(),
|
|
||||||
intersection_context.reflection_normal().normalize(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let origin = intersection_context.point;
|
|
||||||
let origin = origin + JITTER_CONST * reflection_ray;
|
|
||||||
let ray = Ray::new(origin, reflection_ray);
|
|
||||||
|
|
||||||
self.trace_single_ray(origin, ray, depth + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_transparency(
|
|
||||||
&self,
|
|
||||||
intersection_context: &IntersectionContext,
|
|
||||||
incident_ray: &Ray,
|
|
||||||
eta_i: f64,
|
|
||||||
eta_t: f64,
|
|
||||||
depth: usize,
|
|
||||||
) -> Result<Color> {
|
|
||||||
let span =
|
|
||||||
trace_span!("compute_transparency", eta_i = eta_i, eta_t = eta_t);
|
|
||||||
let _enter = span.enter();
|
|
||||||
|
|
||||||
// Fix the normal direction to account for exiting a material
|
|
||||||
let normal = intersection_context.reflection_normal().normalize();
|
|
||||||
|
|
||||||
let i = incident_ray.direction.normalize();
|
|
||||||
|
|
||||||
assert!(eta_t != 0.0, "wtf eta_t is 0");
|
|
||||||
|
|
||||||
// This comes in two parts: one is reflection and one is refraction. The
|
|
||||||
// refraction component will only occur if the angle remains below the
|
|
||||||
// critical angle. The reflection amount is added in proportion to the
|
|
||||||
// Fresnel coefficient.
|
|
||||||
|
|
||||||
// First, calculate whether or not refraction is happening. If total
|
|
||||||
// internal reflection occurs, then there's no refraction since there's
|
|
||||||
// no ray escaping the medium.
|
|
||||||
|
|
||||||
let value =
|
|
||||||
match compute_refraction_lengths(normal, &incident_ray, eta_i, eta_t) {
|
|
||||||
Some(RefractionResult {
|
|
||||||
cos_theta_i,
|
|
||||||
sin_theta_i: _,
|
|
||||||
sin_theta_t,
|
|
||||||
cos_theta_t,
|
|
||||||
}) => {
|
|
||||||
// Now that we identified that there is refraction happening, transmit
|
|
||||||
// a ray through the material at the scene behind it in the
|
|
||||||
// new direction.
|
|
||||||
|
|
||||||
// Calculate refraction direction
|
|
||||||
let a = normal * cos_theta_t;
|
|
||||||
let s_direction = cos_theta_i * normal - i;
|
|
||||||
let m_unit = s_direction.normalize();
|
|
||||||
let b = m_unit * sin_theta_t;
|
|
||||||
let t = a + b;
|
|
||||||
|
|
||||||
// Jitter a bit to reduce acne
|
|
||||||
// TODO: Is this a good constant?
|
|
||||||
let origin = intersection_context.point;
|
|
||||||
let origin = origin + JITTER_CONST * t;
|
|
||||||
let ray = Ray::new(origin, t);
|
|
||||||
|
|
||||||
self.trace_single_ray(origin, ray, depth + 1)?
|
|
||||||
}
|
|
||||||
|
|
||||||
// No extra color from the transmitted ray, since it's completely
|
|
||||||
// reflected
|
|
||||||
None => ZERO_COLOR,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate reflection
|
|
||||||
// let sin_theta_t = (eta_i / eta_t) * sin_theta_i;
|
|
||||||
// let cos_theta_t = (1.0 - sin_theta_t.powi(2)).sqrt();
|
|
||||||
// let fresnel_coefficient = self.compute_fresnel_coefficient(&material, i,
|
|
||||||
// n);
|
|
||||||
|
|
||||||
Ok(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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,
|
|
||||||
|
|
||||||
/// Is this ray exiting the material at the intersection point?
|
|
||||||
pub exiting: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for IntersectionContext {}
|
|
||||||
|
|
||||||
impl IntersectionContext {
|
|
||||||
// If we're exiting the material, the normal should face the other direction
|
|
||||||
// since that's how the reflection works
|
|
||||||
pub fn reflection_normal(&self) -> Vector {
|
|
||||||
match self.exiting {
|
|
||||||
true => -self.normal,
|
|
||||||
false => self.normal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,371 +0,0 @@
|
||||||
pub mod triangle_vertex;
|
|
||||||
|
|
||||||
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},
|
|
||||||
input_file::triangle_vertex::TriangleVertex,
|
|
||||||
object::{Object, ObjectKind},
|
|
||||||
sphere::Sphere,
|
|
||||||
texture::{NormalMap, Texture},
|
|
||||||
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() {
|
|
||||||
// Comments :)
|
|
||||||
let line = line.trim();
|
|
||||||
if line.starts_with("#") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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), file!(), line!()))?
|
|
||||||
};
|
|
||||||
|
|
||||||
($ty:ty, $($ex:expr),* $(,)?) => {
|
|
||||||
<$ty>::construct(&mut parts, $($ex,)*)
|
|
||||||
.with_context(|| format!("Could not parse {} ({}:{})", stringify!($ty), file!(), line!()))?
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 alpha eta
|
|
||||||
"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 alpha = r!(f64);
|
|
||||||
let eta = r!(f64);
|
|
||||||
|
|
||||||
let material = Material {
|
|
||||||
diffuse_color,
|
|
||||||
specular_color,
|
|
||||||
k_a,
|
|
||||||
k_d,
|
|
||||||
k_s,
|
|
||||||
exponent,
|
|
||||||
alpha,
|
|
||||||
eta,
|
|
||||||
};
|
|
||||||
|
|
||||||
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 for {} ({}:{})",
|
|
||||||
stringify!($ty),
|
|
||||||
file!(),
|
|
||||||
line!()
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
use super::Construct;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub struct TriangleVertex {
|
|
||||||
pub vertex_idx: usize,
|
|
||||||
pub normal_idx: Option<usize>,
|
|
||||||
pub 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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
pub mod cylinder;
|
|
||||||
pub mod data;
|
|
||||||
pub mod illumination;
|
|
||||||
pub mod input_file;
|
|
||||||
pub mod object;
|
|
||||||
pub mod sphere;
|
|
||||||
pub mod texture;
|
|
||||||
pub mod tracing;
|
|
||||||
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::{NormalMap, Texture};
|
|
||||||
|
|
||||||
#[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>,
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
if discriminant.is_nan() {
|
|
||||||
warn!("WTF NAN");
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
let exiting = {
|
|
||||||
// To figure out if we're exiting, just test if the origin is inside the
|
|
||||||
// sphere
|
|
||||||
|
|
||||||
let dx = ray.origin.x - self.center.x;
|
|
||||||
let dy = ray.origin.y - self.center.y;
|
|
||||||
let dz = ray.origin.z - self.center.z;
|
|
||||||
|
|
||||||
dx.powi(2) + dy.powi(2) + dz.powi(2) <= self.radius.powi(2)
|
|
||||||
};
|
|
||||||
|
|
||||||
/* let normal = match exiting {
|
|
||||||
true => -normal,
|
|
||||||
false => normal,
|
|
||||||
}; */
|
|
||||||
|
|
||||||
Ok(Some(IntersectionContext {
|
|
||||||
time,
|
|
||||||
point,
|
|
||||||
normal,
|
|
||||||
exiting,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
use crate::{image::Color, ray::Ray, Point};
|
|
||||||
|
|
||||||
use super::Scene;
|
|
||||||
|
|
||||||
const MAX_RECURSION_DEPTH: usize = 10_usize;
|
|
||||||
|
|
||||||
impl Scene {
|
|
||||||
pub fn trace_single_ray(
|
|
||||||
&self,
|
|
||||||
origin: Point,
|
|
||||||
ray: Ray,
|
|
||||||
depth: usize,
|
|
||||||
) -> Result<Color> {
|
|
||||||
if depth > MAX_RECURSION_DEPTH {
|
|
||||||
return Ok(Color::new(0.0, 0.0, 0.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
let span = trace_span!("trace_ray", ray = ?ray, depth = depth);
|
|
||||||
let _enter = span.enter();
|
|
||||||
|
|
||||||
let intersections = self
|
|
||||||
.objects
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(i, object)| {
|
|
||||||
match object.kind.intersects_ray_at(&self, &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);
|
|
||||||
|
|
||||||
info!("Ray {ray:?} intersected at: {earliest_intersection:?}");
|
|
||||||
|
|
||||||
Ok(match earliest_intersection {
|
|
||||||
// Take the object's material color
|
|
||||||
Some((obj_idx, intersection_context, object)) => self
|
|
||||||
.compute_pixel_color(
|
|
||||||
obj_idx,
|
|
||||||
object,
|
|
||||||
origin,
|
|
||||||
ray,
|
|
||||||
intersection_context,
|
|
||||||
depth,
|
|
||||||
)?,
|
|
||||||
|
|
||||||
// There was no intersection, so this should default to the scene's
|
|
||||||
// background color
|
|
||||||
None => self.bkg_color,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,190 +0,0 @@
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
// Intersected the plane behind where the ray started
|
|
||||||
if time < 0.0 {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
exiting: false,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,160 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use nalgebra::{Matrix3, Vector3};
|
|
||||||
use ordered_float::NotNan;
|
|
||||||
|
|
||||||
use crate::{ray::Ray, 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RefractionResult {
|
|
||||||
pub cos_theta_i: f64,
|
|
||||||
pub sin_theta_i: f64,
|
|
||||||
pub sin_theta_t: f64,
|
|
||||||
pub cos_theta_t: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function computes the 4 values:
|
|
||||||
///
|
|
||||||
/// - cos_theta_i
|
|
||||||
/// - sin_theta_i
|
|
||||||
/// - sin_theta_t
|
|
||||||
/// - cos_theta_t
|
|
||||||
///
|
|
||||||
/// If total internal reflection occurs, return None instead.
|
|
||||||
pub fn compute_refraction_lengths(
|
|
||||||
normal: Vector,
|
|
||||||
incident_ray: &Ray,
|
|
||||||
eta_i: f64,
|
|
||||||
eta_t: f64,
|
|
||||||
) -> Option<RefractionResult> {
|
|
||||||
let i = incident_ray.direction.normalize();
|
|
||||||
let cos_theta_i = dot(i, normal);
|
|
||||||
let sin_theta_i = (1.0 - cos_theta_i.powi(2)).sqrt();
|
|
||||||
|
|
||||||
if sin_theta_i * eta_i > eta_t {
|
|
||||||
info!("Total internal reflection encountered.");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sin_theta_t = (eta_i / eta_t) * sin_theta_i;
|
|
||||||
let cos_theta_t = (1.0 - sin_theta_t.powi(2)).sqrt();
|
|
||||||
|
|
||||||
Some(RefractionResult {
|
|
||||||
cos_theta_i,
|
|
||||||
sin_theta_i,
|
|
||||||
sin_theta_t,
|
|
||||||
cos_theta_t,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub fn compute_reflection_ray(incident_ray: Vector, normal: Vector) -> Vector {
|
|
||||||
let I = (-incident_ray).normalize();
|
|
||||||
let N = normal.normalize();
|
|
||||||
|
|
||||||
2.0 * dot(N, I) * N - I
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::{utils::compute_reflection_ray, Vector};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reflection_ray() {
|
|
||||||
let incident_ray = Vector::new(2.0, -1.0, 2.0);
|
|
||||||
let normal = Vector::new(0.0, 1.0, 0.0);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
compute_reflection_ray(incident_ray, normal),
|
|
||||||
Vector::new(2.0, 1.0, 2.0).normalize()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
BasedOnStyle: LLVM
|
|
||||||
|
|
||||||
AlignAfterOpenBracket: BlockIndent
|
|
5
assignment-2a/.gitignore
vendored
|
@ -1,5 +0,0 @@
|
||||||
/build
|
|
||||||
/result*
|
|
||||||
.cache
|
|
||||||
|
|
||||||
hw2a.michael.zhang.zip
|
|
|
@ -1,79 +0,0 @@
|
||||||
# Set the minimum required version of cmake for this project
|
|
||||||
cmake_minimum_required (VERSION 3.1)
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
|
|
||||||
# Create a project called 'HW2a'
|
|
||||||
project(HW2a)
|
|
||||||
|
|
||||||
# Define in the C++ code what the variable "SRC_DIR" should be equal to the current_path/src
|
|
||||||
add_definitions( -DSRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src" )
|
|
||||||
|
|
||||||
# Generate the `compile_commands.json` file.
|
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "")
|
|
||||||
|
|
||||||
if(CMAKE_EXPORT_COMPILE_COMMANDS)
|
|
||||||
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
|
|
||||||
${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Find OpenGL, and set link library names and include paths
|
|
||||||
find_package(OpenGL REQUIRED)
|
|
||||||
set(OPENGL_LIBRARIES ${OPENGL_gl_LIBRARY} ${OPENGL_glu_LIBRARY})
|
|
||||||
set(OPENGL_INCLUDE_DIRS ${OPENGL_INCLUDE_DIR})
|
|
||||||
include_directories(${OPENGL_INCLUDE_DIRS})
|
|
||||||
|
|
||||||
# Also disable building some of the extra things GLFW has (examples, tests, docs)
|
|
||||||
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL " " FORCE)
|
|
||||||
set(GLFW_BUILD_TESTS OFF CACHE BOOL " " FORCE)
|
|
||||||
set(GLFW_BUILD_DOCS OFF CACHE BOOL " " FORCE)
|
|
||||||
|
|
||||||
# Now actually run cmake on the CMakeLists.txt file found inside of the GLFW directory
|
|
||||||
add_subdirectory(ext/glfw)
|
|
||||||
|
|
||||||
# Make a list of all the source files
|
|
||||||
set(
|
|
||||||
SOURCES
|
|
||||||
src/HW2a.cpp
|
|
||||||
|
|
||||||
ext/glad/src/glad.c
|
|
||||||
)
|
|
||||||
|
|
||||||
# Make a list of all the header files (optional-- only necessary to make them appear in IDE)
|
|
||||||
set(
|
|
||||||
INCLUDES
|
|
||||||
src/ShaderStuff.hpp
|
|
||||||
)
|
|
||||||
|
|
||||||
# Make a list of all of the directories to look in when doing #include "whatever.h"
|
|
||||||
set(
|
|
||||||
INCLUDE_DIRS
|
|
||||||
ext/
|
|
||||||
ext/glfw/include
|
|
||||||
ext/glad/include
|
|
||||||
)
|
|
||||||
|
|
||||||
set(
|
|
||||||
LIBS
|
|
||||||
glfw
|
|
||||||
${OPENGL_LIBRARIES}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Define what we are trying to produce here (an executable), as
|
|
||||||
# well as what items are needed to create it (the header and source files)
|
|
||||||
add_executable(${PROJECT_NAME} ${SOURCES} ${INCLUDES})
|
|
||||||
|
|
||||||
# Tell cmake which directories to look in when you #include a file
|
|
||||||
# Equivalent to the "-I" option for g++
|
|
||||||
include_directories(${INCLUDE_DIRS})
|
|
||||||
|
|
||||||
# Tell cmake which libraries to link to
|
|
||||||
# Equivalent to the "-l" option for g++
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBS})
|
|
||||||
|
|
||||||
# For Visual Studio only
|
|
||||||
if (MSVC)
|
|
||||||
# Do a parallel compilation of this project
|
|
||||||
target_compile_options(${PROJECT_NAME} PRIVATE "/MP")
|
|
||||||
# Have this project be the default startup project (the one to build/run when hitting F5)
|
|
||||||
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
|
|
||||||
endif()
|
|
|
@ -1,14 +0,0 @@
|
||||||
.PHONY: all clean
|
|
||||||
|
|
||||||
ZIP := zip
|
|
||||||
|
|
||||||
HANDIN := hw2a.michael.zhang.zip
|
|
||||||
SOURCES := $(shell find -name "*.cpp")
|
|
||||||
|
|
||||||
all: $(HANDIN)
|
|
||||||
|
|
||||||
$(HANDIN): src/HW2a.cpp examples README.md
|
|
||||||
$(ZIP) -r $@ $^
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(HANDIN)
|
|
|
@ -1,13 +0,0 @@
|
||||||
# Assignment 2A
|
|
||||||
|
|
||||||
Compiles but does not run on CSE labs machines due to OpenGL 3.2 missing.
|
|
||||||
|
|
||||||
Try with `examples/test.obj`, run the program with
|
|
||||||
|
|
||||||
cmake -B build
|
|
||||||
make -C build
|
|
||||||
./build/HW2a examples/test.obj
|
|
||||||
|
|
||||||
This test has a few triangles in it. Other obj files _should_ work in theory
|
|
||||||
|
|
||||||
![](examples/test.png)
|
|
|
@ -1 +0,0 @@
|
||||||
build/compile_commands.json
|
|
|
@ -1,26 +0,0 @@
|
||||||
{ stdenv, cmake, ninja, libglvnd, libGLU, xorg, spdlog }:
|
|
||||||
|
|
||||||
stdenv.mkDerivation {
|
|
||||||
name = "assignment-2a";
|
|
||||||
src = ./.;
|
|
||||||
|
|
||||||
nativeBuildInputs = [ cmake ninja ];
|
|
||||||
buildInputs = [
|
|
||||||
libglvnd
|
|
||||||
libGLU
|
|
||||||
xorg.libX11
|
|
||||||
xorg.libXcursor
|
|
||||||
xorg.libXext
|
|
||||||
xorg.libXi
|
|
||||||
xorg.libXinerama
|
|
||||||
xorg.libXrandr
|
|
||||||
xorg.libXrender
|
|
||||||
spdlog
|
|
||||||
];
|
|
||||||
|
|
||||||
preBuild = ''
|
|
||||||
env
|
|
||||||
'';
|
|
||||||
|
|
||||||
cmakeFlags = [ "-DCMAKE_CURRENT_SOURCE_DIR=${./.}" ];
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
v 2 3 0
|
|
||||||
v 1.5 4 0
|
|
||||||
v 3 2 0
|
|
||||||
|
|
||||||
v 1.5 4 0
|
|
||||||
v 3 2 0
|
|
||||||
v 5 3.4 0
|
|
||||||
|
|
||||||
v 3 2 0
|
|
||||||
v 5 3.4 0
|
|
||||||
v 6 5.9 0
|
|
Before Width: | Height: | Size: 30 KiB |
BIN
assignment-2a/ext/.DS_Store
vendored
BIN
assignment-2a/ext/glad/.DS_Store
vendored
|
@ -1,290 +0,0 @@
|
||||||
#ifndef __khrplatform_h_
|
|
||||||
#define __khrplatform_h_
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Copyright (c) 2008-2018 The Khronos Group Inc.
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
** copy of this software and/or associated documentation files (the
|
|
||||||
** "Materials"), to deal in the Materials without restriction, including
|
|
||||||
** without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
** distribute, sublicense, and/or sell copies of the Materials, and to
|
|
||||||
** permit persons to whom the Materials are furnished to do so, subject to
|
|
||||||
** the following conditions:
|
|
||||||
**
|
|
||||||
** The above copyright notice and this permission notice shall be included
|
|
||||||
** in all copies or substantial portions of the Materials.
|
|
||||||
**
|
|
||||||
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Khronos platform-specific types and definitions.
|
|
||||||
*
|
|
||||||
* The master copy of khrplatform.h is maintained in the Khronos EGL
|
|
||||||
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
|
|
||||||
* The last semantic modification to khrplatform.h was at commit ID:
|
|
||||||
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
|
|
||||||
*
|
|
||||||
* Adopters may modify this file to suit their platform. Adopters are
|
|
||||||
* encouraged to submit platform specific modifications to the Khronos
|
|
||||||
* group so that they can be included in future versions of this file.
|
|
||||||
* Please submit changes by filing pull requests or issues on
|
|
||||||
* the EGL Registry repository linked above.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* See the Implementer's Guidelines for information about where this file
|
|
||||||
* should be located on your system and for more details of its use:
|
|
||||||
* http://www.khronos.org/registry/implementers_guide.pdf
|
|
||||||
*
|
|
||||||
* This file should be included as
|
|
||||||
* #include <KHR/khrplatform.h>
|
|
||||||
* by Khronos client API header files that use its types and defines.
|
|
||||||
*
|
|
||||||
* The types in khrplatform.h should only be used to define API-specific types.
|
|
||||||
*
|
|
||||||
* Types defined in khrplatform.h:
|
|
||||||
* khronos_int8_t signed 8 bit
|
|
||||||
* khronos_uint8_t unsigned 8 bit
|
|
||||||
* khronos_int16_t signed 16 bit
|
|
||||||
* khronos_uint16_t unsigned 16 bit
|
|
||||||
* khronos_int32_t signed 32 bit
|
|
||||||
* khronos_uint32_t unsigned 32 bit
|
|
||||||
* khronos_int64_t signed 64 bit
|
|
||||||
* khronos_uint64_t unsigned 64 bit
|
|
||||||
* khronos_intptr_t signed same number of bits as a pointer
|
|
||||||
* khronos_uintptr_t unsigned same number of bits as a pointer
|
|
||||||
* khronos_ssize_t signed size
|
|
||||||
* khronos_usize_t unsigned size
|
|
||||||
* khronos_float_t signed 32 bit floating point
|
|
||||||
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
|
|
||||||
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
|
|
||||||
* nanoseconds
|
|
||||||
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
|
|
||||||
* khronos_boolean_enum_t enumerated boolean type. This should
|
|
||||||
* only be used as a base type when a client API's boolean type is
|
|
||||||
* an enum. Client APIs which use an integer or other type for
|
|
||||||
* booleans cannot use this as the base type for their boolean.
|
|
||||||
*
|
|
||||||
* Tokens defined in khrplatform.h:
|
|
||||||
*
|
|
||||||
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
|
|
||||||
*
|
|
||||||
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
|
|
||||||
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
|
|
||||||
*
|
|
||||||
* Calling convention macros defined in this file:
|
|
||||||
* KHRONOS_APICALL
|
|
||||||
* KHRONOS_APIENTRY
|
|
||||||
* KHRONOS_APIATTRIBUTES
|
|
||||||
*
|
|
||||||
* These may be used in function prototypes as:
|
|
||||||
*
|
|
||||||
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
|
|
||||||
* int arg1,
|
|
||||||
* int arg2) KHRONOS_APIATTRIBUTES;
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
|
|
||||||
# define KHRONOS_STATIC 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
|
||||||
* Definition of KHRONOS_APICALL
|
|
||||||
*-------------------------------------------------------------------------
|
|
||||||
* This precedes the return type of the function in the function prototype.
|
|
||||||
*/
|
|
||||||
#if defined(KHRONOS_STATIC)
|
|
||||||
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
|
|
||||||
* header compatible with static linking. */
|
|
||||||
# define KHRONOS_APICALL
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
# define KHRONOS_APICALL __declspec(dllimport)
|
|
||||||
#elif defined (__SYMBIAN32__)
|
|
||||||
# define KHRONOS_APICALL IMPORT_C
|
|
||||||
#elif defined(__ANDROID__)
|
|
||||||
# define KHRONOS_APICALL __attribute__((visibility("default")))
|
|
||||||
#else
|
|
||||||
# define KHRONOS_APICALL
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
|
||||||
* Definition of KHRONOS_APIENTRY
|
|
||||||
*-------------------------------------------------------------------------
|
|
||||||
* This follows the return type of the function and precedes the function
|
|
||||||
* name in the function prototype.
|
|
||||||
*/
|
|
||||||
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(KHRONOS_STATIC)
|
|
||||||
/* Win32 but not WinCE */
|
|
||||||
# define KHRONOS_APIENTRY __stdcall
|
|
||||||
#else
|
|
||||||
# define KHRONOS_APIENTRY
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
|
||||||
* Definition of KHRONOS_APIATTRIBUTES
|
|
||||||
*-------------------------------------------------------------------------
|
|
||||||
* This follows the closing parenthesis of the function prototype arguments.
|
|
||||||
*/
|
|
||||||
#if defined (__ARMCC_2__)
|
|
||||||
#define KHRONOS_APIATTRIBUTES __softfp
|
|
||||||
#else
|
|
||||||
#define KHRONOS_APIATTRIBUTES
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
|
||||||
* basic type definitions
|
|
||||||
*-----------------------------------------------------------------------*/
|
|
||||||
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Using <stdint.h>
|
|
||||||
*/
|
|
||||||
#include <stdint.h>
|
|
||||||
typedef int32_t khronos_int32_t;
|
|
||||||
typedef uint32_t khronos_uint32_t;
|
|
||||||
typedef int64_t khronos_int64_t;
|
|
||||||
typedef uint64_t khronos_uint64_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
|
|
||||||
#elif defined(__VMS ) || defined(__sgi)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Using <inttypes.h>
|
|
||||||
*/
|
|
||||||
#include <inttypes.h>
|
|
||||||
typedef int32_t khronos_int32_t;
|
|
||||||
typedef uint32_t khronos_uint32_t;
|
|
||||||
typedef int64_t khronos_int64_t;
|
|
||||||
typedef uint64_t khronos_uint64_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
|
|
||||||
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Win32
|
|
||||||
*/
|
|
||||||
typedef __int32 khronos_int32_t;
|
|
||||||
typedef unsigned __int32 khronos_uint32_t;
|
|
||||||
typedef __int64 khronos_int64_t;
|
|
||||||
typedef unsigned __int64 khronos_uint64_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
|
|
||||||
#elif defined(__sun__) || defined(__digital__)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sun or Digital
|
|
||||||
*/
|
|
||||||
typedef int khronos_int32_t;
|
|
||||||
typedef unsigned int khronos_uint32_t;
|
|
||||||
#if defined(__arch64__) || defined(_LP64)
|
|
||||||
typedef long int khronos_int64_t;
|
|
||||||
typedef unsigned long int khronos_uint64_t;
|
|
||||||
#else
|
|
||||||
typedef long long int khronos_int64_t;
|
|
||||||
typedef unsigned long long int khronos_uint64_t;
|
|
||||||
#endif /* __arch64__ */
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
|
|
||||||
#elif 0
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Hypothetical platform with no float or int64 support
|
|
||||||
*/
|
|
||||||
typedef int khronos_int32_t;
|
|
||||||
typedef unsigned int khronos_uint32_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 0
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 0
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Generic fallback
|
|
||||||
*/
|
|
||||||
#include <stdint.h>
|
|
||||||
typedef int32_t khronos_int32_t;
|
|
||||||
typedef uint32_t khronos_uint32_t;
|
|
||||||
typedef int64_t khronos_int64_t;
|
|
||||||
typedef uint64_t khronos_uint64_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Types that are (so far) the same on all platforms
|
|
||||||
*/
|
|
||||||
typedef signed char khronos_int8_t;
|
|
||||||
typedef unsigned char khronos_uint8_t;
|
|
||||||
typedef signed short int khronos_int16_t;
|
|
||||||
typedef unsigned short int khronos_uint16_t;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Types that differ between LLP64 and LP64 architectures - in LLP64,
|
|
||||||
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
|
|
||||||
* to be the only LLP64 architecture in current use.
|
|
||||||
*/
|
|
||||||
#ifdef _WIN64
|
|
||||||
typedef signed long long int khronos_intptr_t;
|
|
||||||
typedef unsigned long long int khronos_uintptr_t;
|
|
||||||
typedef signed long long int khronos_ssize_t;
|
|
||||||
typedef unsigned long long int khronos_usize_t;
|
|
||||||
#else
|
|
||||||
typedef signed long int khronos_intptr_t;
|
|
||||||
typedef unsigned long int khronos_uintptr_t;
|
|
||||||
typedef signed long int khronos_ssize_t;
|
|
||||||
typedef unsigned long int khronos_usize_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if KHRONOS_SUPPORT_FLOAT
|
|
||||||
/*
|
|
||||||
* Float type
|
|
||||||
*/
|
|
||||||
typedef float khronos_float_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if KHRONOS_SUPPORT_INT64
|
|
||||||
/* Time types
|
|
||||||
*
|
|
||||||
* These types can be used to represent a time interval in nanoseconds or
|
|
||||||
* an absolute Unadjusted System Time. Unadjusted System Time is the number
|
|
||||||
* of nanoseconds since some arbitrary system event (e.g. since the last
|
|
||||||
* time the system booted). The Unadjusted System Time is an unsigned
|
|
||||||
* 64 bit value that wraps back to 0 every 584 years. Time intervals
|
|
||||||
* may be either signed or unsigned.
|
|
||||||
*/
|
|
||||||
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
|
|
||||||
typedef khronos_int64_t khronos_stime_nanoseconds_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Dummy value used to pad enum types to 32 bits.
|
|
||||||
*/
|
|
||||||
#ifndef KHRONOS_MAX_ENUM
|
|
||||||
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Enumerated boolean type
|
|
||||||
*
|
|
||||||
* Values other than zero should be considered to be true. Therefore
|
|
||||||
* comparisons should not be made against KHRONOS_TRUE.
|
|
||||||
*/
|
|
||||||
typedef enum {
|
|
||||||
KHRONOS_FALSE = 0,
|
|
||||||
KHRONOS_TRUE = 1,
|
|
||||||
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
|
|
||||||
} khronos_boolean_enum_t;
|
|
||||||
|
|
||||||
#endif /* __khrplatform_h_ */
|
|
|
@ -1,65 +0,0 @@
|
||||||
image:
|
|
||||||
- Visual Studio 2015
|
|
||||||
- Visual Studio 2019
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- ci
|
|
||||||
- master
|
|
||||||
- 3.3-stable
|
|
||||||
skip_tags: true
|
|
||||||
skip_commits:
|
|
||||||
files:
|
|
||||||
- README.md
|
|
||||||
- LICENSE.md
|
|
||||||
- docs/*
|
|
||||||
environment:
|
|
||||||
matrix:
|
|
||||||
- GENERATOR: MinGW Makefiles
|
|
||||||
BUILD_SHARED_LIBS: ON
|
|
||||||
CFLAGS: -Werror
|
|
||||||
- GENERATOR: MinGW Makefiles
|
|
||||||
BUILD_SHARED_LIBS: OFF
|
|
||||||
CFLAGS: -Werror
|
|
||||||
- GENERATOR: Visual Studio 10 2010
|
|
||||||
BUILD_SHARED_LIBS: ON
|
|
||||||
CFLAGS: /WX
|
|
||||||
- GENERATOR: Visual Studio 10 2010
|
|
||||||
BUILD_SHARED_LIBS: OFF
|
|
||||||
CFLAGS: /WX
|
|
||||||
- GENERATOR: Visual Studio 16 2019
|
|
||||||
BUILD_SHARED_LIBS: ON
|
|
||||||
CFLAGS: /WX
|
|
||||||
- GENERATOR: Visual Studio 16 2019
|
|
||||||
BUILD_SHARED_LIBS: OFF
|
|
||||||
CFLAGS: /WX
|
|
||||||
matrix:
|
|
||||||
fast_finish: true
|
|
||||||
exclude:
|
|
||||||
- image: Visual Studio 2015
|
|
||||||
GENERATOR: Visual Studio 16 2019
|
|
||||||
- image: Visual Studio 2019
|
|
||||||
GENERATOR: Visual Studio 10 2010
|
|
||||||
- image: Visual Studio 2019
|
|
||||||
GENERATOR: MinGW Makefiles
|
|
||||||
for:
|
|
||||||
-
|
|
||||||
matrix:
|
|
||||||
except:
|
|
||||||
- GENERATOR: Visual Studio 10 2010
|
|
||||||
build_script:
|
|
||||||
- set PATH=%PATH:C:\Program Files\Git\usr\bin=C:\MinGW\bin%
|
|
||||||
- cmake -S . -B build -G "%GENERATOR%" -DBUILD_SHARED_LIBS=%BUILD_SHARED_LIBS%
|
|
||||||
- cmake --build build
|
|
||||||
-
|
|
||||||
matrix:
|
|
||||||
only:
|
|
||||||
- GENERATOR: Visual Studio 10 2010
|
|
||||||
build_script:
|
|
||||||
- cmake -S . -B build -G "%GENERATOR%" -DBUILD_SHARED_LIBS=%BUILD_SHARED_LIBS%
|
|
||||||
- cmake --build build --target glfw
|
|
||||||
notifications:
|
|
||||||
- provider: Email
|
|
||||||
to:
|
|
||||||
- ci@glfw.org
|
|
||||||
on_build_failure: true
|
|
||||||
on_build_success: false
|
|
5
assignment-2a/ext/glfw/.gitattributes
vendored
|
@ -1,5 +0,0 @@
|
||||||
*.m linguist-language=Objective-C
|
|
||||||
.gitignore export-ignore
|
|
||||||
.gitattributes export-ignore
|
|
||||||
.travis.yml export-ignore
|
|
||||||
.appveyor.yml export-ignore
|
|
85
assignment-2a/ext/glfw/.gitignore
vendored
|
@ -1,85 +0,0 @@
|
||||||
# External junk
|
|
||||||
.DS_Store
|
|
||||||
_ReSharper*
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.suo
|
|
||||||
*.dir
|
|
||||||
*.vcxproj*
|
|
||||||
*.sln
|
|
||||||
.vs/
|
|
||||||
Win32
|
|
||||||
x64
|
|
||||||
Debug
|
|
||||||
Release
|
|
||||||
MinSizeRel
|
|
||||||
RelWithDebInfo
|
|
||||||
*.xcodeproj
|
|
||||||
|
|
||||||
# CMake files
|
|
||||||
Makefile
|
|
||||||
CMakeCache.txt
|
|
||||||
CMakeFiles
|
|
||||||
CMakeScripts
|
|
||||||
cmake_install.cmake
|
|
||||||
cmake_uninstall.cmake
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
docs/Doxyfile
|
|
||||||
docs/html
|
|
||||||
docs/warnings.txt
|
|
||||||
docs/doxygen_sqlite3.db
|
|
||||||
src/glfw_config.h
|
|
||||||
src/glfw3.pc
|
|
||||||
src/glfw3Config.cmake
|
|
||||||
src/glfw3ConfigVersion.cmake
|
|
||||||
src/wayland-pointer-constraints-unstable-v1-client-protocol.h
|
|
||||||
src/wayland-pointer-constraints-unstable-v1-protocol.c
|
|
||||||
src/wayland-relative-pointer-unstable-v1-client-protocol.h
|
|
||||||
src/wayland-relative-pointer-unstable-v1-protocol.c
|
|
||||||
|
|
||||||
# Compiled binaries
|
|
||||||
src/libglfw.so
|
|
||||||
src/libglfw.so.3
|
|
||||||
src/libglfw.so.3.4
|
|
||||||
src/libglfw.dylib
|
|
||||||
src/libglfw.dylib
|
|
||||||
src/libglfw.3.dylib
|
|
||||||
src/libglfw.3.4.dylib
|
|
||||||
src/libglfw3.a
|
|
||||||
src/glfw3.lib
|
|
||||||
src/glfw3.dll
|
|
||||||
src/glfw3dll.lib
|
|
||||||
src/libglfw3dll.a
|
|
||||||
examples/*.app
|
|
||||||
examples/*.exe
|
|
||||||
examples/boing
|
|
||||||
examples/gears
|
|
||||||
examples/heightmap
|
|
||||||
examples/offscreen
|
|
||||||
examples/particles
|
|
||||||
examples/splitview
|
|
||||||
examples/sharing
|
|
||||||
examples/triangle-opengl
|
|
||||||
examples/wave
|
|
||||||
tests/*.app
|
|
||||||
tests/*.exe
|
|
||||||
tests/clipboard
|
|
||||||
tests/cursor
|
|
||||||
tests/empty
|
|
||||||
tests/events
|
|
||||||
tests/gamma
|
|
||||||
tests/glfwinfo
|
|
||||||
tests/icon
|
|
||||||
tests/iconify
|
|
||||||
tests/joysticks
|
|
||||||
tests/monitors
|
|
||||||
tests/msaa
|
|
||||||
tests/reopen
|
|
||||||
tests/tearing
|
|
||||||
tests/threads
|
|
||||||
tests/timeout
|
|
||||||
tests/title
|
|
||||||
tests/triangle-vulkan
|
|
||||||
tests/windows
|
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
language: c
|
|
||||||
compiler: clang
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- ci
|
|
||||||
- master
|
|
||||||
- 3.3-stable
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: linux
|
|
||||||
dist: xenial
|
|
||||||
sudo: false
|
|
||||||
name: "X11 shared library"
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- libxrandr-dev
|
|
||||||
- libxinerama-dev
|
|
||||||
- libxcursor-dev
|
|
||||||
- libxi-dev
|
|
||||||
env:
|
|
||||||
- BUILD_SHARED_LIBS=ON
|
|
||||||
- CFLAGS=-Werror
|
|
||||||
- os: linux
|
|
||||||
dist: xenial
|
|
||||||
sudo: false
|
|
||||||
name: "X11 static library"
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- libxrandr-dev
|
|
||||||
- libxinerama-dev
|
|
||||||
- libxcursor-dev
|
|
||||||
- libxi-dev
|
|
||||||
env:
|
|
||||||
- BUILD_SHARED_LIBS=OFF
|
|
||||||
- CFLAGS=-Werror
|
|
||||||
- os: linux
|
|
||||||
dist: xenial
|
|
||||||
sudo: required
|
|
||||||
name: "Wayland shared library"
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
sources:
|
|
||||||
- ppa:kubuntu-ppa/backports
|
|
||||||
packages:
|
|
||||||
- extra-cmake-modules
|
|
||||||
- libwayland-dev
|
|
||||||
- libxkbcommon-dev
|
|
||||||
- libegl1-mesa-dev
|
|
||||||
env:
|
|
||||||
- USE_WAYLAND=ON
|
|
||||||
- BUILD_SHARED_LIBS=ON
|
|
||||||
- CFLAGS=-Werror
|
|
||||||
- os: linux
|
|
||||||
dist: xenial
|
|
||||||
sudo: required
|
|
||||||
name: "Wayland static library"
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
sources:
|
|
||||||
- ppa:kubuntu-ppa/backports
|
|
||||||
packages:
|
|
||||||
- extra-cmake-modules
|
|
||||||
- libwayland-dev
|
|
||||||
- libxkbcommon-dev
|
|
||||||
- libegl1-mesa-dev
|
|
||||||
env:
|
|
||||||
- USE_WAYLAND=ON
|
|
||||||
- BUILD_SHARED_LIBS=OFF
|
|
||||||
- CFLAGS=-Werror
|
|
||||||
- os: osx
|
|
||||||
sudo: false
|
|
||||||
name: "Cocoa shared library"
|
|
||||||
env:
|
|
||||||
- BUILD_SHARED_LIBS=ON
|
|
||||||
- CFLAGS=-Werror
|
|
||||||
- os: osx
|
|
||||||
sudo: false
|
|
||||||
name: "Cocoa static library"
|
|
||||||
env:
|
|
||||||
- BUILD_SHARED_LIBS=OFF
|
|
||||||
- CFLAGS=-Werror
|
|
||||||
script:
|
|
||||||
- if grep -Inr '\s$' src include docs tests examples CMake *.md .gitattributes .gitignore; then
|
|
||||||
echo Trailing whitespace found, aborting;
|
|
||||||
exit 1;
|
|
||||||
fi
|
|
||||||
- mkdir build
|
|
||||||
- cd build
|
|
||||||
- if test -n "${USE_WAYLAND}"; then
|
|
||||||
git clone git://anongit.freedesktop.org/wayland/wayland-protocols;
|
|
||||||
pushd wayland-protocols;
|
|
||||||
git checkout 1.15 && ./autogen.sh --prefix=/usr && make && sudo make install;
|
|
||||||
popd;
|
|
||||||
fi
|
|
||||||
- cmake -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS} -DGLFW_USE_WAYLAND=${USE_WAYLAND} ..
|
|
||||||
- cmake --build .
|
|
||||||
notifications:
|
|
||||||
email:
|
|
||||||
recipients:
|
|
||||||
- ci@glfw.org
|
|
||||||
on_success: never
|
|
||||||
on_failure: always
|
|
|
@ -1,33 +0,0 @@
|
||||||
# Usage:
|
|
||||||
# cmake -P GenerateMappings.cmake <path/to/mappings.h.in> <path/to/mappings.h>
|
|
||||||
|
|
||||||
set(source_url "https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt")
|
|
||||||
set(source_path "${CMAKE_CURRENT_BINARY_DIR}/gamecontrollerdb.txt")
|
|
||||||
set(template_path "${CMAKE_ARGV3}")
|
|
||||||
set(target_path "${CMAKE_ARGV4}")
|
|
||||||
|
|
||||||
if (NOT EXISTS "${template_path}")
|
|
||||||
message(FATAL_ERROR "Failed to find template file ${template_path}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
file(DOWNLOAD "${source_url}" "${source_path}"
|
|
||||||
STATUS download_status
|
|
||||||
TLS_VERIFY on)
|
|
||||||
|
|
||||||
list(GET download_status 0 status_code)
|
|
||||||
list(GET download_status 1 status_message)
|
|
||||||
|
|
||||||
if (status_code)
|
|
||||||
message(FATAL_ERROR "Failed to download ${source_url}: ${status_message}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
file(STRINGS "${source_path}" lines)
|
|
||||||
foreach(line ${lines})
|
|
||||||
if ("${line}" MATCHES "^[0-9a-fA-F].*$")
|
|
||||||
set(GLFW_GAMEPAD_MAPPINGS "${GLFW_GAMEPAD_MAPPINGS}\"${line}\",\n")
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
configure_file("${template_path}" "${target_path}" @ONLY NEWLINE_STYLE UNIX)
|
|
||||||
file(REMOVE "${source_path}")
|
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>English</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
|
||||||
<key>CFBundleGetInfoString</key>
|
|
||||||
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
|
||||||
<key>CFBundleIconFile</key>
|
|
||||||
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleLongVersionString</key>
|
|
||||||
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
|
||||||
<key>CFBundleSignature</key>
|
|
||||||
<string>????</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
|
||||||
<key>CSResourcesFileMapped</key>
|
|
||||||
<true/>
|
|
||||||
<key>LSRequiresCarbon</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSHumanReadableCopyright</key>
|
|
||||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
|
||||||
<key>NSHighResolutionCapable</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|