commit 500314755c31621ac5460f34d7bf87bc436d9b9d Author: Elise Amber Katze Date: Sun Jun 30 11:04:17 2024 +0200 Initial book done diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b1fa5ea --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,864 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "image" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" +dependencies = [ + "bytemuck", + "byteorder", + "num-traits", + "png", +] + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "plotters" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" + +[[package]] +name = "plotters-svg" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rsrt" +version = "0.1.0" +dependencies = [ + "criterion", + "fastrand", + "image", + "rayon", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "syn" +version = "2.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b491b44 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "rsrt" +authors = ["Elise Amber Katze "] +version = "0.1.0" +edition = "2021" + +[dependencies] +fastrand = "2.1.0" +image = { version = "0.25.1", default-features = false, features = ["png"] } +rayon = "1.10.0" +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } + +[dev-dependencies] +criterion = "0.5.1" + +[[bench]] +name = "cover" +harness = false diff --git a/README.md b/README.md new file mode 100644 index 0000000..a23b8e5 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# rsrt + +A raytracer built using https://raytracing.github.io as a follow up to my last attempt back in 2020 https://github.com/EliseZeroTwo/rs-ytrace/. + +## Renders + +### 2024-06-29 + +A slightly modified version of the cover of the first book "Ray Tracing in One Weekend". + +![A slightly modified version of the cover from the first book "Ray Tracing in One Weekend"](./renders/1st-cover-4k.png) + +```rs +let mut builder = Raytracer::builder() + .image_size(3840, 2160) + .samples_per_pixel(500) + .max_depth(50) + .vertical_fov(20.0) + .look_from(13.0, 2.0, 3.0) + .look_at(0.0, 0.0, 0.0) + .vup(0.0, 1.0, 0.0) + .defocus_angle(0.6) + .focus_dist(10.0) + .with_sphere(0.0, -1000.0, 0.0, 1000.0) + .lambertian(1.0, 0.6, 0.7); + +for a in -11..11 { + for b in -11..11 { + let choose_mat = random_f64(); + let center = Point3::new(a as f64 + 0.9 * random_f64(), 0.2, b as f64 + 0.9 * random_f64()); + + if (center - Point3::new(4.0, 0.2, 0.0)).len() > 0.9 { + let shape_builder = builder.with_sphere(center.x(), center.y(), center.z(), 0.2); + + builder = if choose_mat < 0.8 { + let albedo = Color::random() * Color::random(); + shape_builder.lambertian(albedo.r(), albedo.g(), albedo.b()) + } else if choose_mat < 0.95 { + let albedo = Color::random_range(0.5, 1.0); + let fuzz = random_f64_range(0.0, 0.5); + shape_builder.metal(albedo.r(), albedo.g(), albedo.b(), fuzz) + } else { + shape_builder.dielectric(1.5) + }; + } + } +} + +let raytracer = builder + .with_sphere(0.0, 1.0, 0.0, 1.0) + .dielectric(1.5) + .with_sphere(-4.0, 1.0, 0.0, 1.0) + .lambertian(1.0, 0.5, 0.6) + .with_sphere(4.0, 1.0, 0.0, 1.0) + .metal(0.7, 0.6, 0.5, 0.0) + .finish(); +``` \ No newline at end of file diff --git a/benches/cover.rs b/benches/cover.rs new file mode 100644 index 0000000..cf3b48e --- /dev/null +++ b/benches/cover.rs @@ -0,0 +1,14 @@ +use criterion::{criterion_group, criterion_main, Criterion}; + +fn render_cover_400_225_rayon_100spp(c: &mut Criterion) { + let mut group = c.benchmark_group("render_cover_400_225_rayon_100spp_group"); + let raytracer = rsrt::cover(400, 225, 100); + group.sample_size(10); + group.bench_function("render_cover_400_225_rayon_100spp", |b| { + b.iter(|| raytracer.render_multithread_rayon()) + }); + group.finish(); +} + +criterion_group!(render_cover_400_225, render_cover_400_225_rayon_100spp); +criterion_main!(render_cover_400_225); diff --git a/renders/1st-cover-4k.png b/renders/1st-cover-4k.png new file mode 100644 index 0000000..2d6a9aa Binary files /dev/null and b/renders/1st-cover-4k.png differ diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..fe4ad2e --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,4 @@ +edition = "2021" +hard_tabs = true +max_width = 100 +use_small_heuristics = "Max" \ No newline at end of file diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 0000000..a61808c --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,128 @@ +use crate::{ + hittable::{sphere::Sphere, HittableList, Object}, + material::{dielectric::Dielectric, lambertian::Lambertian, metal::Metal, Materials}, + raytracer::Raytracer, + vec3::{Color, Point3, Vec3}, +}; + +pub struct SphereBuilder { + child: RaytracerBuilder, + center: Point3, + radius: f64, +} + +impl SphereBuilder { + pub fn dielectric(self, refraction_index: f64) -> RaytracerBuilder { + self.with_material(Materials::Dielectric(Dielectric::new(refraction_index))) + } + + pub fn lambertian(self, albedo_r: f64, albedo_g: f64, albedo_b: f64) -> RaytracerBuilder { + self.with_material(Materials::Lambertian(Lambertian::new(Color::new( + albedo_r, albedo_g, albedo_b, + )))) + } + + pub fn metal(self, albedo_r: f64, albedo_g: f64, albedo_b: f64, fuzz: f64) -> RaytracerBuilder { + self.with_material(Materials::Metal(Metal::new( + Color::new(albedo_r, albedo_g, albedo_b), + fuzz, + ))) + } + + pub fn with_material(self, material: Materials) -> RaytracerBuilder { + let mut builder = self.child; + builder.world.add(Object::Sphere(Sphere::new(self.center, self.radius, material))); + builder + } +} + +pub struct RaytracerBuilder { + pub world: HittableList, + + pub image_height: usize, + pub image_width: usize, + pub samples_per_pixel: usize, + pub max_depth: usize, + + pub defocus_angle: f64, + pub focus_dist: f64, + + pub vertical_fov: f64, + pub look_from: Point3, + pub look_at: Point3, + pub vup: Vec3, +} + +impl Default for RaytracerBuilder { + fn default() -> Self { + Self { + world: HittableList::new(), + image_height: 1080, + image_width: 1920, + samples_per_pixel: 100, + max_depth: 50, + vertical_fov: 90.0, + look_from: Point3::new(0.0, 0.0, 0.0), + look_at: Point3::new(0.0, 0.0, -1.0), + vup: Point3::new(0.0, 1.0, 0.0), + defocus_angle: 0.0, + focus_dist: 10.0, + } + } +} + +impl RaytracerBuilder { + pub fn image_size(mut self, width: usize, height: usize) -> Self { + self.image_width = width; + self.image_height = height; + self + } + + pub fn look_from(mut self, x: f64, y: f64, z: f64) -> Self { + self.look_from = Point3::new(x, y, z); + self + } + + pub fn look_at(mut self, x: f64, y: f64, z: f64) -> Self { + self.look_at = Point3::new(x, y, z); + self + } + + pub fn vup(mut self, x: f64, y: f64, z: f64) -> Self { + self.vup = Point3::new(x, y, z); + self + } + + pub fn max_depth(mut self, depth: usize) -> Self { + self.max_depth = depth; + self + } + + pub fn samples_per_pixel(mut self, samples_per_pixel: usize) -> Self { + self.samples_per_pixel = samples_per_pixel; + self + } + + pub fn vertical_fov(mut self, vertical_fov: f64) -> Self { + self.vertical_fov = vertical_fov; + self + } + + pub fn defocus_angle(mut self, defocus_angle: f64) -> Self { + self.defocus_angle = defocus_angle; + self + } + + pub fn focus_dist(mut self, focus_dist: f64) -> Self { + self.focus_dist = focus_dist; + self + } + + pub fn with_sphere(self, x: f64, y: f64, z: f64, radius: f64) -> SphereBuilder { + SphereBuilder { child: self, center: Point3::new(x, y, z), radius } + } + + pub fn finish(self) -> Raytracer { + self.into() + } +} diff --git a/src/hittable.rs b/src/hittable.rs new file mode 100644 index 0000000..a58995d --- /dev/null +++ b/src/hittable.rs @@ -0,0 +1,79 @@ +pub mod sphere; + +use crate::{ + interval::Interval, + material::Materials, + ray::Ray, + vec3::{Point3, Vec3}, +}; + +#[derive(Debug, Clone)] +pub struct HitRecord { + pub p: Point3, + pub normal: Vec3, + pub t: f64, + pub front_face: bool, + pub mat: Materials, +} + +impl HitRecord { + pub fn new(t: f64, p: Point3, outward_normal: Vec3, mat: Materials, ray: &Ray) -> Self { + let front_face = ray.direction().dot(&outward_normal) < 0.0; + Self { + p, + normal: match front_face { + true => outward_normal, + false => -outward_normal, + }, + t, + front_face, + mat, + } + } +} + +pub trait Hittable { + fn hit(&self, ray: &Ray, ray_t: &Interval) -> Option; +} + +pub enum Object { + Sphere(sphere::Sphere), +} + +impl Hittable for Object { + fn hit(&self, ray: &Ray, ray_t: &Interval) -> Option { + match self { + Object::Sphere(sphere) => sphere.hit(ray, ray_t), + } + } +} + +pub struct HittableList { + objects: Vec, +} + +impl HittableList { + pub fn new() -> Self { + Self { objects: Vec::with_capacity(128) } + } + + pub fn add(&mut self, object: Object) { + self.objects.push(object); + } +} + +impl Hittable for HittableList { + fn hit(&self, ray: &Ray, ray_t: &Interval) -> Option { + let mut out = None; + let mut interval = Interval::new(ray_t.min, ray_t.max); + + for object in &self.objects { + if let Some(hit_record) = object.hit(ray, &interval) { + interval.max = hit_record.t; + out = Some(hit_record); + } + } + + out + } +} diff --git a/src/hittable/sphere.rs b/src/hittable/sphere.rs new file mode 100644 index 0000000..64edaa9 --- /dev/null +++ b/src/hittable/sphere.rs @@ -0,0 +1,43 @@ +use crate::{interval::Interval, material::Materials, vec3::Point3}; + +use super::{HitRecord, Hittable}; + +pub struct Sphere { + center: Point3, + radius: f64, + material: Materials, +} + +impl Sphere { + pub fn new(center: Point3, radius: f64, material: Materials) -> Self { + Self { center, radius, material } + } +} + +impl Hittable for Sphere { + fn hit(&self, ray: &crate::ray::Ray, ray_t: &Interval) -> Option { + let oc = self.center - *ray.origin(); + let a = ray.direction().len_squared(); + let h = ray.direction().dot(&oc); + let c = oc.len_squared() - self.radius * self.radius; + let discriminant = h * h - a * c; + + if discriminant < 0.0 { + return None; + } + + let sqrtd = discriminant.sqrt(); + + let mut root = (h - sqrtd) / a; + if !ray_t.surrounds(root) { + root = (h + sqrtd) / a; + if !ray_t.surrounds(root) { + return None; + } + } + + let p = ray.at(root); + let outward_normal = (p - self.center) / self.radius; + Some(HitRecord::new(root, p, outward_normal, self.material, ray)) + } +} diff --git a/src/interval.rs b/src/interval.rs new file mode 100644 index 0000000..4ac77f1 --- /dev/null +++ b/src/interval.rs @@ -0,0 +1,30 @@ +pub struct Interval { + pub min: f64, + pub max: f64, +} + +impl Interval { + pub const fn new(min: f64, max: f64) -> Self { + Self { min, max } + } + + pub fn size(&self) -> f64 { + self.max - self.min + } + + pub fn contains(&self, value: f64) -> bool { + self.min <= value && value <= self.max + } + + pub fn surrounds(&self, value: f64) -> bool { + self.min < value && value < self.max + } + + pub fn clamp(&self, value: f64) -> f64 { + value.clamp(self.min, self.max) + } +} + +pub const EMPTY: Interval = Interval::new(f64::INFINITY, -f64::INFINITY); +pub const WORLD: Interval = Interval::new(0.001, f64::INFINITY); +pub const UNIVERSE: Interval = Interval::new(-f64::INFINITY, f64::INFINITY); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..22c4ead --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,60 @@ +#![feature(portable_simd)] + +use raytracer::{random_f64, random_f64_range, Raytracer}; +use vec3::{Color, Point3}; + +pub mod builder; +pub mod hittable; +pub mod interval; +pub mod material; +pub mod out; +pub mod ray; +pub mod raytracer; +pub mod vec3; + +pub fn cover(width: usize, height: usize, spp: usize) -> Raytracer { + let mut builder = Raytracer::builder() + .image_size(width, height) + .samples_per_pixel(spp) + .max_depth(50) + .vertical_fov(20.0) + .look_from(13.0, 2.0, 3.0) + .look_at(0.0, 0.0, 0.0) + .vup(0.0, 1.0, 0.0) + .defocus_angle(0.6) + .focus_dist(10.0) + .with_sphere(0.0, -1000.0, 0.0, 1000.0) + .lambertian(1.0, 0.6, 0.7); + + for a in -11..11 { + for b in -11..11 { + let choose_mat = random_f64(); + let center = + Point3::new(a as f64 + 0.9 * random_f64(), 0.2, b as f64 + 0.9 * random_f64()); + + if (center - Point3::new(4.0, 0.2, 0.0)).len() > 0.9 { + let shape_builder = builder.with_sphere(center.x(), center.y(), center.z(), 0.2); + + builder = if choose_mat < 0.8 { + let albedo = Color::random() * Color::random(); + shape_builder.lambertian(albedo.r(), albedo.g(), albedo.b()) + } else if choose_mat < 0.95 { + let albedo = Color::random_range(0.5, 1.0); + let fuzz = random_f64_range(0.0, 0.5); + shape_builder.metal(albedo.r(), albedo.g(), albedo.b(), fuzz) + } else { + shape_builder.dielectric(1.5) + }; + } + } + } + + builder + .with_sphere(0.0, 1.0, 0.0, 1.0) + .dielectric(1.5) + .with_sphere(-4.0, 1.0, 0.0, 1.0) + .lambertian(1.0, 0.5, 0.6) + .with_sphere(4.0, 1.0, 0.0, 1.0) + .metal(0.7, 0.6, 0.5, 0.0) + .finish() +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..69b4df3 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,16 @@ +use rsrt::{cover, out}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +fn main() { + const WIDTH: usize = 1920; + const HEIGHT: usize = 1080; + + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with(tracing_subscriber::EnvFilter::from_default_env()) + .init(); + + let image = cover(WIDTH, HEIGHT, 500).render(); + + out::write_image("./image.png", &image[..], WIDTH as u32, HEIGHT as u32).unwrap(); +} diff --git a/src/material.rs b/src/material.rs new file mode 100644 index 0000000..20a516a --- /dev/null +++ b/src/material.rs @@ -0,0 +1,30 @@ +pub mod dielectric; +pub mod lambertian; +pub mod metal; + +use dielectric::Dielectric; +use lambertian::Lambertian; +use metal::Metal; + +use crate::{hittable::HitRecord, ray::Ray, vec3::Color}; + +pub trait Material { + fn scatter(&self, r_in: &Ray, hit_record: &HitRecord) -> Option<(Color, Ray)>; +} + +#[derive(Debug, Clone, Copy)] +pub enum Materials { + Lambertian(Lambertian), + Metal(Metal), + Dielectric(Dielectric), +} + +impl Material for Materials { + fn scatter(&self, r_in: &Ray, hit_record: &HitRecord) -> Option<(Color, Ray)> { + match self { + Materials::Lambertian(m) => m.scatter(r_in, hit_record), + Materials::Metal(m) => m.scatter(r_in, hit_record), + Materials::Dielectric(m) => m.scatter(r_in, hit_record), + } + } +} diff --git a/src/material/dielectric.rs b/src/material/dielectric.rs new file mode 100644 index 0000000..63760c1 --- /dev/null +++ b/src/material/dielectric.rs @@ -0,0 +1,47 @@ +use crate::{ray::Ray, raytracer::random_f64, vec3::Color}; + +use super::Material; + +#[derive(Debug, Clone, Copy)] +pub struct Dielectric { + refraction_index: f64, +} + +impl Dielectric { + pub fn new(refraction_index: f64) -> Self { + Self { refraction_index } + } + + fn reflectance(refraction_index: f64, cos: f64) -> f64 { + let mut r0 = (1.0 - refraction_index) / (1.0 + refraction_index); + r0 *= r0; + r0 + (1.0 - r0) * (1.0 - cos).powi(5) + } +} + +impl Material for Dielectric { + fn scatter( + &self, + r_in: &crate::ray::Ray, + hit_record: &crate::hittable::HitRecord, + ) -> Option<(crate::vec3::Color, crate::ray::Ray)> { + let attenuation = Color::new(1.0, 1.0, 1.0); + let ri = match hit_record.front_face { + true => 1.0 / self.refraction_index, + false => self.refraction_index, + }; + + let unit_direction = r_in.direction().unit_vector(); + let cos_theta = (-unit_direction).dot(&hit_record.normal).min(1.0); + let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); + + let cannot_refract = ri * sin_theta > 1.0; + + let direction = match cannot_refract || Self::reflectance(cos_theta, ri) > random_f64() { + true => unit_direction.reflect(hit_record.normal), + false => unit_direction.refract(hit_record.normal, ri), + }; + + Some((attenuation, Ray::new(hit_record.p, direction))) + } +} diff --git a/src/material/lambertian.rs b/src/material/lambertian.rs new file mode 100644 index 0000000..e1deed1 --- /dev/null +++ b/src/material/lambertian.rs @@ -0,0 +1,33 @@ +use crate::{ + ray::Ray, + vec3::{Color, Vec3}, +}; + +use super::Material; + +#[derive(Debug, Clone, Copy)] +pub struct Lambertian { + albedo: Color, +} + +impl Lambertian { + pub const fn new(albedo: Color) -> Self { + Self { albedo } + } +} + +impl Material for Lambertian { + fn scatter( + &self, + _r_in: &crate::ray::Ray, + hit_record: &crate::hittable::HitRecord, + ) -> Option<(Color, Ray)> { + let mut scatter_direction = hit_record.normal + Vec3::random_unit_vector(); + + if scatter_direction.near_zero() { + scatter_direction = hit_record.normal; + } + + Some((self.albedo, Ray::new(hit_record.p, scatter_direction))) + } +} diff --git a/src/material/metal.rs b/src/material/metal.rs new file mode 100644 index 0000000..1dcaf2e --- /dev/null +++ b/src/material/metal.rs @@ -0,0 +1,34 @@ +use crate::{ + ray::Ray, + vec3::{Color, Vec3}, +}; + +use super::Material; + +#[derive(Debug, Clone, Copy)] +pub struct Metal { + albedo: Color, + fuzz: f64, +} + +impl Metal { + pub fn new(albedo: Color, fuzz: f64) -> Self { + Self { albedo, fuzz: fuzz.min(1.0) } + } +} + +impl Material for Metal { + fn scatter( + &self, + r_in: &crate::ray::Ray, + hit_record: &crate::hittable::HitRecord, + ) -> Option<(Color, Ray)> { + let reflected = r_in.direction().reflect(hit_record.normal).unit_vector() + + (self.fuzz * Vec3::random_unit_vector()); + let scattered = Ray::new(hit_record.p, reflected); + match scattered.direction().dot(&hit_record.normal) > 0.0 { + true => Some((self.albedo, scattered)), + false => None, + } + } +} diff --git a/src/out.rs b/src/out.rs new file mode 100644 index 0000000..5f50b29 --- /dev/null +++ b/src/out.rs @@ -0,0 +1,40 @@ +use std::path::Path; + +use crate::{interval::Interval, vec3::Color}; + +fn linear_to_gamma(linear_component: f64) -> f64 { + if linear_component > 0.0 { + return linear_component.sqrt(); + } + + 0.0 +} + +pub fn write_image>( + path: P, + image: &[Color], + width: u32, + height: u32, +) -> Result<(), image::ImageError> { + let mut out = image::ImageBuffer::new(width, height); + + for (x, y, pixel) in out.enumerate_pixels_mut() { + let index = (y * width) + x; + + let r = linear_to_gamma(image[index as usize].r()); + let g = linear_to_gamma(image[index as usize].g()); + let b = linear_to_gamma(image[index as usize].b()); + + const INTENSITY: Interval = Interval::new(0.000, 0.999); + + *pixel = image::Rgb([ + (INTENSITY.clamp(r) * 256.0) as u8, + (INTENSITY.clamp(g) * 256.0) as u8, + (INTENSITY.clamp(b) * 256.0) as u8, + ]); + } + + out.save(path)?; + + Ok(()) +} diff --git a/src/ray.rs b/src/ray.rs new file mode 100644 index 0000000..f8460f7 --- /dev/null +++ b/src/ray.rs @@ -0,0 +1,24 @@ +use crate::vec3::{Point3, Vec3}; + +pub struct Ray { + origin: Point3, + direction: Vec3, +} + +impl Ray { + pub fn new(origin: Point3, direction: Vec3) -> Self { + Self { origin, direction } + } + + pub fn at(&self, t: f64) -> Point3 { + self.origin + (t * self.direction) + } + + pub const fn origin(&self) -> &Point3 { + &self.origin + } + + pub const fn direction(&self) -> &Vec3 { + &self.direction + } +} diff --git a/src/raytracer.rs b/src/raytracer.rs new file mode 100644 index 0000000..830e3db --- /dev/null +++ b/src/raytracer.rs @@ -0,0 +1,174 @@ +use rayon::iter::{IntoParallelIterator, ParallelIterator}; + +use crate::{ + builder::RaytracerBuilder, + hittable::{Hittable, HittableList, Object}, + interval::WORLD, + material::Material, + ray::Ray, + vec3::{Color, Point3, Vec3}, +}; + +pub struct Raytracer { + world: HittableList, + + samples_per_pixel: usize, + max_depth: usize, + pixel_samples_scale: f64, + + image_width: usize, + image_height: usize, + + center: Point3, + pixel00_loc: Point3, + pixel_delta_u: Vec3, + pixel_delta_v: Vec3, + + defocus_angle: f64, + defocus_disk_u: Vec3, + defocus_disk_v: Vec3, +} + +impl From for Raytracer { + fn from(config: RaytracerBuilder) -> Self { + let aspect_ratio: f64 = config.image_width as f64 / config.image_height as f64; + let theta = config.vertical_fov.to_radians(); + let h = (theta / 2.0).tan(); + let viewport_height = 2.0 * h * config.focus_dist; + let viewport_width = viewport_height as f64 * aspect_ratio; + + let center = config.look_from; + + let w = (config.look_from - config.look_at).unit_vector(); + let u = config.vup.cross(&w).unit_vector(); + let v = w.cross(&u); + + let viewport_u = viewport_width * u; + let viewport_v = viewport_height * -v; + + let pixel_delta_u = viewport_u / config.image_width as f64; + let pixel_delta_v = viewport_v / config.image_height as f64; + + let viewport_upper_left = + center - (config.focus_dist * w) - (viewport_u / 2.0) - (viewport_v / 2.0); + let pixel00_loc = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v); + + let defocus_radius = config.focus_dist * (config.defocus_angle / 2.0).to_radians().tan(); + let defocus_disk_u = u * defocus_radius; + let defocus_disk_v = v * defocus_radius; + + Self { + world: config.world, + samples_per_pixel: config.samples_per_pixel, + max_depth: config.max_depth, + pixel_samples_scale: 1.0 / config.samples_per_pixel as f64, + image_width: config.image_width, + image_height: config.image_height, + center, + pixel00_loc, + pixel_delta_u, + pixel_delta_v, + defocus_angle: config.defocus_angle, + defocus_disk_u, + defocus_disk_v, + } + } +} + +impl Raytracer { + pub fn builder() -> RaytracerBuilder { + RaytracerBuilder::default() + } + + pub fn add(&mut self, object: Object) { + self.world.add(object); + } + + fn sample_square() -> Vec3 { + Vec3::new(random_f64() - 0.5, random_f64() - 0.5, 0.0) + } + + fn defocus_disk_sample(&self) -> Point3 { + let p = Point3::random_in_unit_disk(); + self.center + (p[0] * self.defocus_disk_u) + (p[1] * self.defocus_disk_v) + } + + fn get_ray(&self, i: usize, j: usize) -> Ray { + let offset = Self::sample_square(); + let pixel_sample = self.pixel00_loc + + ((i as f64 + offset.x()) * self.pixel_delta_u) + + ((j as f64 + offset.y()) * self.pixel_delta_v); + + let origin = match self.defocus_angle <= 0.0 { + true => self.center, + false => self.defocus_disk_sample(), + }; + let direction = pixel_sample - origin; + + Ray::new(origin, direction) + } + + fn ray_color(&self, depth: usize, ray: &Ray) -> Color { + if depth == 0 { + return Color::default(); + } + + if let Some(hit_record) = self.world.hit(ray, &WORLD) { + if let Some((attenuation, scattered)) = hit_record.mat.scatter(ray, &hit_record) { + return attenuation * self.ray_color(depth - 1, &scattered); + } + return Color::default(); + } + + let unit_direction = ray.direction().unit_vector(); + let a = 0.5 * (unit_direction.y() + 1.0); + ((1.0 - a) * Color::new(1.0, 1.0, 1.0)) + (a * Color::new(0.5, 0.7, 1.0)) + } + + pub fn render_multithread_rayon(&self) -> Vec { + (0..self.image_height) + .into_par_iter() + .flat_map(|y| (0..self.image_width).into_par_iter().map(move |x| (x, y))) + .map(|(j, i)| { + let mut pixel_color = Vec3::default(); + + for _ in 0..self.samples_per_pixel { + let ray = self.get_ray(i, j); + pixel_color += self.ray_color(self.max_depth, &ray); + } + + pixel_color * self.pixel_samples_scale + }) + .collect() + } + + pub fn render(&self) -> Vec { + let mut out = Vec::with_capacity(self.image_height * self.image_width); + + for j in 0..self.image_height { + tracing::debug!("Scanlines remaining: {}", self.image_height - j); + for i in 0..self.image_width { + let mut pixel_color = Vec3::default(); + + for _ in 0..self.samples_per_pixel { + let ray = self.get_ray(i, j); + pixel_color += self.ray_color(self.max_depth, &ray); + } + + out.push(pixel_color * self.pixel_samples_scale); + } + } + + out + } +} + +#[inline(always)] +pub fn random_f64() -> f64 { + fastrand::f64() +} + +#[inline(always)] +pub fn random_f64_range(min: f64, max: f64) -> f64 { + min + (max - min) * random_f64() +} diff --git a/src/vec3.rs b/src/vec3.rs new file mode 100644 index 0000000..c8a6f96 --- /dev/null +++ b/src/vec3.rs @@ -0,0 +1,250 @@ +use core::ops::{Index, IndexMut}; + +use crate::raytracer::{random_f64, random_f64_range}; + +#[derive(Debug, Clone, Copy, Default)] +pub struct Vec3([f64; 3]); + +pub type Point3 = Vec3; +pub type Color = Vec3; + +impl Vec3 { + pub const fn new(x: f64, y: f64, z: f64) -> Self { + Self([x, y, z]) + } + + pub const fn x(&self) -> f64 { + self.0[0] + } + + pub const fn y(&self) -> f64 { + self.0[1] + } + + pub const fn z(&self) -> f64 { + self.0[2] + } + + pub const fn r(&self) -> f64 { + self.x() + } + + pub const fn g(&self) -> f64 { + self.y() + } + + pub const fn b(&self) -> f64 { + self.z() + } + + pub fn len(&self) -> f64 { + self.len_squared().sqrt() + } + + pub fn len_squared(&self) -> f64 { + self.dot(self) + } + + pub fn dot(&self, rhs: &Self) -> f64 { + (self.0[0] * rhs[0]) + (self.0[1] * rhs[1]) + (self.0[2] * rhs[2]) + } + + pub fn cross(&self, rhs: &Self) -> Self { + Self::new( + self[1] * rhs[2] - self[2] * rhs[1], + self[2] * rhs[0] - self[0] * rhs[2], + self[0] * rhs[1] - self[1] * rhs[0], + ) + } + + pub fn unit_vector(&self) -> Self { + *self / self.len() + } + + pub fn random() -> Self { + Self::new(random_f64(), random_f64(), random_f64()) + } + + pub fn random_range(min: f64, max: f64) -> Self { + Self::new( + random_f64_range(min, max), + random_f64_range(min, max), + random_f64_range(min, max), + ) + } + + pub fn random_in_unit_sphere() -> Self { + loop { + let p = Self::random_range(-1.0, 1.0); + if p.len_squared() < 1.0 { + return p; + } + } + } + + pub fn random_in_unit_disk() -> Self { + loop { + let p = Self::new(random_f64_range(-1.0, 1.0), random_f64_range(-1.0, 1.0), 0.0); + if p.len_squared() < 1.0 { + return p; + } + } + } + + pub fn random_unit_vector() -> Self { + Self::random_in_unit_sphere().unit_vector() + } + + pub fn random_on_hemisphere(&self) -> Self { + let on_unit_sphere = Self::random_unit_vector(); + + if on_unit_sphere.dot(self) > 0.0 { + on_unit_sphere + } else { + -on_unit_sphere + } + } + + pub fn near_zero(&self) -> bool { + const S: f64 = 1e-8; + + self[0].abs() < S && self[1].abs() < S && self[2].abs() < S + } + + pub fn reflect(self, n: Self) -> Self { + self - 2.0 * self.dot(&n) * n + } + + pub fn refract(self, n: Self, etai_over_etat: f64) -> Self { + let cos_theta = (-self).dot(&n).min(1.0); + let r_out_perpendicular = etai_over_etat * (self + cos_theta * n); + let r_out_parallel = -((1.0 - r_out_perpendicular.len_squared()).abs().sqrt()) * n; + r_out_perpendicular + r_out_parallel + } +} + +impl Index for Vec3 { + type Output = f64; + + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} + +impl IndexMut for Vec3 { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] + } +} + +impl core::ops::Add for Vec3 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self::new(self[0] + rhs[0], self[1] + rhs[1], self[2] + rhs[2]) + } +} + +impl core::ops::AddAssign for &mut Vec3 { + fn add_assign(&mut self, rhs: Vec3) { + self[0] += rhs[0]; + self[1] += rhs[1]; + self[2] += rhs[2]; + } +} + +impl core::ops::AddAssign for Vec3 { + fn add_assign(&mut self, rhs: Self) { + self[0] += rhs[0]; + self[1] += rhs[1]; + self[2] += rhs[2]; + } +} + +impl core::ops::Sub for Vec3 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self::new(self[0] - rhs[0], self[1] - rhs[1], self[2] - rhs[2]) + } +} + +impl core::ops::Neg for Vec3 { + type Output = Self; + + fn neg(self) -> Self::Output { + Self::new(-self[0], -self[1], -self[2]) + } +} + +impl core::ops::Mul for Vec3 { + type Output = Self; + + fn mul(self, rhs: f64) -> Self::Output { + Vec3::new(self[0] * rhs, self[1] * rhs, self[2] * rhs) + } +} + +impl core::ops::MulAssign for &mut Vec3 { + fn mul_assign(&mut self, rhs: f64) { + self[0] *= rhs; + self[1] *= rhs; + self[2] *= rhs; + } +} + +impl core::ops::Mul for f64 { + type Output = Vec3; + + fn mul(self, rhs: Vec3) -> Self::Output { + rhs * self + } +} + +impl core::ops::Mul for Vec3 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + Self::new(self[0] * rhs[0], self[1] * rhs[1], self[2] * rhs[2]) + } +} + +impl core::ops::MulAssign for &mut Vec3 { + fn mul_assign(&mut self, rhs: Vec3) { + self[0] *= rhs[0]; + self[1] *= rhs[1]; + self[2] *= rhs[2]; + } +} + +impl core::ops::MulAssign for Vec3 { + fn mul_assign(&mut self, rhs: Self) { + self[0] *= rhs[0]; + self[1] *= rhs[1]; + self[2] *= rhs[2]; + } +} + +impl core::ops::Div for Vec3 { + type Output = Self; + + fn div(self, rhs: f64) -> Self::Output { + (1. / rhs) * self + } +} + +impl core::ops::Div for Vec3 { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output { + Self::new(self[0] / rhs[0], self[1] / rhs[1], self[2] / rhs[2]) + } +} + +impl core::ops::DivAssign for Vec3 { + fn div_assign(&mut self, rhs: Self) { + self[0] /= rhs[0]; + self[1] /= rhs[1]; + self[2] /= rhs[2]; + } +}