Rust port
This commit is contained in:
parent
3a1864647b
commit
9502137d77
7 changed files with 1303 additions and 0 deletions
1
rust/.gitignore
vendored
Normal file
1
rust/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
601
rust/Cargo.lock
generated
Normal file
601
rust/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,601 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.25.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.15.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"unicode-width",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-deque"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-epoch"
|
||||||
|
version = "0.9.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fdeflate"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide 0.9.1",
|
||||||
|
"zlib-rs",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.25.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder-lite",
|
||||||
|
"moxcms",
|
||||||
|
"num-traits",
|
||||||
|
"png",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indicatif"
|
||||||
|
version = "0.17.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"number_prefix",
|
||||||
|
"portable-atomic",
|
||||||
|
"unicode-width",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"futures-util",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.189"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moxcms"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"pxfm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"libm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "number_prefix"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathtracer"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"image",
|
||||||
|
"indicatif",
|
||||||
|
"rand",
|
||||||
|
"rand_distr",
|
||||||
|
"rayon",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide 0.8.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pxfm"
|
||||||
|
version = "0.1.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_distr"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"rayon-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon-core"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-deque",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.129"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.129"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.129"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.6",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.129"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-time"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.59"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.59"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zlib-rs"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
|
||||||
17
rust/Cargo.toml
Normal file
17
rust/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "pathtracer"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Cornell Box path tracer (Rust port of raytracer.py)"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = { version = "0.8", features = ["small_rng"] }
|
||||||
|
rand_distr = "0.4"
|
||||||
|
rayon = "1"
|
||||||
|
indicatif = "0.17"
|
||||||
|
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = 3
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
33
rust/src/camera.rs
Normal file
33
rust/src/camera.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
use crate::scene::Ray;
|
||||||
|
use crate::vec3::Vec3;
|
||||||
|
|
||||||
|
pub struct Camera {
|
||||||
|
origin: Vec3,
|
||||||
|
lower_left_corner: Vec3,
|
||||||
|
horizontal: Vec3,
|
||||||
|
vertical: Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Camera {
|
||||||
|
pub fn new(lookfrom: Vec3, lookat: Vec3, vup: Vec3, vfov: f64, aspect: f64, focus_dist: f64) -> Self {
|
||||||
|
let half_height = (vfov.to_radians() / 2.0).tan();
|
||||||
|
let half_width = aspect * half_height;
|
||||||
|
let w = (lookfrom - lookat).normalize();
|
||||||
|
let u = vup.cross(w).normalize();
|
||||||
|
let v = w.cross(u);
|
||||||
|
let horizontal = focus_dist * half_width * u;
|
||||||
|
let vertical = focus_dist * half_height * v;
|
||||||
|
let lower_left_corner = lookfrom - horizontal / 2.0 - vertical / 2.0 - focus_dist * w;
|
||||||
|
Camera {
|
||||||
|
origin: lookfrom,
|
||||||
|
lower_left_corner,
|
||||||
|
horizontal,
|
||||||
|
vertical,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ray(&self, s: f64, t: f64) -> Ray {
|
||||||
|
let direction = self.lower_left_corner + s * self.horizontal + t * self.vertical - self.origin;
|
||||||
|
Ray::new(self.origin, direction)
|
||||||
|
}
|
||||||
|
}
|
||||||
239
rust/src/main.rs
Normal file
239
rust/src/main.rs
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
mod camera;
|
||||||
|
mod scene;
|
||||||
|
mod vec3;
|
||||||
|
|
||||||
|
use std::f64::consts::PI;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
use rand::rngs::SmallRng;
|
||||||
|
use rand::{Rng, SeedableRng};
|
||||||
|
use rayon::prelude::*;
|
||||||
|
|
||||||
|
use camera::Camera;
|
||||||
|
use scene::{cornell_box, Material, Ray, Scene, EPSILON};
|
||||||
|
use vec3::Vec3;
|
||||||
|
|
||||||
|
fn trace<R: Rng>(scene: &Scene, ray: &Ray, depth: u32, max_depth: u32, rng: &mut R) -> Vec3 {
|
||||||
|
if depth >= max_depth {
|
||||||
|
return Vec3::ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(rec) = scene.hit(ray, EPSILON, f64::INFINITY) else {
|
||||||
|
let t = 0.5 * (ray.direction.y + 1.0);
|
||||||
|
return Vec3::ONE * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t;
|
||||||
|
};
|
||||||
|
|
||||||
|
let emitted = rec.material.emission();
|
||||||
|
if let Material::Light { .. } = rec.material {
|
||||||
|
return emitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((attenuation, scattered)) = rec.material.scatter(ray, &rec, rng) else {
|
||||||
|
return emitted;
|
||||||
|
};
|
||||||
|
|
||||||
|
let indirect = trace(scene, &scattered, depth + 1, max_depth, rng);
|
||||||
|
let mut color = emitted + attenuation.mul_elem(indirect);
|
||||||
|
|
||||||
|
if let Material::Diffuse { albedo } = rec.material {
|
||||||
|
let mut direct = Vec3::ZERO;
|
||||||
|
for light in &scene.lights {
|
||||||
|
let (lo, hi) = (light.min, light.max);
|
||||||
|
for _ in 0..2 {
|
||||||
|
let face = (rng.gen::<f64>() * 6.0) as usize;
|
||||||
|
let x = lo.x + (hi.x - lo.x) * rng.gen::<f64>();
|
||||||
|
let y = lo.y + (hi.y - lo.y) * rng.gen::<f64>();
|
||||||
|
let z = lo.z + (hi.z - lo.z) * rng.gen::<f64>();
|
||||||
|
let lp = match face {
|
||||||
|
0 => Vec3::new(lo.x, y, z),
|
||||||
|
1 => Vec3::new(hi.x, y, z),
|
||||||
|
2 => Vec3::new(x, lo.y, z),
|
||||||
|
3 => Vec3::new(x, hi.y, z),
|
||||||
|
4 => Vec3::new(x, y, lo.z),
|
||||||
|
_ => Vec3::new(x, y, hi.z),
|
||||||
|
};
|
||||||
|
|
||||||
|
let to_light = lp - rec.point;
|
||||||
|
let dist_sq = to_light.dot(to_light);
|
||||||
|
if dist_sq < 1e-8 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let dist = dist_sq.sqrt();
|
||||||
|
let d_to_light = to_light / dist;
|
||||||
|
let cos_at_rec = rec.normal.dot(d_to_light);
|
||||||
|
if cos_at_rec <= 0.0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if scene.blocked(rec.point + EPSILON * rec.normal, d_to_light, dist - EPSILON) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let light_area = 6.0 * (hi.x - lo.x) * (hi.y - lo.y) * (hi.z - lo.z);
|
||||||
|
let pdf = light_area / (dist_sq * cos_at_rec);
|
||||||
|
if pdf < 1e-12 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let brdf = albedo / PI;
|
||||||
|
direct += light.emission.mul_elem(brdf) * (cos_at_rec / pdf);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if direct.dot(direct) > 0.0 {
|
||||||
|
color = emitted + attenuation.mul_elem(indirect + direct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
color
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Settings {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
spp: u32,
|
||||||
|
max_depth: u32,
|
||||||
|
output: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(scene: &Scene, s: &Settings) -> Vec<Vec3> {
|
||||||
|
let (width, height) = (s.width, s.height);
|
||||||
|
let cam = Camera::new(
|
||||||
|
Vec3::new(278.0, 278.0, -800.0),
|
||||||
|
Vec3::new(278.0, 278.0, 278.0),
|
||||||
|
Vec3::new(0.0, 1.0, 0.0),
|
||||||
|
40.0,
|
||||||
|
width as f64 / height as f64,
|
||||||
|
1078.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
let pb = ProgressBar::new(height as u64);
|
||||||
|
pb.set_style(
|
||||||
|
ProgressStyle::with_template(
|
||||||
|
"{spinner:.green} Rendering [{bar:50.bright_green/blue}] {percent:>3}% {pos}/{len} rows {elapsed_precise} ETA {eta_precise}",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.progress_chars("=> "),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut image = vec![Vec3::ZERO; width * height];
|
||||||
|
image
|
||||||
|
.par_chunks_mut(width)
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(j, row)| {
|
||||||
|
for (i, px) in row.iter_mut().enumerate() {
|
||||||
|
let mut rng = SmallRng::seed_from_u64((j * width + i) as u64 ^ 0x9E37_79B9_7F4A_7C15);
|
||||||
|
let mut color = Vec3::ZERO;
|
||||||
|
for _ in 0..s.spp {
|
||||||
|
let u = (i as f64 + rng.gen::<f64>()) / (width - 1) as f64;
|
||||||
|
let v = ((height - 1 - j) as f64 + rng.gen::<f64>()) / (height - 1) as f64;
|
||||||
|
let ray = cam.get_ray(u, v);
|
||||||
|
color += trace(scene, &ray, 0, s.max_depth, &mut rng);
|
||||||
|
}
|
||||||
|
color /= s.spp as f64;
|
||||||
|
*px = color.map(|c| c.max(0.0).sqrt().min(1.0));
|
||||||
|
}
|
||||||
|
pb.inc(1);
|
||||||
|
});
|
||||||
|
pb.finish();
|
||||||
|
|
||||||
|
image
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_png(image: &[Vec3], width: usize, height: usize, path: &str) -> image::ImageResult<()> {
|
||||||
|
let bytes: Vec<u8> = image
|
||||||
|
.iter()
|
||||||
|
.flat_map(|c| [c.x, c.y, c.z])
|
||||||
|
.map(|c| (c.clamp(0.0, 1.0) * 255.0) as u8)
|
||||||
|
.collect();
|
||||||
|
image::save_buffer(path, &bytes, width as u32, height as u32, image::ColorType::Rgb8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn region_mean(image: &[Vec3], width: usize, rows: std::ops::Range<usize>, cols: std::ops::Range<usize>) -> f64 {
|
||||||
|
let n = rows.len() * cols.len();
|
||||||
|
if n == 0 {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
let sum: f64 = rows
|
||||||
|
.flat_map(|j| cols.clone().map(move |i| image[j * width + i].mean()))
|
||||||
|
.sum();
|
||||||
|
sum / n as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_region_table(image: &[Vec3], width: usize, height: usize) {
|
||||||
|
// Same regions as the Python version (which slices the Floor/Ceiling/Center
|
||||||
|
// columns by height-based indices).
|
||||||
|
let (h_start, h_end) = (height / 3, 2 * height / 3);
|
||||||
|
let clamp_cols = |r: std::ops::Range<usize>| r.start.min(width)..r.end.min(width);
|
||||||
|
let regions = [
|
||||||
|
("Left Wall", 0..height, 0..width / 4),
|
||||||
|
("Right Wall", 0..height, 3 * width / 4..width),
|
||||||
|
("Floor", 2 * height / 3..height, clamp_cols(h_start..h_end)),
|
||||||
|
("Ceiling", 0..height / 3, clamp_cols(h_start..h_end)),
|
||||||
|
("Center", h_start..h_end, clamp_cols(h_start..h_end)),
|
||||||
|
];
|
||||||
|
|
||||||
|
println!("Region Brightness Analysis");
|
||||||
|
println!("{:<12} {:>10} Visual", "Region", "Brightness");
|
||||||
|
for (name, rows, cols) in regions {
|
||||||
|
let brightness = region_mean(image, width, rows, cols) * 255.0;
|
||||||
|
let filled = ((brightness / 255.0 * 20.0) as usize).min(20);
|
||||||
|
let bar = format!("{}{}", "#".repeat(filled), "-".repeat(20 - filled));
|
||||||
|
let color = if brightness > 80.0 { 32 } else if brightness > 40.0 { 33 } else { 31 };
|
||||||
|
println!("{name:<12} {brightness:>10.1} \x1b[{color}m{bar}\x1b[0m");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
let arg = |i: usize, default: &str| args.get(i).cloned().unwrap_or_else(|| default.to_string());
|
||||||
|
let parse = |i: usize, default: &str| -> usize {
|
||||||
|
arg(i, default).parse().unwrap_or_else(|_| {
|
||||||
|
eprintln!("usage: pathtracer [width] [height] [samples_per_pixel] [max_bounces] [output]");
|
||||||
|
std::process::exit(2);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let settings = Settings {
|
||||||
|
width: parse(1, "400"),
|
||||||
|
height: parse(2, "300"),
|
||||||
|
spp: parse(3, "50") as u32,
|
||||||
|
max_depth: parse(4, "15") as u32,
|
||||||
|
output: arg(5, "cornell_box.png"),
|
||||||
|
};
|
||||||
|
if settings.width < 2 || settings.height < 2 || settings.spp == 0 {
|
||||||
|
eprintln!("width and height must be >= 2 and samples_per_pixel >= 1");
|
||||||
|
std::process::exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("=== Cornell Box Path Tracer (Rust) ===");
|
||||||
|
println!("Features: reflections, refraction/caustics, multiple bounces, NEE");
|
||||||
|
println!();
|
||||||
|
println!("Resolution: {}x{}", settings.width, settings.height);
|
||||||
|
println!("Samples per pixel: {}", settings.spp);
|
||||||
|
println!("Max bounces: {}", settings.max_depth);
|
||||||
|
println!("Threads: {}", rayon::current_num_threads());
|
||||||
|
println!("Output: {}", settings.output);
|
||||||
|
println!();
|
||||||
|
|
||||||
|
let scene = cornell_box();
|
||||||
|
let start = Instant::now();
|
||||||
|
let image = render(&scene, &settings);
|
||||||
|
let elapsed = start.elapsed().as_secs_f64();
|
||||||
|
|
||||||
|
if let Err(e) = save_png(&image, settings.width, settings.height, &settings.output) {
|
||||||
|
eprintln!("failed to write {}: {e}", settings.output);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
|
println!("Rendered {}", settings.output);
|
||||||
|
println!(
|
||||||
|
"Total time: {:.2}s | Speed: {:.2} MPix*samp/s",
|
||||||
|
elapsed,
|
||||||
|
(settings.width * settings.height) as f64 * settings.spp as f64 / elapsed / 1e6
|
||||||
|
);
|
||||||
|
println!();
|
||||||
|
print_region_table(&image, settings.width, settings.height);
|
||||||
|
}
|
||||||
268
rust/src/scene.rs
Normal file
268
rust/src/scene.rs
Normal file
|
|
@ -0,0 +1,268 @@
|
||||||
|
use rand::Rng;
|
||||||
|
use rand_distr::StandardNormal;
|
||||||
|
|
||||||
|
use crate::vec3::{reflect, refract, schlick, Vec3};
|
||||||
|
|
||||||
|
pub const EPSILON: f64 = 1e-6;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Ray {
|
||||||
|
pub origin: Vec3,
|
||||||
|
pub direction: Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ray {
|
||||||
|
pub fn new(origin: Vec3, direction: Vec3) -> Self {
|
||||||
|
Ray {
|
||||||
|
origin,
|
||||||
|
direction: direction.normalize(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn at(&self, t: f64) -> Vec3 {
|
||||||
|
self.origin + t * self.direction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Material {
|
||||||
|
Diffuse { albedo: Vec3 },
|
||||||
|
Mirror { albedo: Vec3 },
|
||||||
|
Dielectric { ref_idx: f64, albedo: Vec3 },
|
||||||
|
Light { emission: Vec3 },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HitRecord {
|
||||||
|
pub point: Vec3,
|
||||||
|
pub normal: Vec3,
|
||||||
|
pub front_face: bool,
|
||||||
|
pub material: Material,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HitRecord {
|
||||||
|
/// Orients the normal against the incoming ray, as in the Python version.
|
||||||
|
fn new(r: &Ray, t: f64, outward: Vec3, material: Material) -> Self {
|
||||||
|
let front_face = r.direction.dot(outward) < 0.0;
|
||||||
|
HitRecord {
|
||||||
|
point: r.at(t),
|
||||||
|
normal: if front_face { outward } else { -outward },
|
||||||
|
front_face,
|
||||||
|
material,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Material {
|
||||||
|
pub fn emission(&self) -> Vec3 {
|
||||||
|
match self {
|
||||||
|
Material::Light { emission } => *emission,
|
||||||
|
_ => Vec3::ZERO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns (attenuation, scattered ray), or None if the ray is absorbed.
|
||||||
|
pub fn scatter<R: Rng>(&self, ray_in: &Ray, rec: &HitRecord, rng: &mut R) -> Option<(Vec3, Ray)> {
|
||||||
|
match *self {
|
||||||
|
Material::Diffuse { albedo } => {
|
||||||
|
let jitter = Vec3::new(
|
||||||
|
rng.sample(StandardNormal),
|
||||||
|
rng.sample(StandardNormal),
|
||||||
|
rng.sample(StandardNormal),
|
||||||
|
);
|
||||||
|
let mut target = rec.normal + jitter * 0.5;
|
||||||
|
if target.length() < 1e-8 {
|
||||||
|
target = rec.normal;
|
||||||
|
}
|
||||||
|
Some((albedo, Ray::new(rec.point, target)))
|
||||||
|
}
|
||||||
|
Material::Mirror { albedo } => {
|
||||||
|
let reflected = reflect(ray_in.direction, rec.normal);
|
||||||
|
if reflected.dot(rec.normal) <= 0.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((albedo, Ray::new(rec.point, reflected)))
|
||||||
|
}
|
||||||
|
Material::Dielectric { ref_idx, albedo } => {
|
||||||
|
let ratio = if rec.front_face { 1.0 / ref_idx } else { ref_idx };
|
||||||
|
let d = ray_in.direction;
|
||||||
|
let cos_theta = (-d.dot(rec.normal)).min(1.0);
|
||||||
|
let sin_theta = (1.0 - cos_theta * cos_theta).max(0.0).sqrt();
|
||||||
|
let cannot_refract = ratio * sin_theta > 1.0;
|
||||||
|
let direction = if cannot_refract || schlick(cos_theta, ratio) > rng.gen::<f64>() {
|
||||||
|
reflect(d, rec.normal)
|
||||||
|
} else {
|
||||||
|
refract(d, rec.normal, ratio).unwrap_or_else(|| reflect(d, rec.normal))
|
||||||
|
};
|
||||||
|
Some((albedo, Ray::new(rec.point, direction)))
|
||||||
|
}
|
||||||
|
Material::Light { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Shape {
|
||||||
|
Sphere { center: Vec3, radius: f64 },
|
||||||
|
Plane { point: Vec3, normal: Vec3 },
|
||||||
|
Box { min: Vec3, max: Vec3 },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shape {
|
||||||
|
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<(f64, Vec3)> {
|
||||||
|
match *self {
|
||||||
|
Shape::Sphere { center, radius } => {
|
||||||
|
let oc = r.origin - center;
|
||||||
|
let a = r.direction.dot(r.direction);
|
||||||
|
let b = oc.dot(r.direction);
|
||||||
|
let c = oc.dot(oc) - radius * radius;
|
||||||
|
let disc = b * b - a * c;
|
||||||
|
if disc < 0.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let sq = disc.sqrt();
|
||||||
|
let mut root = (-b - sq) / a;
|
||||||
|
if root < t_min || root > t_max {
|
||||||
|
root = (-b + sq) / a;
|
||||||
|
if root < t_min || root > t_max {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let outward = (r.at(root) - center).normalize();
|
||||||
|
Some((root, outward))
|
||||||
|
}
|
||||||
|
Shape::Plane { point, normal } => {
|
||||||
|
let denom = r.direction.dot(normal);
|
||||||
|
if denom.abs() < 1e-12 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let t = (point - r.origin).dot(normal) / denom;
|
||||||
|
if t < t_min || t > t_max {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((t, normal))
|
||||||
|
}
|
||||||
|
Shape::Box { min, max } => {
|
||||||
|
let mut t_lo = t_min;
|
||||||
|
let mut t_hi = t_max;
|
||||||
|
// Python indexes with -1 when no slab raises t_lo, which means the z axis.
|
||||||
|
let mut hit_axis = 2;
|
||||||
|
let mut inv_ds = [0.0; 3];
|
||||||
|
for axis in 0..3 {
|
||||||
|
let d = r.direction[axis];
|
||||||
|
let inv_d = if d.abs() > 1e-12 { 1.0 / d } else { 1e12 };
|
||||||
|
inv_ds[axis] = inv_d;
|
||||||
|
let mut t0 = (min[axis] - r.origin[axis]) * inv_d;
|
||||||
|
let mut t1 = (max[axis] - r.origin[axis]) * inv_d;
|
||||||
|
if inv_d < 0.0 {
|
||||||
|
std::mem::swap(&mut t0, &mut t1);
|
||||||
|
}
|
||||||
|
if t0 > t_lo {
|
||||||
|
t_lo = t0;
|
||||||
|
hit_axis = axis;
|
||||||
|
}
|
||||||
|
t_hi = t_hi.min(t1);
|
||||||
|
if t_hi < t_lo {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t_lo >= t_max || t_lo <= t_min {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let sign = if inv_ds[hit_axis] > 0.0 { 1.0 } else { -1.0 };
|
||||||
|
let mut n = [0.0; 3];
|
||||||
|
n[hit_axis] = sign;
|
||||||
|
Some((t_lo, Vec3::new(n[0], n[1], n[2])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Object {
|
||||||
|
pub shape: Shape,
|
||||||
|
pub material: Material,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An axis-aligned box light, sampled explicitly for next event estimation.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct BoxLight {
|
||||||
|
pub min: Vec3,
|
||||||
|
pub max: Vec3,
|
||||||
|
pub emission: Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Scene {
|
||||||
|
pub objects: Vec<Object>,
|
||||||
|
pub lights: Vec<BoxLight>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
pub fn add(&mut self, shape: Shape, material: Material) {
|
||||||
|
self.objects.push(Object { shape, material });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)] // see cornell_box()
|
||||||
|
pub fn add_box_light(&mut self, min: Vec3, max: Vec3, emission: Vec3) {
|
||||||
|
self.lights.push(BoxLight { min, max, emission });
|
||||||
|
self.add(Shape::Box { min, max }, Material::Light { emission });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||||
|
let mut closest: Option<(f64, Vec3, Material)> = None;
|
||||||
|
let mut min_t = t_max;
|
||||||
|
for obj in &self.objects {
|
||||||
|
if let Some((t, outward)) = obj.shape.hit(r, t_min, min_t) {
|
||||||
|
if t < min_t {
|
||||||
|
min_t = t;
|
||||||
|
closest = Some((t, outward, obj.material));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closest.map(|(t, outward, material)| HitRecord::new(r, t, outward, material))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blocked(&self, origin: Vec3, direction: Vec3, t_max: f64) -> bool {
|
||||||
|
let r = Ray::new(origin, direction);
|
||||||
|
self.objects
|
||||||
|
.iter()
|
||||||
|
.any(|obj| obj.shape.hit(&r, EPSILON, t_max).is_some())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cornell_box() -> Scene {
|
||||||
|
let mut scene = Scene::default();
|
||||||
|
|
||||||
|
let white = Material::Diffuse { albedo: Vec3::new(0.73, 0.73, 0.73) };
|
||||||
|
let gray = Material::Diffuse { albedo: Vec3::new(0.5, 0.5, 0.5) };
|
||||||
|
let red = Material::Diffuse { albedo: Vec3::new(0.65, 0.05, 0.05) };
|
||||||
|
let green = Material::Diffuse { albedo: Vec3::new(0.12, 0.45, 0.15) };
|
||||||
|
let mirror = Material::Mirror { albedo: Vec3::new(0.95, 0.95, 0.95) };
|
||||||
|
let glass = Material::Dielectric { ref_idx: 1.5, albedo: Vec3::ONE };
|
||||||
|
|
||||||
|
let plane = |p: Vec3, n: Vec3| Shape::Plane { point: p, normal: n.normalize() };
|
||||||
|
scene.add(plane(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 1.0, 0.0)), gray);
|
||||||
|
scene.add(plane(Vec3::new(0.0, 500.0, 0.0), Vec3::new(0.0, -1.0, 0.0)), white);
|
||||||
|
scene.add(plane(Vec3::new(0.0, 0.0, 500.0), Vec3::new(0.0, 0.0, -1.0)), white);
|
||||||
|
scene.add(plane(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)), red);
|
||||||
|
scene.add(plane(Vec3::new(500.0, 0.0, 0.0), Vec3::new(-1.0, 0.0, 0.0)), green);
|
||||||
|
|
||||||
|
// Like raytracer.py, the light is added as a plain object rather than via
|
||||||
|
// add_box_light, so `lights` stays empty and NEE in `trace` never runs.
|
||||||
|
// Using add_box_light here enables the Python NEE code as-written, which
|
||||||
|
// over-brightens the image (it double-counts light and its pdf is off).
|
||||||
|
scene.add(
|
||||||
|
Shape::Box {
|
||||||
|
min: Vec3::new(0.0, 180.0, 180.0),
|
||||||
|
max: Vec3::new(25.0, 220.0, 320.0),
|
||||||
|
},
|
||||||
|
Material::Light { emission: Vec3::splat(50.0) },
|
||||||
|
);
|
||||||
|
|
||||||
|
let sphere = |c: Vec3, r: f64| Shape::Sphere { center: c, radius: r };
|
||||||
|
scene.add(sphere(Vec3::new(250.0, 180.0, 250.0), 80.0), glass);
|
||||||
|
scene.add(sphere(Vec3::new(150.0, 180.0, 380.0), 80.0), mirror);
|
||||||
|
scene.add(sphere(Vec3::new(350.0, 160.0, 300.0), 60.0), red);
|
||||||
|
|
||||||
|
scene
|
||||||
|
}
|
||||||
144
rust/src/vec3.rs
Normal file
144
rust/src/vec3.rs
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
use std::ops::{Add, AddAssign, Div, DivAssign, Index, Mul, Neg, Sub};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
|
pub struct Vec3 {
|
||||||
|
pub x: f64,
|
||||||
|
pub y: f64,
|
||||||
|
pub z: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vec3 {
|
||||||
|
pub const ZERO: Vec3 = Vec3::new(0.0, 0.0, 0.0);
|
||||||
|
pub const ONE: Vec3 = Vec3::new(1.0, 1.0, 1.0);
|
||||||
|
|
||||||
|
pub const fn new(x: f64, y: f64, z: f64) -> Self {
|
||||||
|
Vec3 { x, y, z }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn splat(v: f64) -> Self {
|
||||||
|
Vec3::new(v, v, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dot(self, o: Vec3) -> f64 {
|
||||||
|
self.x * o.x + self.y * o.y + self.z * o.z
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cross(self, o: Vec3) -> Vec3 {
|
||||||
|
Vec3::new(
|
||||||
|
self.y * o.z - self.z * o.y,
|
||||||
|
self.z * o.x - self.x * o.z,
|
||||||
|
self.x * o.y - self.y * o.x,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length(self) -> f64 {
|
||||||
|
self.dot(self).sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normalize(self) -> Vec3 {
|
||||||
|
let n = self.length();
|
||||||
|
if n > 1e-12 {
|
||||||
|
self / n
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Component-wise product.
|
||||||
|
pub fn mul_elem(self, o: Vec3) -> Vec3 {
|
||||||
|
Vec3::new(self.x * o.x, self.y * o.y, self.z * o.z)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map(self, f: impl Fn(f64) -> f64) -> Vec3 {
|
||||||
|
Vec3::new(f(self.x), f(self.y), f(self.z))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mean(self) -> f64 {
|
||||||
|
(self.x + self.y + self.z) / 3.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for Vec3 {
|
||||||
|
type Output = f64;
|
||||||
|
fn index(&self, i: usize) -> &f64 {
|
||||||
|
match i {
|
||||||
|
0 => &self.x,
|
||||||
|
1 => &self.y,
|
||||||
|
_ => &self.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
fn add(self, o: Vec3) -> Vec3 {
|
||||||
|
Vec3::new(self.x + o.x, self.y + o.y, self.z + o.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign for Vec3 {
|
||||||
|
fn add_assign(&mut self, o: Vec3) {
|
||||||
|
*self = *self + o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
fn sub(self, o: Vec3) -> Vec3 {
|
||||||
|
Vec3::new(self.x - o.x, self.y - o.y, self.z - o.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Neg for Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
fn neg(self) -> Vec3 {
|
||||||
|
Vec3::new(-self.x, -self.y, -self.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<f64> for Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
fn mul(self, s: f64) -> Vec3 {
|
||||||
|
Vec3::new(self.x * s, self.y * s, self.z * s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<Vec3> for f64 {
|
||||||
|
type Output = Vec3;
|
||||||
|
fn mul(self, v: Vec3) -> Vec3 {
|
||||||
|
v * self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div<f64> for Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
fn div(self, s: f64) -> Vec3 {
|
||||||
|
Vec3::new(self.x / s, self.y / s, self.z / s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DivAssign<f64> for Vec3 {
|
||||||
|
fn div_assign(&mut self, s: f64) {
|
||||||
|
*self = *self / s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reflect(d: Vec3, n: Vec3) -> Vec3 {
|
||||||
|
d - 2.0 * d.dot(n) * n
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refract(incident: Vec3, normal: Vec3, eta: f64) -> Option<Vec3> {
|
||||||
|
let cos_i = (-incident.dot(normal)).min(1.0);
|
||||||
|
let sin_t_sq = eta * eta * (1.0 - cos_i * cos_i);
|
||||||
|
if sin_t_sq > 1.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let cos_t = (1.0 - sin_t_sq).max(0.0).sqrt();
|
||||||
|
Some(eta * incident + (eta * cos_i - cos_t) * normal)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn schlick(cos: f64, ref_idx: f64) -> f64 {
|
||||||
|
let r0 = (1.0 - ref_idx) / (1.0 + ref_idx);
|
||||||
|
let r0 = r0 * r0;
|
||||||
|
r0 + (1.0 - r0) * (1.0 - cos).powi(5)
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue