├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── assets └── textures │ └── skybox_001 │ ├── purp_back.png │ ├── purp_bottom.png │ ├── purp_front.png │ ├── purp_left.png │ ├── purp_right.png │ └── purp_top.png ├── build.rs ├── rrq.bat └── src ├── byte_to_f32.rs ├── camera.rs ├── chunk.rs ├── cubemap.rs ├── instack.rs ├── lib.rs ├── main.rs ├── math.rs ├── open_simplex.rs ├── perlin.rs └── ray.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /*.png 3 | /output -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 10 | 11 | [[package]] 12 | name = "aligned-vec" 13 | version = "0.5.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" 16 | 17 | [[package]] 18 | name = "anyhow" 19 | version = "1.0.96" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" 22 | 23 | [[package]] 24 | name = "arbitrary" 25 | version = "1.4.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" 28 | 29 | [[package]] 30 | name = "arg_enum_proc_macro" 31 | version = "0.3.4" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" 34 | dependencies = [ 35 | "proc-macro2", 36 | "quote", 37 | "syn", 38 | ] 39 | 40 | [[package]] 41 | name = "arrayvec" 42 | version = "0.7.6" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 45 | 46 | [[package]] 47 | name = "autocfg" 48 | version = "1.4.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 51 | 52 | [[package]] 53 | name = "av1-grain" 54 | version = "0.2.3" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" 57 | dependencies = [ 58 | "anyhow", 59 | "arrayvec", 60 | "log", 61 | "nom", 62 | "num-rational", 63 | "v_frame", 64 | ] 65 | 66 | [[package]] 67 | name = "avif-serialize" 68 | version = "0.8.3" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" 71 | dependencies = [ 72 | "arrayvec", 73 | ] 74 | 75 | [[package]] 76 | name = "bit_field" 77 | version = "0.10.2" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" 80 | 81 | [[package]] 82 | name = "bitflags" 83 | version = "1.3.2" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 86 | 87 | [[package]] 88 | name = "bitflags" 89 | version = "2.8.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 92 | 93 | [[package]] 94 | name = "bitstream-io" 95 | version = "2.6.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" 98 | 99 | [[package]] 100 | name = "block-buffer" 101 | version = "0.10.4" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 104 | dependencies = [ 105 | "generic-array", 106 | ] 107 | 108 | [[package]] 109 | name = "built" 110 | version = "0.7.7" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" 113 | 114 | [[package]] 115 | name = "bumpalo" 116 | version = "3.17.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 119 | 120 | [[package]] 121 | name = "bytemuck" 122 | version = "1.22.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" 125 | 126 | [[package]] 127 | name = "byteorder-lite" 128 | version = "0.1.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" 131 | 132 | [[package]] 133 | name = "cc" 134 | version = "1.2.16" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" 137 | dependencies = [ 138 | "jobserver", 139 | "libc", 140 | "shlex", 141 | ] 142 | 143 | [[package]] 144 | name = "cfg-expr" 145 | version = "0.15.8" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" 148 | dependencies = [ 149 | "smallvec", 150 | "target-lexicon", 151 | ] 152 | 153 | [[package]] 154 | name = "cfg-if" 155 | version = "1.0.0" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 158 | 159 | [[package]] 160 | name = "color_quant" 161 | version = "1.1.0" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 164 | 165 | [[package]] 166 | name = "console" 167 | version = "0.15.11" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 170 | dependencies = [ 171 | "encode_unicode", 172 | "libc", 173 | "once_cell", 174 | "unicode-width", 175 | "windows-sys", 176 | ] 177 | 178 | [[package]] 179 | name = "cpufeatures" 180 | version = "0.2.17" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 183 | dependencies = [ 184 | "libc", 185 | ] 186 | 187 | [[package]] 188 | name = "crc32fast" 189 | version = "1.4.2" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 192 | dependencies = [ 193 | "cfg-if", 194 | ] 195 | 196 | [[package]] 197 | name = "crossbeam-deque" 198 | version = "0.8.6" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 201 | dependencies = [ 202 | "crossbeam-epoch", 203 | "crossbeam-utils", 204 | ] 205 | 206 | [[package]] 207 | name = "crossbeam-epoch" 208 | version = "0.9.18" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 211 | dependencies = [ 212 | "crossbeam-utils", 213 | ] 214 | 215 | [[package]] 216 | name = "crossbeam-utils" 217 | version = "0.8.21" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 220 | 221 | [[package]] 222 | name = "crunchy" 223 | version = "0.2.3" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" 226 | 227 | [[package]] 228 | name = "crypto-common" 229 | version = "0.1.6" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 232 | dependencies = [ 233 | "generic-array", 234 | "typenum", 235 | ] 236 | 237 | [[package]] 238 | name = "digest" 239 | version = "0.10.7" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 242 | dependencies = [ 243 | "block-buffer", 244 | "crypto-common", 245 | ] 246 | 247 | [[package]] 248 | name = "either" 249 | version = "1.14.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" 252 | 253 | [[package]] 254 | name = "encode_unicode" 255 | version = "1.0.0" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 258 | 259 | [[package]] 260 | name = "equivalent" 261 | version = "1.0.2" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 264 | 265 | [[package]] 266 | name = "exr" 267 | version = "1.73.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" 270 | dependencies = [ 271 | "bit_field", 272 | "half", 273 | "lebe", 274 | "miniz_oxide", 275 | "rayon-core", 276 | "smallvec", 277 | "zune-inflate", 278 | ] 279 | 280 | [[package]] 281 | name = "fdeflate" 282 | version = "0.3.7" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" 285 | dependencies = [ 286 | "simd-adler32", 287 | ] 288 | 289 | [[package]] 290 | name = "flate2" 291 | version = "1.1.0" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" 294 | dependencies = [ 295 | "crc32fast", 296 | "miniz_oxide", 297 | ] 298 | 299 | [[package]] 300 | name = "generic-array" 301 | version = "0.14.7" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 304 | dependencies = [ 305 | "typenum", 306 | "version_check", 307 | ] 308 | 309 | [[package]] 310 | name = "getrandom" 311 | version = "0.2.15" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 314 | dependencies = [ 315 | "cfg-if", 316 | "libc", 317 | "wasi 0.11.0+wasi-snapshot-preview1", 318 | ] 319 | 320 | [[package]] 321 | name = "getrandom" 322 | version = "0.3.2" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 325 | dependencies = [ 326 | "cfg-if", 327 | "libc", 328 | "r-efi", 329 | "wasi 0.14.2+wasi-0.2.4", 330 | ] 331 | 332 | [[package]] 333 | name = "gif" 334 | version = "0.13.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" 337 | dependencies = [ 338 | "color_quant", 339 | "weezl", 340 | ] 341 | 342 | [[package]] 343 | name = "glam" 344 | version = "0.30.1" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "bf3aa70d918d2b234126ff4f850f628f172542bf0603ded26b8ee36e5e22d5f9" 347 | 348 | [[package]] 349 | name = "half" 350 | version = "2.5.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1" 353 | dependencies = [ 354 | "cfg-if", 355 | "crunchy", 356 | ] 357 | 358 | [[package]] 359 | name = "hashbrown" 360 | version = "0.15.2" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 363 | 364 | [[package]] 365 | name = "heck" 366 | version = "0.5.0" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 369 | 370 | [[package]] 371 | name = "image" 372 | version = "0.25.6" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" 375 | dependencies = [ 376 | "bytemuck", 377 | "byteorder-lite", 378 | "color_quant", 379 | "exr", 380 | "gif", 381 | "image-webp", 382 | "num-traits", 383 | "png", 384 | "qoi", 385 | "ravif", 386 | "rayon", 387 | "rgb", 388 | "tiff", 389 | "zune-core", 390 | "zune-jpeg", 391 | ] 392 | 393 | [[package]] 394 | name = "image-webp" 395 | version = "0.2.1" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" 398 | dependencies = [ 399 | "byteorder-lite", 400 | "quick-error", 401 | ] 402 | 403 | [[package]] 404 | name = "imgref" 405 | version = "1.11.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" 408 | 409 | [[package]] 410 | name = "indexmap" 411 | version = "2.7.1" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" 414 | dependencies = [ 415 | "equivalent", 416 | "hashbrown", 417 | ] 418 | 419 | [[package]] 420 | name = "indicatif" 421 | version = "0.17.11" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" 424 | dependencies = [ 425 | "console", 426 | "number_prefix", 427 | "portable-atomic", 428 | "unicode-width", 429 | "web-time", 430 | ] 431 | 432 | [[package]] 433 | name = "interpolate_name" 434 | version = "0.2.4" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" 437 | dependencies = [ 438 | "proc-macro2", 439 | "quote", 440 | "syn", 441 | ] 442 | 443 | [[package]] 444 | name = "itertools" 445 | version = "0.12.1" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 448 | dependencies = [ 449 | "either", 450 | ] 451 | 452 | [[package]] 453 | name = "jobserver" 454 | version = "0.1.32" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 457 | dependencies = [ 458 | "libc", 459 | ] 460 | 461 | [[package]] 462 | name = "jpeg-decoder" 463 | version = "0.3.1" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" 466 | 467 | [[package]] 468 | name = "js-sys" 469 | version = "0.3.77" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 472 | dependencies = [ 473 | "once_cell", 474 | "wasm-bindgen", 475 | ] 476 | 477 | [[package]] 478 | name = "lebe" 479 | version = "0.5.2" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" 482 | 483 | [[package]] 484 | name = "libc" 485 | version = "0.2.169" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 488 | 489 | [[package]] 490 | name = "libfuzzer-sys" 491 | version = "0.4.9" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" 494 | dependencies = [ 495 | "arbitrary", 496 | "cc", 497 | ] 498 | 499 | [[package]] 500 | name = "log" 501 | version = "0.4.25" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 504 | 505 | [[package]] 506 | name = "loop9" 507 | version = "0.1.5" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" 510 | dependencies = [ 511 | "imgref", 512 | ] 513 | 514 | [[package]] 515 | name = "maybe-rayon" 516 | version = "0.1.1" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" 519 | dependencies = [ 520 | "cfg-if", 521 | "rayon", 522 | ] 523 | 524 | [[package]] 525 | name = "memchr" 526 | version = "2.7.4" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 529 | 530 | [[package]] 531 | name = "minimal-lexical" 532 | version = "0.2.1" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 535 | 536 | [[package]] 537 | name = "miniz_oxide" 538 | version = "0.8.5" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" 541 | dependencies = [ 542 | "adler2", 543 | "simd-adler32", 544 | ] 545 | 546 | [[package]] 547 | name = "new_debug_unreachable" 548 | version = "1.0.6" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 551 | 552 | [[package]] 553 | name = "noise" 554 | version = "0.9.0" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "6da45c8333f2e152fc665d78a380be060eb84fad8ca4c9f7ac8ca29216cff0cc" 557 | dependencies = [ 558 | "num-traits", 559 | "rand 0.8.5", 560 | "rand_xorshift", 561 | ] 562 | 563 | [[package]] 564 | name = "nom" 565 | version = "7.1.3" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 568 | dependencies = [ 569 | "memchr", 570 | "minimal-lexical", 571 | ] 572 | 573 | [[package]] 574 | name = "noop_proc_macro" 575 | version = "0.3.0" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" 578 | 579 | [[package]] 580 | name = "num-bigint" 581 | version = "0.4.6" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 584 | dependencies = [ 585 | "num-integer", 586 | "num-traits", 587 | ] 588 | 589 | [[package]] 590 | name = "num-derive" 591 | version = "0.4.2" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" 594 | dependencies = [ 595 | "proc-macro2", 596 | "quote", 597 | "syn", 598 | ] 599 | 600 | [[package]] 601 | name = "num-integer" 602 | version = "0.1.46" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 605 | dependencies = [ 606 | "num-traits", 607 | ] 608 | 609 | [[package]] 610 | name = "num-rational" 611 | version = "0.4.2" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" 614 | dependencies = [ 615 | "num-bigint", 616 | "num-integer", 617 | "num-traits", 618 | ] 619 | 620 | [[package]] 621 | name = "num-traits" 622 | version = "0.2.19" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 625 | dependencies = [ 626 | "autocfg", 627 | ] 628 | 629 | [[package]] 630 | name = "number_prefix" 631 | version = "0.4.0" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 634 | 635 | [[package]] 636 | name = "once_cell" 637 | version = "1.20.3" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 640 | 641 | [[package]] 642 | name = "paste" 643 | version = "1.0.15" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 646 | 647 | [[package]] 648 | name = "pkg-config" 649 | version = "0.3.31" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 652 | 653 | [[package]] 654 | name = "png" 655 | version = "0.17.16" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" 658 | dependencies = [ 659 | "bitflags 1.3.2", 660 | "crc32fast", 661 | "fdeflate", 662 | "flate2", 663 | "miniz_oxide", 664 | ] 665 | 666 | [[package]] 667 | name = "portable-atomic" 668 | version = "1.11.0" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" 671 | 672 | [[package]] 673 | name = "ppv-lite86" 674 | version = "0.2.21" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 677 | dependencies = [ 678 | "zerocopy", 679 | ] 680 | 681 | [[package]] 682 | name = "proc-macro2" 683 | version = "1.0.94" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 686 | dependencies = [ 687 | "unicode-ident", 688 | ] 689 | 690 | [[package]] 691 | name = "profiling" 692 | version = "1.0.16" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" 695 | dependencies = [ 696 | "profiling-procmacros", 697 | ] 698 | 699 | [[package]] 700 | name = "profiling-procmacros" 701 | version = "1.0.16" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" 704 | dependencies = [ 705 | "quote", 706 | "syn", 707 | ] 708 | 709 | [[package]] 710 | name = "qoi" 711 | version = "0.4.1" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" 714 | dependencies = [ 715 | "bytemuck", 716 | ] 717 | 718 | [[package]] 719 | name = "quick-error" 720 | version = "2.0.1" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" 723 | 724 | [[package]] 725 | name = "quote" 726 | version = "1.0.39" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" 729 | dependencies = [ 730 | "proc-macro2", 731 | ] 732 | 733 | [[package]] 734 | name = "r-efi" 735 | version = "5.2.0" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 738 | 739 | [[package]] 740 | name = "rand" 741 | version = "0.8.5" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 744 | dependencies = [ 745 | "libc", 746 | "rand_chacha 0.3.1", 747 | "rand_core 0.6.4", 748 | ] 749 | 750 | [[package]] 751 | name = "rand" 752 | version = "0.9.0" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" 755 | dependencies = [ 756 | "rand_chacha 0.9.0", 757 | "rand_core 0.9.3", 758 | "zerocopy", 759 | ] 760 | 761 | [[package]] 762 | name = "rand_chacha" 763 | version = "0.3.1" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 766 | dependencies = [ 767 | "ppv-lite86", 768 | "rand_core 0.6.4", 769 | ] 770 | 771 | [[package]] 772 | name = "rand_chacha" 773 | version = "0.9.0" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 776 | dependencies = [ 777 | "ppv-lite86", 778 | "rand_core 0.9.3", 779 | ] 780 | 781 | [[package]] 782 | name = "rand_core" 783 | version = "0.6.4" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 786 | dependencies = [ 787 | "getrandom 0.2.15", 788 | ] 789 | 790 | [[package]] 791 | name = "rand_core" 792 | version = "0.9.3" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 795 | dependencies = [ 796 | "getrandom 0.3.2", 797 | ] 798 | 799 | [[package]] 800 | name = "rand_xorshift" 801 | version = "0.3.0" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" 804 | dependencies = [ 805 | "rand_core 0.6.4", 806 | ] 807 | 808 | [[package]] 809 | name = "rav1e" 810 | version = "0.7.1" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" 813 | dependencies = [ 814 | "arbitrary", 815 | "arg_enum_proc_macro", 816 | "arrayvec", 817 | "av1-grain", 818 | "bitstream-io", 819 | "built", 820 | "cfg-if", 821 | "interpolate_name", 822 | "itertools", 823 | "libc", 824 | "libfuzzer-sys", 825 | "log", 826 | "maybe-rayon", 827 | "new_debug_unreachable", 828 | "noop_proc_macro", 829 | "num-derive", 830 | "num-traits", 831 | "once_cell", 832 | "paste", 833 | "profiling", 834 | "rand 0.8.5", 835 | "rand_chacha 0.3.1", 836 | "simd_helpers", 837 | "system-deps", 838 | "thiserror", 839 | "v_frame", 840 | "wasm-bindgen", 841 | ] 842 | 843 | [[package]] 844 | name = "ravif" 845 | version = "0.11.11" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" 848 | dependencies = [ 849 | "avif-serialize", 850 | "imgref", 851 | "loop9", 852 | "quick-error", 853 | "rav1e", 854 | "rayon", 855 | "rgb", 856 | ] 857 | 858 | [[package]] 859 | name = "rayon" 860 | version = "1.10.0" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 863 | dependencies = [ 864 | "either", 865 | "rayon-core", 866 | ] 867 | 868 | [[package]] 869 | name = "rayon-core" 870 | version = "1.12.1" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 873 | dependencies = [ 874 | "crossbeam-deque", 875 | "crossbeam-utils", 876 | ] 877 | 878 | [[package]] 879 | name = "rgb" 880 | version = "0.8.50" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" 883 | 884 | [[package]] 885 | name = "rustversion" 886 | version = "1.0.19" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" 889 | 890 | [[package]] 891 | name = "scratch" 892 | version = "0.1.0" 893 | dependencies = [ 894 | "glam", 895 | "image", 896 | "indicatif", 897 | "noise", 898 | "rand 0.9.0", 899 | "rayon", 900 | "sha2", 901 | ] 902 | 903 | [[package]] 904 | name = "serde" 905 | version = "1.0.218" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" 908 | dependencies = [ 909 | "serde_derive", 910 | ] 911 | 912 | [[package]] 913 | name = "serde_derive" 914 | version = "1.0.218" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" 917 | dependencies = [ 918 | "proc-macro2", 919 | "quote", 920 | "syn", 921 | ] 922 | 923 | [[package]] 924 | name = "serde_spanned" 925 | version = "0.6.8" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 928 | dependencies = [ 929 | "serde", 930 | ] 931 | 932 | [[package]] 933 | name = "sha2" 934 | version = "0.10.8" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 937 | dependencies = [ 938 | "cfg-if", 939 | "cpufeatures", 940 | "digest", 941 | ] 942 | 943 | [[package]] 944 | name = "shlex" 945 | version = "1.3.0" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 948 | 949 | [[package]] 950 | name = "simd-adler32" 951 | version = "0.3.7" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 954 | 955 | [[package]] 956 | name = "simd_helpers" 957 | version = "0.1.0" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" 960 | dependencies = [ 961 | "quote", 962 | ] 963 | 964 | [[package]] 965 | name = "smallvec" 966 | version = "1.14.0" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 969 | 970 | [[package]] 971 | name = "syn" 972 | version = "2.0.99" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" 975 | dependencies = [ 976 | "proc-macro2", 977 | "quote", 978 | "unicode-ident", 979 | ] 980 | 981 | [[package]] 982 | name = "system-deps" 983 | version = "6.2.2" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" 986 | dependencies = [ 987 | "cfg-expr", 988 | "heck", 989 | "pkg-config", 990 | "toml", 991 | "version-compare", 992 | ] 993 | 994 | [[package]] 995 | name = "target-lexicon" 996 | version = "0.12.16" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" 999 | 1000 | [[package]] 1001 | name = "thiserror" 1002 | version = "1.0.69" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1005 | dependencies = [ 1006 | "thiserror-impl", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "thiserror-impl" 1011 | version = "1.0.69" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1014 | dependencies = [ 1015 | "proc-macro2", 1016 | "quote", 1017 | "syn", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "tiff" 1022 | version = "0.9.1" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" 1025 | dependencies = [ 1026 | "flate2", 1027 | "jpeg-decoder", 1028 | "weezl", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "toml" 1033 | version = "0.8.20" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" 1036 | dependencies = [ 1037 | "serde", 1038 | "serde_spanned", 1039 | "toml_datetime", 1040 | "toml_edit", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "toml_datetime" 1045 | version = "0.6.8" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 1048 | dependencies = [ 1049 | "serde", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "toml_edit" 1054 | version = "0.22.24" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" 1057 | dependencies = [ 1058 | "indexmap", 1059 | "serde", 1060 | "serde_spanned", 1061 | "toml_datetime", 1062 | "winnow", 1063 | ] 1064 | 1065 | [[package]] 1066 | name = "typenum" 1067 | version = "1.18.0" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 1070 | 1071 | [[package]] 1072 | name = "unicode-ident" 1073 | version = "1.0.17" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" 1076 | 1077 | [[package]] 1078 | name = "unicode-width" 1079 | version = "0.2.0" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 1082 | 1083 | [[package]] 1084 | name = "v_frame" 1085 | version = "0.3.8" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" 1088 | dependencies = [ 1089 | "aligned-vec", 1090 | "num-traits", 1091 | "wasm-bindgen", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "version-compare" 1096 | version = "0.2.0" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" 1099 | 1100 | [[package]] 1101 | name = "version_check" 1102 | version = "0.9.5" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1105 | 1106 | [[package]] 1107 | name = "wasi" 1108 | version = "0.11.0+wasi-snapshot-preview1" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1111 | 1112 | [[package]] 1113 | name = "wasi" 1114 | version = "0.14.2+wasi-0.2.4" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 1117 | dependencies = [ 1118 | "wit-bindgen-rt", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "wasm-bindgen" 1123 | version = "0.2.100" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1126 | dependencies = [ 1127 | "cfg-if", 1128 | "once_cell", 1129 | "rustversion", 1130 | "wasm-bindgen-macro", 1131 | ] 1132 | 1133 | [[package]] 1134 | name = "wasm-bindgen-backend" 1135 | version = "0.2.100" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 1138 | dependencies = [ 1139 | "bumpalo", 1140 | "log", 1141 | "proc-macro2", 1142 | "quote", 1143 | "syn", 1144 | "wasm-bindgen-shared", 1145 | ] 1146 | 1147 | [[package]] 1148 | name = "wasm-bindgen-macro" 1149 | version = "0.2.100" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1152 | dependencies = [ 1153 | "quote", 1154 | "wasm-bindgen-macro-support", 1155 | ] 1156 | 1157 | [[package]] 1158 | name = "wasm-bindgen-macro-support" 1159 | version = "0.2.100" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1162 | dependencies = [ 1163 | "proc-macro2", 1164 | "quote", 1165 | "syn", 1166 | "wasm-bindgen-backend", 1167 | "wasm-bindgen-shared", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "wasm-bindgen-shared" 1172 | version = "0.2.100" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1175 | dependencies = [ 1176 | "unicode-ident", 1177 | ] 1178 | 1179 | [[package]] 1180 | name = "web-time" 1181 | version = "1.1.0" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 1184 | dependencies = [ 1185 | "js-sys", 1186 | "wasm-bindgen", 1187 | ] 1188 | 1189 | [[package]] 1190 | name = "weezl" 1191 | version = "0.1.8" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" 1194 | 1195 | [[package]] 1196 | name = "windows-sys" 1197 | version = "0.59.0" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1200 | dependencies = [ 1201 | "windows-targets", 1202 | ] 1203 | 1204 | [[package]] 1205 | name = "windows-targets" 1206 | version = "0.52.6" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1209 | dependencies = [ 1210 | "windows_aarch64_gnullvm", 1211 | "windows_aarch64_msvc", 1212 | "windows_i686_gnu", 1213 | "windows_i686_gnullvm", 1214 | "windows_i686_msvc", 1215 | "windows_x86_64_gnu", 1216 | "windows_x86_64_gnullvm", 1217 | "windows_x86_64_msvc", 1218 | ] 1219 | 1220 | [[package]] 1221 | name = "windows_aarch64_gnullvm" 1222 | version = "0.52.6" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1225 | 1226 | [[package]] 1227 | name = "windows_aarch64_msvc" 1228 | version = "0.52.6" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1231 | 1232 | [[package]] 1233 | name = "windows_i686_gnu" 1234 | version = "0.52.6" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1237 | 1238 | [[package]] 1239 | name = "windows_i686_gnullvm" 1240 | version = "0.52.6" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1243 | 1244 | [[package]] 1245 | name = "windows_i686_msvc" 1246 | version = "0.52.6" 1247 | source = "registry+https://github.com/rust-lang/crates.io-index" 1248 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1249 | 1250 | [[package]] 1251 | name = "windows_x86_64_gnu" 1252 | version = "0.52.6" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1255 | 1256 | [[package]] 1257 | name = "windows_x86_64_gnullvm" 1258 | version = "0.52.6" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1261 | 1262 | [[package]] 1263 | name = "windows_x86_64_msvc" 1264 | version = "0.52.6" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1267 | 1268 | [[package]] 1269 | name = "winnow" 1270 | version = "0.7.4" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" 1273 | dependencies = [ 1274 | "memchr", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "wit-bindgen-rt" 1279 | version = "0.39.0" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 1282 | dependencies = [ 1283 | "bitflags 2.8.0", 1284 | ] 1285 | 1286 | [[package]] 1287 | name = "zerocopy" 1288 | version = "0.8.24" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" 1291 | dependencies = [ 1292 | "zerocopy-derive", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "zerocopy-derive" 1297 | version = "0.8.24" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" 1300 | dependencies = [ 1301 | "proc-macro2", 1302 | "quote", 1303 | "syn", 1304 | ] 1305 | 1306 | [[package]] 1307 | name = "zune-core" 1308 | version = "0.4.12" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" 1311 | 1312 | [[package]] 1313 | name = "zune-inflate" 1314 | version = "0.2.54" 1315 | source = "registry+https://github.com/rust-lang/crates.io-index" 1316 | checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" 1317 | dependencies = [ 1318 | "simd-adler32", 1319 | ] 1320 | 1321 | [[package]] 1322 | name = "zune-jpeg" 1323 | version = "0.4.14" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" 1326 | dependencies = [ 1327 | "zune-core", 1328 | ] 1329 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scratch" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | # External Dependencies 8 | image = "0.25.6" 9 | rand = "0.9.0" 10 | sha2 = "0.10.8" 11 | glam = "0.30.1" 12 | rayon = "1.10.0" 13 | noise = "0.9.0" 14 | indicatif = "0.17.11" 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Originally, this was my experiments repo, but then I wrote a cool CPU voxel raytracer in it, so I've now removed all of the experimental code that I could find and tried to leave things that would be useful. 2 | 3 | This image was rendered at 3840x2160 resolution in 150.12ms on my i7-13700F 4 | ![raycast](https://github.com/user-attachments/assets/da5d2f5b-70bf-4c5f-b7fa-85132af74e8c) 5 | -------------------------------------------------------------------------------- /assets/textures/skybox_001/purp_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErisianArchitect/scratch/c84a8a3f99e31404145f0c3cb6cdcc1773772b88/assets/textures/skybox_001/purp_back.png -------------------------------------------------------------------------------- /assets/textures/skybox_001/purp_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErisianArchitect/scratch/c84a8a3f99e31404145f0c3cb6cdcc1773772b88/assets/textures/skybox_001/purp_bottom.png -------------------------------------------------------------------------------- /assets/textures/skybox_001/purp_front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErisianArchitect/scratch/c84a8a3f99e31404145f0c3cb6cdcc1773772b88/assets/textures/skybox_001/purp_front.png -------------------------------------------------------------------------------- /assets/textures/skybox_001/purp_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErisianArchitect/scratch/c84a8a3f99e31404145f0c3cb6cdcc1773772b88/assets/textures/skybox_001/purp_left.png -------------------------------------------------------------------------------- /assets/textures/skybox_001/purp_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErisianArchitect/scratch/c84a8a3f99e31404145f0c3cb6cdcc1773772b88/assets/textures/skybox_001/purp_right.png -------------------------------------------------------------------------------- /assets/textures/skybox_001/purp_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErisianArchitect/scratch/c84a8a3f99e31404145f0c3cb6cdcc1773772b88/assets/textures/skybox_001/purp_top.png -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | 3 | } -------------------------------------------------------------------------------- /rrq.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cargo run --release --quiet -------------------------------------------------------------------------------- /src/byte_to_f32.rs: -------------------------------------------------------------------------------- 1 | // DO NOT DELETE THIS FILE! 2 | // This is included in color.rs 3 | const BYTE_TO_F32: [f32; 256] = [ 4 | 0e0, 5 | 3.921569e-3, 6 | 7.843138e-3, 7 | 1.1764706e-2, 8 | 1.5686275e-2, 9 | 1.9607844e-2, 10 | 2.3529412e-2, 11 | 2.745098e-2, 12 | 3.137255e-2, 13 | 3.529412e-2, 14 | 3.9215688e-2, 15 | 4.3137256e-2, 16 | 4.7058824e-2, 17 | 5.0980393e-2, 18 | 5.490196e-2, 19 | 5.882353e-2, 20 | 6.27451e-2, 21 | 6.666667e-2, 22 | 7.058824e-2, 23 | 7.450981e-2, 24 | 7.8431375e-2, 25 | 8.235294e-2, 26 | 8.627451e-2, 27 | 9.019608e-2, 28 | 9.411765e-2, 29 | 9.803922e-2, 30 | 1.01960786e-1, 31 | 1.05882354e-1, 32 | 1.0980392e-1, 33 | 1.1372549e-1, 34 | 1.1764706e-1, 35 | 1.2156863e-1, 36 | 1.254902e-1, 37 | 1.2941177e-1, 38 | 1.3333334e-1, 39 | 1.3725491e-1, 40 | 1.4117648e-1, 41 | 1.4509805e-1, 42 | 1.4901961e-1, 43 | 1.5294118e-1, 44 | 1.5686275e-1, 45 | 1.6078432e-1, 46 | 1.6470589e-1, 47 | 1.6862746e-1, 48 | 1.7254902e-1, 49 | 1.764706e-1, 50 | 1.8039216e-1, 51 | 1.8431373e-1, 52 | 1.882353e-1, 53 | 1.9215687e-1, 54 | 1.9607843e-1, 55 | 2e-1, 56 | 2.0392157e-1, 57 | 2.0784314e-1, 58 | 2.1176471e-1, 59 | 2.1568628e-1, 60 | 2.1960784e-1, 61 | 2.2352941e-1, 62 | 2.2745098e-1, 63 | 2.3137255e-1, 64 | 2.3529412e-1, 65 | 2.3921569e-1, 66 | 2.4313726e-1, 67 | 2.4705882e-1, 68 | 2.509804e-1, 69 | 2.5490198e-1, 70 | 2.5882354e-1, 71 | 2.627451e-1, 72 | 2.6666668e-1, 73 | 2.7058825e-1, 74 | 2.7450982e-1, 75 | 2.784314e-1, 76 | 2.8235295e-1, 77 | 2.8627452e-1, 78 | 2.901961e-1, 79 | 2.9411766e-1, 80 | 2.9803923e-1, 81 | 3.019608e-1, 82 | 3.0588236e-1, 83 | 3.0980393e-1, 84 | 3.137255e-1, 85 | 3.1764707e-1, 86 | 3.2156864e-1, 87 | 3.254902e-1, 88 | 3.2941177e-1, 89 | 3.3333334e-1, 90 | 3.372549e-1, 91 | 3.4117648e-1, 92 | 3.4509805e-1, 93 | 3.4901962e-1, 94 | 3.529412e-1, 95 | 3.5686275e-1, 96 | 3.6078432e-1, 97 | 3.647059e-1, 98 | 3.6862746e-1, 99 | 3.7254903e-1, 100 | 3.764706e-1, 101 | 3.8039216e-1, 102 | 3.8431373e-1, 103 | 3.882353e-1, 104 | 3.9215687e-1, 105 | 3.9607844e-1, 106 | 4e-1, 107 | 4.0392157e-1, 108 | 4.0784314e-1, 109 | 4.117647e-1, 110 | 4.1568628e-1, 111 | 4.1960785e-1, 112 | 4.2352942e-1, 113 | 4.2745098e-1, 114 | 4.3137255e-1, 115 | 4.3529412e-1, 116 | 4.392157e-1, 117 | 4.4313726e-1, 118 | 4.4705883e-1, 119 | 4.509804e-1, 120 | 4.5490196e-1, 121 | 4.5882353e-1, 122 | 4.627451e-1, 123 | 4.6666667e-1, 124 | 4.7058824e-1, 125 | 4.745098e-1, 126 | 4.7843137e-1, 127 | 4.8235294e-1, 128 | 4.862745e-1, 129 | 4.9019608e-1, 130 | 4.9411765e-1, 131 | 4.9803922e-1, 132 | 5.019608e-1, 133 | 5.058824e-1, 134 | 5.0980395e-1, 135 | 5.137255e-1, 136 | 5.176471e-1, 137 | 5.2156866e-1, 138 | 5.254902e-1, 139 | 5.294118e-1, 140 | 5.3333336e-1, 141 | 5.372549e-1, 142 | 5.411765e-1, 143 | 5.4509807e-1, 144 | 5.4901963e-1, 145 | 5.529412e-1, 146 | 5.568628e-1, 147 | 5.6078434e-1, 148 | 5.647059e-1, 149 | 5.686275e-1, 150 | 5.7254905e-1, 151 | 5.764706e-1, 152 | 5.803922e-1, 153 | 5.8431375e-1, 154 | 5.882353e-1, 155 | 5.921569e-1, 156 | 5.9607846e-1, 157 | 6e-1, 158 | 6.039216e-1, 159 | 6.0784316e-1, 160 | 6.117647e-1, 161 | 6.156863e-1, 162 | 6.1960787e-1, 163 | 6.2352943e-1, 164 | 6.27451e-1, 165 | 6.313726e-1, 166 | 6.3529414e-1, 167 | 6.392157e-1, 168 | 6.431373e-1, 169 | 6.4705884e-1, 170 | 6.509804e-1, 171 | 6.54902e-1, 172 | 6.5882355e-1, 173 | 6.627451e-1, 174 | 6.666667e-1, 175 | 6.7058825e-1, 176 | 6.745098e-1, 177 | 6.784314e-1, 178 | 6.8235296e-1, 179 | 6.862745e-1, 180 | 6.901961e-1, 181 | 6.9411767e-1, 182 | 6.9803923e-1, 183 | 7.019608e-1, 184 | 7.058824e-1, 185 | 7.0980394e-1, 186 | 7.137255e-1, 187 | 7.176471e-1, 188 | 7.2156864e-1, 189 | 7.254902e-1, 190 | 7.294118e-1, 191 | 7.3333335e-1, 192 | 7.372549e-1, 193 | 7.411765e-1, 194 | 7.4509805e-1, 195 | 7.490196e-1, 196 | 7.529412e-1, 197 | 7.5686276e-1, 198 | 7.607843e-1, 199 | 7.647059e-1, 200 | 7.6862746e-1, 201 | 7.7254903e-1, 202 | 7.764706e-1, 203 | 7.8039217e-1, 204 | 7.8431374e-1, 205 | 7.882353e-1, 206 | 7.921569e-1, 207 | 7.9607844e-1, 208 | 8e-1, 209 | 8.039216e-1, 210 | 8.0784315e-1, 211 | 8.117647e-1, 212 | 8.156863e-1, 213 | 8.1960785e-1, 214 | 8.235294e-1, 215 | 8.27451e-1, 216 | 8.3137256e-1, 217 | 8.352941e-1, 218 | 8.392157e-1, 219 | 8.4313726e-1, 220 | 8.4705883e-1, 221 | 8.509804e-1, 222 | 8.5490197e-1, 223 | 8.5882354e-1, 224 | 8.627451e-1, 225 | 8.666667e-1, 226 | 8.7058824e-1, 227 | 8.745098e-1, 228 | 8.784314e-1, 229 | 8.8235295e-1, 230 | 8.862745e-1, 231 | 8.901961e-1, 232 | 8.9411765e-1, 233 | 8.980392e-1, 234 | 9.019608e-1, 235 | 9.0588236e-1, 236 | 9.098039e-1, 237 | 9.137255e-1, 238 | 9.1764706e-1, 239 | 9.2156863e-1, 240 | 9.254902e-1, 241 | 9.2941177e-1, 242 | 9.3333334e-1, 243 | 9.372549e-1, 244 | 9.411765e-1, 245 | 9.4509804e-1, 246 | 9.490196e-1, 247 | 9.529412e-1, 248 | 9.5686275e-1, 249 | 9.607843e-1, 250 | 9.647059e-1, 251 | 9.6862745e-1, 252 | 9.72549e-1, 253 | 9.764706e-1, 254 | 9.8039216e-1, 255 | 9.843137e-1, 256 | 9.882353e-1, 257 | 9.9215686e-1, 258 | 9.9607843e-1, 259 | 1e0, 260 | ]; -------------------------------------------------------------------------------- /src/camera.rs: -------------------------------------------------------------------------------- 1 | use glam::{ 2 | vec2, Mat3, Mat4, Quat, Vec2, Vec3, Vec3A, Vec4, Vec4Swizzles 3 | }; 4 | 5 | use crate::ray::Ray3; 6 | 7 | pub fn rotation_from_look_at(position: Vec3A, target: Vec3A) -> Vec2 { 8 | let dir = (target - position).normalize(); 9 | rotation_from_direction(dir) 10 | } 11 | 12 | pub fn rotation_from_direction(direction: Vec3A) -> Vec2 { 13 | let yaw = (-direction.x).atan2(-direction.z); 14 | let pitch = direction.y.asin(); 15 | vec2(pitch, yaw) 16 | } 17 | 18 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 19 | pub enum MoveType { 20 | /// Absolute movement. No rotation of the translation vector. 21 | Absolute, 22 | /// Free movement. Rotates the translation vector with the camera. 23 | Free, 24 | /// Planar movement. Rotates the translation vector with the angle around the Y axis. 25 | Planar, 26 | } 27 | 28 | #[derive(Debug, Clone)] 29 | pub struct Camera { 30 | pub position: Vec3A, 31 | pub rotation: Vec2, 32 | pub fov: f32, 33 | pub aspect_ratio: f32, 34 | pub z_near: f32, 35 | pub z_far: f32, 36 | pub screen_size: (u32, u32), 37 | } 38 | 39 | const fn aspect_ratio(size: (u32, u32)) -> f32 { 40 | size.0 as f32 / size.1 as f32 41 | } 42 | 43 | impl Camera { 44 | pub fn new( 45 | position: Vec3A, 46 | rotation: Vec2, 47 | fov: f32, 48 | z_near: f32, 49 | z_far: f32, 50 | screen_size: (u32, u32), 51 | ) -> Self { 52 | Self { 53 | position, 54 | rotation, 55 | fov, 56 | aspect_ratio: aspect_ratio(screen_size), 57 | z_near, 58 | z_far, 59 | screen_size, 60 | } 61 | } 62 | 63 | /// Creates an unrotated camera at the given position. 64 | pub fn at( 65 | position: Vec3A, 66 | fov: f32, 67 | z_near: f32, 68 | z_far: f32, 69 | screen_size: (u32, u32), 70 | ) -> Self { 71 | Self { 72 | position, 73 | rotation: Vec2::ZERO, 74 | fov, 75 | aspect_ratio: aspect_ratio(screen_size), 76 | z_near, 77 | z_far, 78 | screen_size, 79 | } 80 | } 81 | 82 | pub fn from_look_at( 83 | position: Vec3A, 84 | target: Vec3A, 85 | fov: f32, 86 | z_near: f32, 87 | z_far: f32, 88 | screen_size: (u32, u32), 89 | ) -> Self { 90 | let rotation = rotation_from_look_at(position, target); 91 | Self { 92 | position, 93 | rotation, 94 | fov, 95 | aspect_ratio: aspect_ratio(screen_size), 96 | z_near, 97 | z_far, 98 | screen_size, 99 | } 100 | } 101 | 102 | /// `look_to` means to point in the same direction as the given `direction` vector. 103 | pub fn from_look_to( 104 | position: Vec3A, 105 | direction: Vec3A, 106 | fov: f32, 107 | z_near: f32, 108 | z_far: f32, 109 | screen_size: (u32, u32), 110 | ) -> Self { 111 | let rotation = rotation_from_direction(direction); 112 | Self { 113 | position, 114 | rotation, 115 | fov, 116 | aspect_ratio: aspect_ratio(screen_size), 117 | z_near, 118 | z_far, 119 | screen_size, 120 | } 121 | } 122 | 123 | pub fn resize(&mut self, size: (u32, u32)) { 124 | self.screen_size = size; 125 | self.aspect_ratio = aspect_ratio(size); 126 | } 127 | 128 | pub fn rotate_vec(&self, v: Vec3A) -> Vec3A { 129 | let rot = self.quat(); 130 | rot * v 131 | } 132 | 133 | /// Rotates vector around the Y axis. 134 | pub fn rotate_vec_y(&self, v: Vec3A) -> Vec3A { 135 | let rot = self.y_quat(); 136 | rot * v 137 | } 138 | 139 | pub fn up(&self) -> Vec3A { 140 | self.rotate_vec(Vec3A::Y) 141 | } 142 | 143 | pub fn down(&self) -> Vec3A { 144 | self.rotate_vec(Vec3A::NEG_Y) 145 | } 146 | 147 | pub fn left(&self) -> Vec3A { 148 | self.rotate_vec(Vec3A::NEG_X) 149 | } 150 | 151 | pub fn right(&self) -> Vec3A { 152 | self.rotate_vec(Vec3A::X) 153 | } 154 | 155 | pub fn forward(&self) -> Vec3A { 156 | self.rotate_vec(Vec3A::NEG_Z) 157 | } 158 | 159 | pub fn backward(&self) -> Vec3A { 160 | self.rotate_vec(Vec3A::Z) 161 | } 162 | 163 | pub fn pan_forward(&self) -> Vec3A { 164 | self.rotate_vec_y(Vec3A::NEG_Z) 165 | } 166 | 167 | pub fn pan_backward(&self) -> Vec3A { 168 | self.rotate_vec_y(Vec3A::Z) 169 | } 170 | 171 | pub fn adv_move(&mut self, move_type: MoveType, translation: Vec3A) { 172 | match move_type { 173 | MoveType::Absolute => self.translate(translation), 174 | MoveType::Free => self.translate_rotated(translation), 175 | MoveType::Planar => self.translate_planar(translation), 176 | } 177 | } 178 | 179 | pub fn translate(&mut self, translation: Vec3A) { 180 | self.position += translation; 181 | } 182 | 183 | /// Translates relative to camera rotation. 184 | pub fn translate_rotated(&mut self, translation: Vec3A) { 185 | if translation.length_squared() > 0.000001 { 186 | let rot_quat = self.quat(); 187 | let rot_offset = rot_quat * translation; 188 | self.translate(rot_offset); 189 | } 190 | } 191 | 192 | /// For planar camera translation. 193 | pub fn translate_planar(&mut self, translation: Vec3A) { 194 | if translation.length_squared() > 0.000001 { 195 | self.translate(self.rotate_vec_y(translation)) 196 | } 197 | } 198 | 199 | pub fn look_at(&mut self, target: Vec3A) { 200 | self.rotation = rotation_from_look_at(self.position, target); 201 | } 202 | 203 | pub fn look_to(&mut self, direction: Vec3A) { 204 | self.rotation = rotation_from_direction(direction); 205 | } 206 | 207 | pub fn rotate(&mut self, rotation_radians: Vec2) { 208 | self.rotation += rotation_radians; 209 | self.rotation.x = self.rotation.x.clamp(-90f32.to_radians(), 90f32.to_radians()); 210 | self.rotation.y = self.rotation.y.rem_euclid(360f32.to_radians()); 211 | } 212 | 213 | pub fn rotate_x(&mut self, radians: f32) { 214 | self.rotation.x += radians; 215 | self.rotation.x = self.rotation.x.clamp(-90f32.to_radians(), 90f32.to_radians()); 216 | } 217 | 218 | pub fn rotate_y(&mut self, radians: f32) { 219 | self.rotation.y += radians; 220 | self.rotation.y = self.rotation.y.rem_euclid(360f32.to_radians()); 221 | } 222 | 223 | /// Returns the quaternion for the [Camera]'s rotation. 224 | pub fn quat(&self) -> Quat { 225 | Quat::from_euler(glam::EulerRot::YXZ, self.rotation.y, self.rotation.x, 0.) 226 | } 227 | 228 | pub fn x_quat(&self) -> Quat { 229 | Quat::from_axis_angle(Vec3::X, self.rotation.x) 230 | } 231 | 232 | pub fn y_quat(&self) -> Quat { 233 | Quat::from_axis_angle(Vec3::Y, self.rotation.y) 234 | } 235 | 236 | pub fn view_matrix(&self) -> Mat4 { 237 | let rot_quat = self.quat(); 238 | let up = rot_quat * Vec3::Y; 239 | let dir = rot_quat * Vec3::NEG_Z; 240 | Mat4::look_to_rh(self.position.into(), dir, up) 241 | } 242 | 243 | pub fn rotation_matrix(&self) -> Mat3 { 244 | Mat3::from_euler(glam::EulerRot::YXZ, self.rotation.y, self.rotation.x, 0.0) 245 | } 246 | 247 | pub fn projection_matrix(&self) -> Mat4 { 248 | Mat4::perspective_rh(self.fov, self.aspect_ratio, self.z_near, self.z_far) 249 | } 250 | 251 | pub fn projection_view_matrix(&self) -> Mat4 { 252 | self.projection_matrix() * self.view_matrix() 253 | } 254 | 255 | pub fn world_to_clip(&self, pos: Vec3A) -> Vec4 { 256 | let view_proj = self.projection_view_matrix(); 257 | let pos_w = Vec4::new(pos.x, pos.y, pos.z, 1.0); 258 | view_proj * pos_w 259 | } 260 | 261 | pub fn world_to_clip_ncd(&self, pos: Vec3A) -> Vec3 { 262 | let clip = self.world_to_clip(pos); 263 | clip.xyz() / clip.w 264 | } 265 | 266 | pub fn normalized_screen_to_ray(&self, screen_pos: Vec2) -> Ray3 { 267 | let inv_proj_view = self.projection_view_matrix().inverse(); 268 | 269 | let near_point = inv_proj_view * Vec4::new(screen_pos.x, -screen_pos.y, 0.0, 1.0); 270 | let near_point = near_point.xyz() / near_point.w; 271 | let far_point = inv_proj_view * Vec4::new(screen_pos.x, -screen_pos.y, self.z_far, 1.0); 272 | let far_point = far_point.xyz() / far_point.w; 273 | 274 | let direction = (near_point - far_point).normalize(); 275 | 276 | Ray3::new(self.position, direction.into()) 277 | } 278 | } -------------------------------------------------------------------------------- /src/chunk.rs: -------------------------------------------------------------------------------- 1 | use crate::{math::Face, ray::Ray3}; 2 | use glam::*; 3 | 4 | 5 | // 64x64x64 chunk. 6 | pub struct Chunk { 7 | cols: Box<[u64]>, 8 | reflection_cols: Box<[u64]>, 9 | } 10 | 11 | impl Chunk { 12 | pub fn new() -> Self { 13 | Self { 14 | cols: (0..4096).map(|_| 0u64).collect::>(), 15 | reflection_cols: (0..4096).map(|_| 0u64).collect::>(), 16 | } 17 | } 18 | 19 | #[inline(always)] 20 | pub fn col_index(x: i32, z: i32) -> i32 { 21 | x | z << 6 22 | } 23 | 24 | #[inline(always)] 25 | pub fn get(&self, x: i32, y: i32, z: i32) -> bool { 26 | // Before 27 | // if x < 0 || y < 0 || z < 0 || x >= 64 || y >= 64 || z >= 64 { 28 | // return false; 29 | // } 30 | // After 31 | // This is a neat bit level trick to bounds check x, y, and z 32 | // simultaneously. This works because of Two's Complement, which 33 | // causes negative numbers to become exceedingly high positive 34 | // numbers when cast to an unsigned type. 35 | // This reduces the bounds check from 11 instructions down to just 4. 36 | let xyz = x | y | z; 37 | if (xyz as u32) >= 64 { // this works because of twos-complement. 38 | return false; 39 | } 40 | 41 | let index = Self::col_index(x, z); 42 | let col = self.cols[index as usize]; 43 | (col & (1 << y)) != 0 44 | } 45 | 46 | #[inline(always)] 47 | pub fn get_reflection(&self, x: i32, y: i32, z: i32) -> bool { 48 | // Before 49 | // if x < 0 || y < 0 || z < 0 || x >= 64 || y >= 64 || z >= 64 { 50 | // return false; 51 | // } 52 | // After 53 | let xyz = x | y | z; 54 | if (xyz as u32) >= 64 { 55 | return false; 56 | } 57 | let index = Self::col_index(x, z); 58 | let col = self.reflection_cols[index as usize]; 59 | (col & (1 << y)) != 0 60 | } 61 | 62 | pub fn set(&mut self, x: i32, y: i32, z: i32, on: bool) { 63 | let xyz = x | y | z; 64 | if (xyz as u32) >= 64 { 65 | return; 66 | } 67 | let index = Self::col_index(x, z); 68 | let mut col = self.cols[index as usize]; 69 | if on { 70 | col = col | (1 << y); 71 | } else { 72 | col = col & !(1 << y); 73 | } 74 | self.cols[index as usize] = col; 75 | } 76 | 77 | pub fn set_reflection(&mut self, x: i32, y: i32, z: i32, on: bool) { 78 | let xyz = x | y | z; 79 | if (xyz as u32) >= 64 { 80 | return; 81 | } 82 | let index = Self::col_index(x, z); 83 | let mut col = self.reflection_cols[index as usize]; 84 | if on { 85 | col = col | (1 << y); 86 | } else { 87 | col = col & !(1 << y); 88 | } 89 | self.reflection_cols[index as usize] = col; 90 | } 91 | 92 | pub fn fill_box(&mut self, start: (i32, i32, i32), end: (i32, i32, i32), on: bool) { 93 | let end = (end.0.clamp(0, 64), end.1.clamp(0, 64), end.2.clamp(0, 64)); 94 | let start = ( 95 | start.0.clamp(0, end.0), 96 | start.1.clamp(0, end.1), 97 | start.2.clamp(0, end.2), 98 | ); 99 | for x in start.0..end.0 { 100 | for z in start.2..end.2 { 101 | for y in start.1..end.1 { 102 | self.set(x, y, z, on); 103 | } 104 | } 105 | } 106 | } 107 | 108 | pub fn fill_box_reflection(&mut self, start: (i32, i32, i32), end: (i32, i32, i32), on: bool) { 109 | let end = (end.0.clamp(0, 64), end.1.clamp(0, 64), end.2.clamp(0, 64)); 110 | let start = ( 111 | start.0.clamp(0, end.0), 112 | start.1.clamp(0, end.1), 113 | start.2.clamp(0, end.2), 114 | ); 115 | for x in start.0..end.0 { 116 | for z in start.2..end.2 { 117 | for y in start.1..end.1 { 118 | self.set_reflection(x, y, z, on); 119 | } 120 | } 121 | } 122 | } 123 | 124 | pub fn set_with bool>(&mut self, x: i32, y: i32, z: i32, f: F) { 125 | let cur = self.get(x, y, z); 126 | let new = f(cur); 127 | self.set(x, y, z, new); 128 | } 129 | 130 | pub fn set_with_reflection bool>(&mut self, x: i32, y: i32, z: i32, f: F) { 131 | let cur = self.get_reflection(x, y, z); 132 | let new = f(cur); 133 | self.set_reflection(x, y, z, new); 134 | } 135 | 136 | pub fn draw_box_with bool>(&mut self, start: (i32, i32, i32), end: (i32, i32, i32), f: F) { 137 | for y in start.1..end.1 { 138 | self.set_with(start.0, y, start.2, |b| f(b)); 139 | self.set_with(end.0 - 1, y, start.2, |b| f(b)); 140 | self.set_with(start.0, y, end.2 - 1, |b| f(b)); 141 | self.set_with(end.0 - 1, y, end.2 - 1, |b| f(b)); 142 | } 143 | for z in start.2+1..end.2-1 { 144 | self.set_with(start.0, start.1, z, |b| f(b)); 145 | self.set_with(end.0-1, start.1, z, |b| f(b)); 146 | self.set_with(start.0, end.1-1, z, |b| f(b)); 147 | self.set_with(end.0-1, end.1-1, z, |b| f(b)); 148 | } 149 | for x in start.0+1..end.0-1 { 150 | self.set_with(x, start.1, start.2, |b| f(b)); 151 | self.set_with(x, end.1-1, start.2, |b| f(b)); 152 | self.set_with(x, start.1, end.2-1, |b| f(b)); 153 | self.set_with(x, end.1-1, end.2-1, |b| f(b)); 154 | } 155 | } 156 | 157 | pub fn draw_box_with_reflection bool>(&mut self, start: (i32, i32, i32), end: (i32, i32, i32), f: F) { 158 | for y in start.1..end.1 { 159 | self.set_with_reflection(start.0, y, start.2, |b| f(b)); 160 | self.set_with_reflection(end.0 - 1, y, start.2, |b| f(b)); 161 | self.set_with_reflection(start.0, y, end.2 - 1, |b| f(b)); 162 | self.set_with_reflection(end.0 - 1, y, end.2 - 1, |b| f(b)); 163 | } 164 | for z in start.2+1..end.2-1 { 165 | self.set_with_reflection(start.0, start.1, z, |b| f(b)); 166 | self.set_with_reflection(end.0-1, start.1, z, |b| f(b)); 167 | self.set_with_reflection(start.0, end.1-1, z, |b| f(b)); 168 | self.set_with_reflection(end.0-1, end.1-1, z, |b| f(b)); 169 | } 170 | for x in start.0+1..end.0-1 { 171 | self.set_with_reflection(x, start.1, start.2, |b| f(b)); 172 | self.set_with_reflection(x, end.1-1, start.2, |b| f(b)); 173 | self.set_with_reflection(x, start.1, end.2-1, |b| f(b)); 174 | self.set_with_reflection(x, end.1-1, end.2-1, |b| f(b)); 175 | } 176 | } 177 | 178 | pub fn draw_box(&mut self, start: (i32, i32, i32), end: (i32, i32, i32), on: bool) { 179 | self.draw_box_with(start, end, |_| on); 180 | } 181 | 182 | pub fn draw_box_reflection(&mut self, start: (i32, i32, i32), end: (i32, i32, i32), on: bool) { 183 | self.draw_box_with_reflection(start, end, |_| on); 184 | } 185 | 186 | pub fn raycast(&self, ray: Ray3, max_distance: f32) -> Option { 187 | let mut ray = ray; 188 | let lt = ray.pos.cmplt(Vec3A::ZERO); 189 | const SIXTY_FOUR: Vec3A = Vec3A::splat(64.0); 190 | let ge = ray.pos.cmpge(SIXTY_FOUR); 191 | let outside = lt | ge; 192 | let (step, delta_min, delta_max, delta_add) = if outside.any() { 193 | // Calculate entry point (if there is one). 194 | // calculate distance to cross each plane 195 | let sign = ray.dir.signum(); 196 | let step = sign.as_ivec3(); 197 | 198 | let neg_sign = sign.cmplt(Vec3A::ZERO); 199 | let pos_sign = sign.cmpgt(Vec3A::ZERO); 200 | 201 | if ((lt & neg_sign) | (ge & pos_sign)).any() { 202 | return None; 203 | } 204 | // if lt.test(0) && step.x < 0 // 4 205 | // || lt.test(1) && step.y < 0 // 5 9 206 | // || lt.test(2) && step.z < 0 // 5 14 207 | // || ge.test(0) && step.x > 0 // 5 19 208 | // || ge.test(1) && step.y > 0 // 5 24 209 | // || ge.test(2) && step.z > 0 {// 5 29 210 | // return None; 211 | // } 212 | let (dx_min, dx_max) = match step.x + 1 { 213 | 0 => { 214 | ( 215 | (ray.pos.x - 64.0) / -ray.dir.x, 216 | ray.pos.x / -ray.dir.x, 217 | ) 218 | } 219 | 1 => { 220 | (::NEG_INFINITY, ::INFINITY) 221 | } 222 | 2 => { 223 | ( 224 | -ray.pos.x / ray.dir.x, 225 | (64.0 - ray.pos.x) / ray.dir.x, 226 | ) 227 | } 228 | _ => unreachable!(), 229 | }; 230 | let (dy_min, dy_max) = match step.y + 1 { 231 | 0 => { 232 | ( 233 | (ray.pos.y - 64.0) / -ray.dir.y, 234 | ray.pos.y / -ray.dir.y, 235 | ) 236 | } 237 | 1 => { 238 | (::NEG_INFINITY, ::INFINITY) 239 | } 240 | 2 => { 241 | ( 242 | -ray.pos.y / ray.dir.y, 243 | (64.0 - ray.pos.y) / ray.dir.y, 244 | ) 245 | } 246 | _ => unreachable!() 247 | }; 248 | let (dz_min, dz_max) = match step.z + 1 { 249 | 0 => { 250 | ( 251 | (ray.pos.z - 64.0) / -ray.dir.z, 252 | ray.pos.z / -ray.dir.z, 253 | ) 254 | } 255 | 1 => { 256 | (::NEG_INFINITY, ::INFINITY) 257 | } 258 | 2 => { 259 | ( 260 | -ray.pos.z / ray.dir.z, 261 | (64.0 - ray.pos.z) / ray.dir.z, 262 | ) 263 | } 264 | _ => unreachable!() 265 | }; 266 | let max_min = dx_min.max(dy_min.max(dz_min)); 267 | let min_max = dx_max.min(dy_max.min(dz_max)); 268 | // Early return, the ray does not hit the volume. 269 | if max_min >= min_max { 270 | return None; 271 | } 272 | // This is needed to penetrate the ray into the bounding box. 273 | // Otherwise you'll get weird circles from the rays popping 274 | // in and out of the next cell. This ensures that the ray 275 | // will be inside the bounding box. 276 | const RAY_PENETRATION: f32 = 1e-5; 277 | let delta_add = max_min + RAY_PENETRATION; 278 | if delta_add >= max_distance { 279 | return None; 280 | } 281 | ray.pos = ray.pos + ray.dir * delta_add; 282 | ( 283 | step, 284 | Some(vec3(dx_min, dy_min, dz_min)), 285 | vec3(dx_max, dy_max, dz_max), 286 | delta_add, 287 | ) 288 | } else { 289 | let sign = ray.dir.signum(); 290 | let step = sign.as_ivec3(); 291 | let dx_max = match step.x + 1 { 292 | 0 => { 293 | ray.pos.x / -ray.dir.x 294 | } 295 | 1 => { 296 | ::INFINITY 297 | } 298 | 2 => { 299 | (64.0 - ray.pos.x) / ray.dir.x 300 | } 301 | _ => unreachable!() 302 | }; 303 | let dy_max = match step.y + 1 { 304 | 0 => { 305 | ray.pos.y / -ray.dir.y 306 | } 307 | 1 => { 308 | ::INFINITY 309 | } 310 | 2 => { 311 | (64.0 - ray.pos.y) / ray.dir.y 312 | } 313 | _ => unreachable!() 314 | }; 315 | let dz_max = match step.z + 1{ 316 | 0 => { 317 | ray.pos.z / -ray.dir.z 318 | } 319 | 1 => { 320 | ::INFINITY 321 | } 322 | 2 => { 323 | (64.0 - ray.pos.z) / ray.dir.z 324 | } 325 | _ => unreachable!() 326 | }; 327 | ( 328 | step, 329 | None, 330 | vec3(dx_max, dy_max, dz_max), 331 | 0.0, 332 | ) 333 | }; 334 | #[inline(always)] 335 | fn calc_delta(mag: f32) -> f32 { 336 | 1.0 / mag.abs().max(::MIN_POSITIVE) 337 | } 338 | let delta = vec3( 339 | calc_delta(ray.dir.x), 340 | calc_delta(ray.dir.y), 341 | calc_delta(ray.dir.z), 342 | ); 343 | 344 | let face = ( 345 | if step.x >= 0 { 346 | Face::NegX 347 | } else { 348 | Face::PosX 349 | }, 350 | if step.y >= 0 { 351 | Face::NegY 352 | } else { 353 | Face::PosY 354 | }, 355 | if step.z >= 0 { 356 | Face::NegZ 357 | } else { 358 | Face::PosZ 359 | }, 360 | ); 361 | 362 | let fract = ray.pos.fract(); 363 | 364 | #[inline(always)] 365 | fn calc_t_max(step: i32, fract: f32, mag: f32) -> f32 { 366 | if step > 0 { 367 | (1.0 - fract) / mag.abs().max(::MIN_POSITIVE) 368 | } else if step < 0 { 369 | fract / mag.abs().max(::MIN_POSITIVE) 370 | } else { 371 | ::INFINITY 372 | } 373 | } 374 | let mut t_max = vec3( 375 | calc_t_max(step.x, fract.x, ray.dir.x) + delta_add, 376 | calc_t_max(step.y, fract.y, ray.dir.y) + delta_add, 377 | calc_t_max(step.z, fract.z, ray.dir.z) + delta_add, 378 | ); 379 | 380 | let mut cell = ray.pos.floor().as_ivec3(); 381 | if self.get(cell.x, cell.y, cell.z) { 382 | return Some(RayHit { 383 | face: delta_min.map(|min| { 384 | if min.x >= min.y { 385 | if min.x >= min.z { 386 | face.0 387 | } else { 388 | face.2 389 | } 390 | } else { 391 | if min.y >= min.z { 392 | face.1 393 | } else { 394 | face.2 395 | } 396 | } 397 | }), 398 | coord: cell, 399 | distance: delta_add, 400 | }); 401 | } 402 | let max_d = vec3a( 403 | delta_max.x.min(max_distance), 404 | delta_max.y.min(max_distance), 405 | delta_max.z.min(max_distance), 406 | ); 407 | loop { 408 | 409 | if t_max.x <= t_max.y { 410 | if t_max.x <= t_max.z { 411 | if t_max.x >= max_d.x { 412 | return None; 413 | } 414 | cell.x += step.x; 415 | if self.get(cell.x, cell.y, cell.z) { 416 | return Some(RayHit::hit_face(face.0, cell, t_max.x)); 417 | } 418 | t_max.x += delta.x; 419 | } else { 420 | if t_max.z >= max_d.z { 421 | return None; 422 | } 423 | cell.z += step.z; 424 | if self.get(cell.x, cell.y, cell.z) { 425 | return Some(RayHit::hit_face(face.2, cell, t_max.z)); 426 | } 427 | t_max.z += delta.z; 428 | } 429 | } else { 430 | if t_max.y <= t_max.z { 431 | if t_max.y >= max_d.y { 432 | return None; 433 | } 434 | cell.y += step.y; 435 | if self.get(cell.x, cell.y, cell.z) { 436 | return Some(RayHit::hit_face(face.1, cell, t_max.y)); 437 | } 438 | t_max.y += delta.y; 439 | } else { 440 | if t_max.z >= max_d.z { 441 | return None; 442 | } 443 | cell.z += step.z; 444 | if self.get(cell.x, cell.y, cell.z) { 445 | return Some(RayHit::hit_face(face.2, cell, t_max.z)); 446 | } 447 | t_max.z += delta.z; 448 | } 449 | } 450 | } 451 | } 452 | } 453 | 454 | pub struct RayHit { 455 | pub face: Option, 456 | pub coord: IVec3, 457 | pub distance: f32, 458 | } 459 | 460 | impl RayHit { 461 | #[inline(always)] 462 | pub fn hit_face(face: Face, coord: IVec3, distance: f32) -> Self { 463 | Self { 464 | face: Some(face), 465 | coord, 466 | distance, 467 | } 468 | } 469 | 470 | #[inline(always)] 471 | pub fn hit_cell(coord: IVec3, distance: f32) -> Self { 472 | Self { 473 | face: None, 474 | coord, 475 | distance, 476 | } 477 | } 478 | 479 | #[inline(always)] 480 | pub fn get_hit_point(&self, ray: Ray3, face: Face) -> Vec3A { 481 | let point = ray.point_on_ray(self.distance); 482 | let pre_hit = match face { 483 | Face::PosX => ivec3(self.coord.x + 1, self.coord.y, self.coord.z), 484 | Face::PosY => ivec3(self.coord.x, self.coord.y + 1, self.coord.z), 485 | Face::PosZ => ivec3(self.coord.x, self.coord.y, self.coord.z + 1), 486 | Face::NegX => ivec3(self.coord.x - 1, self.coord.y, self.coord.z), 487 | Face::NegY => ivec3(self.coord.x, self.coord.y - 1, self.coord.z), 488 | Face::NegZ => ivec3(self.coord.x, self.coord.y, self.coord.z - 1), 489 | }; 490 | let pre_hit = pre_hit.as_vec3a(); 491 | const SMIDGEN: Vec3A = Vec3A::splat(1e-3); 492 | const UNSMIDGEN: Vec3A = Vec3A::splat(1.0-1e-3); 493 | // sometimes the hit-point is in the wrong cell (if it goes too far) 494 | // so you want to bring it back into the correct cell. 495 | let min = pre_hit + SMIDGEN; 496 | let max = pre_hit + UNSMIDGEN; 497 | // point.max(min).min(max) 498 | point.clamp(min, max) 499 | } 500 | } -------------------------------------------------------------------------------- /src/cubemap.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use glam::Vec3A; 4 | use image::*; 5 | 6 | use crate::math::Face; 7 | 8 | pub struct Cubemap { 9 | faces: [RgbImage; 6], 10 | width: u32, 11 | height: u32, 12 | } 13 | 14 | impl Cubemap { 15 | 16 | pub fn width(&self) -> u32 { 17 | self.width 18 | } 19 | 20 | pub fn height(&self) -> u32 { 21 | self.height 22 | } 23 | 24 | pub fn new( 25 | pos_x: RgbImage, 26 | pos_y: RgbImage, 27 | pos_z: RgbImage, 28 | neg_x: RgbImage, 29 | neg_y: RgbImage, 30 | neg_z: RgbImage, 31 | ) -> Self { 32 | Self { 33 | width: pos_x.width(), 34 | height: pos_x.height(), 35 | faces: [ 36 | pos_x, 37 | pos_y, 38 | pos_z, 39 | neg_x, 40 | neg_y, 41 | neg_z, 42 | ] 43 | } 44 | } 45 | 46 | pub fn from_files>(files: [P; 6]) -> std::io::Result { 47 | let [px, py, pz, nx, ny, nz] = [ 48 | read_texture(files[0].as_ref())?, 49 | read_texture(files[1].as_ref())?, 50 | read_texture(files[2].as_ref())?, 51 | read_texture(files[3].as_ref())?, 52 | read_texture(files[4].as_ref())?, 53 | read_texture(files[5].as_ref())?, 54 | ]; 55 | Ok(Self::new(px, py, pz, nx, ny, nz)) 56 | } 57 | 58 | pub fn sample_uv(&self, face: Face, u: f32, v: f32) -> Rgb { 59 | let u = u.clamp(0.0, 1.0); 60 | let v = v.clamp(0.0, 1.0); 61 | image::imageops::sample_bilinear(&self.faces[face.index()], u, v).unwrap_or_else(|| Rgb([0; 3])) 62 | } 63 | 64 | pub fn sample_dir(&self, dir: Vec3A) -> Rgb { 65 | let abs = dir.abs(); 66 | if abs.x >= abs.y { 67 | if abs.x >= abs.z { 68 | let dx = 0.5 / abs.x; 69 | if dir.x.is_sign_negative() { 70 | let face = Face::NegX; 71 | let u = (-dir.z * dx) + 0.5; 72 | let v = (-dir.y * dx) + 0.5; 73 | self.sample_uv(face, u, v) 74 | } else { 75 | let face = Face::PosX; 76 | let u = (dir.z * dx) + 0.5; 77 | let v = (-dir.y * dx) + 0.5; 78 | self.sample_uv(face, u, v) 79 | } 80 | } else { 81 | let dz = 0.5 / abs.z; 82 | if dir.z.is_sign_negative() { 83 | let face = Face::NegZ; 84 | let u = (dir.x * dz) + 0.5; 85 | let v = (-dir.y * dz) + 0.5; 86 | self.sample_uv(face, u, v) 87 | } else { 88 | let face = Face::PosZ; 89 | let u = (-dir.x * dz) + 0.5; 90 | let v = (-dir.y * dz) + 0.5; 91 | self.sample_uv(face, u, v) 92 | } 93 | } 94 | } else { 95 | if abs.y >= abs.z { 96 | let dy = 0.5 / abs.y; 97 | if dir.y.is_sign_negative() { 98 | let face = Face::NegY; 99 | let u = (dir.x * dy) + 0.5; 100 | let v = (dir.z * dy) + 0.5; 101 | self.sample_uv(face, u, v) 102 | } else { 103 | let face = Face::PosY; 104 | let u = (dir.x * dy) + 0.5; 105 | let v = (-dir.z * dy) + 0.5; 106 | self.sample_uv(face, u, v) 107 | } 108 | } else { 109 | let dz = 0.5 / abs.z; 110 | if dir.z.is_sign_negative() { 111 | let face = Face::NegZ; 112 | let u = (dir.x * dz) + 0.5; 113 | let v = (-dir.y * dz) + 0.5; 114 | self.sample_uv(face, u, v) 115 | } else { 116 | let face = Face::PosZ; 117 | let u = (-dir.x * dz) + 0.5; 118 | let v = (-dir.y * dz) + 0.5; 119 | self.sample_uv(face, u, v) 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | pub fn read_texture>(path: P) -> std::io::Result { 127 | let file = std::fs::File::open(path.as_ref())?; 128 | let reader = std::io::BufReader::new(file); 129 | let img = image::load(reader, ImageFormat::Png).expect("Failed to load image."); 130 | Ok(img.into_rgb8()) 131 | } -------------------------------------------------------------------------------- /src/instack.rs: -------------------------------------------------------------------------------- 1 | 2 | pub struct InlineStack { 3 | stack: [T; SIZE], 4 | length: usize, 5 | } 6 | 7 | impl InlineStack { 8 | #[inline(always)] 9 | pub const fn new(default: T) -> Self { 10 | Self { 11 | stack: [default; SIZE], 12 | length: 0, 13 | } 14 | } 15 | 16 | #[inline(always)] 17 | pub fn push(&mut self, value: T) { 18 | self.stack[self.length] = value; 19 | self.length += 1; 20 | } 21 | 22 | #[inline(always)] 23 | pub unsafe fn push_unchecked(&mut self, value: T) { 24 | *self.stack.get_unchecked_mut(self.length) = value; 25 | self.length = self.length.unchecked_add(1); 26 | } 27 | 28 | #[inline(always)] 29 | pub fn pop(&mut self) -> T { 30 | self.length -= 1; 31 | self.stack[self.length] 32 | } 33 | 34 | #[inline(always)] 35 | pub unsafe fn pop_unchecked(&mut self) -> T { 36 | self.length = self.length.unchecked_sub(1); 37 | *self.stack.get_unchecked(self.length) 38 | } 39 | 40 | #[inline(always)] 41 | pub fn len(&self) -> usize { 42 | self.length 43 | } 44 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | pub mod perlin; 3 | pub mod open_simplex; 4 | pub mod math; 5 | pub mod chunk; 6 | pub mod camera; 7 | pub mod ray; 8 | pub mod cubemap; 9 | pub mod instack; -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | use std::{borrow::Borrow, collections::{HashMap, VecDeque}, path::PathBuf, sync::{atomic::AtomicU64, Arc, Mutex}, time::{Duration, Instant}}; 3 | 4 | use glam::*; 5 | use scratch::instack::InlineStack; 6 | use scratch::math::*; 7 | use scratch::camera::*; 8 | use scratch::perlin::perlin; 9 | use rayon::prelude::*; 10 | use noise::OpenSimplex; 11 | use scratch::cubemap::*; 12 | use image::{Rgb, RgbImage, Rgba, RgbaImage}; 13 | use noise::NoiseFn; 14 | use rand::{rngs::StdRng, Rng, SeedableRng, prelude::*}; 15 | use scratch::{camera::Camera, chunk::{self, Chunk, RayHit}, cubemap::Cubemap, math::{self, Face}, open_simplex::open_simplex_2d, perlin::{make_seed, Permutation}, ray::Ray3}; 16 | use sha2::digest::typenum::Diff; 17 | 18 | // do while loop, do[lo]op. 19 | macro_rules! doop { 20 | ($($name:lifetime : )? $block:block while $condition:expr) => { 21 | $($name :)?loop { 22 | $block 23 | if !($condition) { 24 | break $($name)?; 25 | } 26 | } 27 | }; 28 | } 29 | 30 | /// For toggling code for fast iteration. 31 | /// Syntax: 32 | /// ```rust, no_run 33 | /// // run 34 | /// code_toggle!([r] { 35 | /// println!("This code will be run because of the 'r'."); 36 | /// }); 37 | /// // no run 38 | /// code_toggle!([] { 39 | /// println!("There's no 'r' inside the brackets, so this code will not run."); 40 | /// }); 41 | /// ``` 42 | macro_rules! code_toggle { 43 | ($([$($kw:ident)?] {$($tokens:tt)*})*) => { 44 | $( 45 | code_toggle!{@unwrap; [$($kw)?] { $($tokens)* }} 46 | )* 47 | }; 48 | (@unwrap; [r] {$($tokens:tt)*}) => { 49 | $($tokens)* 50 | }; 51 | (@unwrap; [] {$($tokens:tt)*}) => {}; 52 | } 53 | 54 | macro_rules! timed { 55 | ($fmt:literal => $code:expr) => { 56 | let elapsed_time = timeit!{ 57 | $code; 58 | }; 59 | println!($fmt, time=elapsed_time); 60 | }; 61 | } 62 | 63 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 64 | pub struct Size { 65 | pub width: u32, 66 | pub height: u32, 67 | } 68 | 69 | impl std::fmt::Display for Size { 70 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 71 | write!(f, "{}x{}", self.width, self.height) 72 | } 73 | } 74 | 75 | impl Size { 76 | pub const fn new(width: u32, height: u32) -> Self { 77 | Self { 78 | width, 79 | height, 80 | } 81 | } 82 | 83 | #[inline(always)] 84 | pub const fn index(self, x: u32, y: u32) -> u32 { 85 | (y * self.width) + x 86 | } 87 | 88 | /// Gets the position based on the index. 89 | #[inline(always)] 90 | pub const fn inv_index(self, index: u32) -> (u32, u32) { 91 | (index % self.width, index / self.width) 92 | } 93 | 94 | #[inline(always)] 95 | pub const fn iter_width(self) -> std::ops::Range { 96 | 0..self.width 97 | } 98 | 99 | #[inline(always)] 100 | pub const fn iter_height(self) -> std::ops::Range { 101 | 0..self.height 102 | } 103 | } 104 | 105 | fn main() { 106 | let start = Instant::now(); 107 | raycast_scene(); 108 | let elapsed = start.elapsed(); 109 | println!("Program finished in {elapsed:.3?}"); 110 | } 111 | 112 | /// Used to calculate the ray direction towards -Z. 113 | #[derive(Debug, Clone, Copy)] 114 | pub struct RayCalc { 115 | mult: Vec2, 116 | } 117 | 118 | impl RayCalc { 119 | pub fn new(fov_rad: f32, screen_size: (u32, u32)) -> Self { 120 | let aspect_ratio = screen_size.0 as f32 / screen_size.1 as f32; 121 | let tan_fov_half = (fov_rad * 0.5).tan(); 122 | let asp_fov = aspect_ratio * tan_fov_half; 123 | Self { 124 | mult: vec2(asp_fov, tan_fov_half), 125 | } 126 | } 127 | 128 | #[inline(always)] 129 | pub fn calc_ray_dir(self, ndc: Vec2) -> Vec3A { 130 | let m = ndc * self.mult; 131 | vec3a(m.x, m.y, -1.0).normalize() 132 | } 133 | } 134 | 135 | /// OpenSimplex color sampling. 136 | struct PosColor { 137 | rsimp: OpenSimplex, 138 | gsimp: OpenSimplex, 139 | bsimp: OpenSimplex, 140 | scale: f64, 141 | } 142 | 143 | impl PosColor { 144 | #[inline(always)] 145 | pub fn get_with_scale(&self, pos: Vec3A, scale: f64) -> Vec3A { 146 | let pos_arr = [pos.x as f64 * scale, pos.y as f64 * scale, pos.z as f64 * scale]; 147 | let r: f64 = self.rsimp.get(pos_arr) * 0.5 + 0.5; 148 | let g: f64 = self.gsimp.get(pos_arr) * 0.5 + 0.5; 149 | let b: f64 = self.bsimp.get(pos_arr) * 0.5 + 0.5; 150 | vec3a(r as f32, g as f32, b as f32) 151 | } 152 | 153 | #[inline(always)] 154 | pub fn get(&self, pos: Vec3A) -> Vec3A { 155 | return Vec3A::ONE; 156 | // let pos_arr = [pos.x as f64 * self.scale, pos.y as f64 * self.scale, pos.z as f64 * self.scale]; 157 | // let r: f64 = self.rsimp.get(pos_arr) * 0.5 + 0.5; 158 | // let g: f64 = self.gsimp.get(pos_arr) * 0.5 + 0.5; 159 | // let b: f64 = self.bsimp.get(pos_arr) * 0.5 + 0.5; 160 | // vec3a(r as f32, g as f32, b as f32) 161 | } 162 | 163 | } 164 | 165 | #[inline(always)] 166 | fn norm_u8(norm: f32) -> u8 { 167 | (norm * 255.0) as u8 168 | } 169 | 170 | #[inline(always)] 171 | fn rgb(r: f32, g: f32, b: f32) -> Rgb { 172 | Rgb([ 173 | norm_u8(r), 174 | norm_u8(g), 175 | norm_u8(b), 176 | ]) 177 | } 178 | 179 | #[derive(Debug, Clone, Copy)] 180 | pub struct DirectionalLight { 181 | pub direction: Vec3A, 182 | pub color: Vec3A, 183 | pub intensity: f32, 184 | pub pre_calc: Vec3A, 185 | pub inv_dir: Vec3A, 186 | } 187 | 188 | impl DirectionalLight { 189 | pub fn new(direction: Vec3A, color: Vec3A, intensity: f32) -> Self { 190 | Self { 191 | direction, 192 | color, 193 | intensity, 194 | pre_calc: color * intensity, 195 | inv_dir: -direction, 196 | } 197 | } 198 | #[inline(always)] 199 | pub fn apply(&self, color: Vec3A, normal: Vec3A) -> Vec3A { 200 | let dot = self.inv_dir.dot(normal); 201 | (color * self.pre_calc) * dot 202 | } 203 | } 204 | 205 | #[derive(Debug, Clone, Copy)] 206 | pub struct AmbientLight { 207 | pub color: Vec3A, 208 | pub intensity: f32, 209 | pub pre_calc: Vec3A, 210 | } 211 | 212 | impl AmbientLight { 213 | pub fn new(color: Vec3A, intensity: f32) -> Self { 214 | Self { 215 | color, 216 | intensity, 217 | pre_calc: color * intensity, 218 | } 219 | } 220 | 221 | #[inline(always)] 222 | pub fn apply(self, color: Vec3A) -> Vec3A { 223 | self.pre_calc * color 224 | } 225 | } 226 | 227 | fn print_f32_range(range: std::ops::Range) { 228 | println!("{range:?}"); 229 | } 230 | 231 | #[test] 232 | fn pfr_test() { 233 | print_f32_range(0.1..1000.0); 234 | } 235 | 236 | pub struct Lighting { 237 | directional: DirectionalLight, 238 | ambient: AmbientLight, 239 | shadow: f32, 240 | } 241 | 242 | impl Lighting { 243 | #[inline(always)] 244 | pub fn calculate(&self, color: Vec3A, normal: Vec3A, occluded: bool) -> Vec3A { 245 | let ambient = self.ambient.color; 246 | let directional = self.directional.inv_dir.dot(normal); 247 | let directional_color = self.directional.pre_calc * directional; 248 | let light = ((1.0 - directional) * self.ambient.intensity) * ambient + directional_color; 249 | let color = color * light; 250 | if occluded { 251 | self.shadow * color 252 | } else { 253 | color 254 | } 255 | } 256 | } 257 | 258 | #[derive(Debug, Default, Clone, Copy)] 259 | pub struct Reflection { 260 | reflectivity: f32, 261 | } 262 | 263 | impl Reflection { 264 | pub fn new(reflectivity: f32) -> Self { 265 | Self { reflectivity } 266 | } 267 | 268 | /// Calculates the reflectivity based on the surface normal and view direction. 269 | #[inline(always)] 270 | pub fn calculate( 271 | self, 272 | surface_normal: Vec3A, 273 | view_dir: Vec3A, 274 | ) -> f32 { 275 | let dot = (-view_dir).dot(surface_normal).max(0.0); 276 | self.reflectivity + (1.0 - self.reflectivity) * (1.0 - dot).powf(5.0) 277 | } 278 | } 279 | 280 | /// Reflects a ray direction on a surface with the given normal. 281 | #[inline(always)] 282 | fn reflect(ray_dir: Vec3A, normal: Vec3A) -> Vec3A { 283 | ray_dir - 2.0 * (ray_dir * normal) * normal 284 | } 285 | 286 | /// Combine reflection color with diffuse based on reflectivity. 287 | #[inline(always)] 288 | fn combine_reflection(reflectivity: f32, diffuse: Vec3A, reflection: Vec3A) -> Vec3A { 289 | reflectivity * reflection + (1.0 - reflectivity) * diffuse 290 | } 291 | 292 | /// Holds raytracer stuff. 293 | pub struct TraceData { 294 | chunk: Chunk, 295 | skybox: Cubemap, 296 | pos_color: PosColor, 297 | lighting: Lighting, 298 | sky_color: Vec3A, 299 | reflection: Reflection, 300 | reflection_steps: u16, 301 | } 302 | 303 | // [diffuse7, diffuse6, diffuse5, diffuse4, 304 | // diffuse3, diffuse2, diffuse1, diffuse0] 305 | // [ 306 | // diffuse6 = combine(diffuse7, diffuse6, reflectivity6), 307 | // diffuse5 = combine(diffuse6, diffuse5, reflectivity5), 308 | // diffuse4 = combine(diffuse5, diffuse4, reflectivity4), 309 | // diffuse3 = combine(diffuse4, diffuse3, reflectivity3), 310 | // diffuse2 = combine(diffuse3, diffuse2, reflectivity2), 311 | // diffuse1 = combine(diffuse2, diffuse1, reflectivity1), 312 | // diffuse0 = combine(diffuse1, diffuse0, reflectivity0), 313 | // ] 314 | 315 | impl TraceData { 316 | /// Calculates the color of a hit point before reflection calculation. This will also calculate shadow. 317 | #[inline(always)] 318 | pub fn calc_color_before_reflections(&self, hit_point: Vec3A, hit_normal: Vec3A, hit_coord: IVec3) -> Vec3A { 319 | let checker = checkerboard(hit_coord.x, hit_coord.y, hit_coord.z); 320 | let checker_color = if checker { 321 | Vec3A::ONE 322 | } else { 323 | Vec3A::splat(0.3) 324 | }; 325 | let color = self.pos_color.get(hit_point) * checker_color; 326 | let light_ray = Ray3::new(hit_point, self.lighting.directional.inv_dir); 327 | let light_hit = self.chunk.raycast(light_ray, 112.0); 328 | self.lighting.calculate(color, hit_normal, light_hit.is_some()) 329 | } 330 | 331 | /// Calculates the grayscale color of a hit point before reflection calculation without a diffuse color. This will also calculate shadow. 332 | #[inline(always)] 333 | pub fn calc_color_before_reflections_no_diffuse(&self, hit_point: Vec3A, hit_normal: Vec3A) -> Vec3A { 334 | let color = Vec3A::ONE; 335 | let light_ray = Ray3::new(hit_point, self.lighting.directional.inv_dir); 336 | let light_hit = self.chunk.raycast(light_ray, 112.0); 337 | self.lighting.calculate(color, hit_normal, light_hit.is_some()) 338 | } 339 | 340 | pub fn trace_reflections_iterative(&self, ray: Ray3, near: f32, far: f32, steps: u16) -> Vec3A { 341 | let steps = steps as usize; 342 | let Some(hit) = self.chunk.raycast(ray, far) else { 343 | return self.sky_color(ray.dir); 344 | }; 345 | if hit.distance < near { 346 | // Return green when the hit point is too close. 347 | return Vec3A::Y; 348 | } 349 | let Some(face) = hit.face else { 350 | return Vec3A::X; 351 | }; 352 | let hit_point = hit.get_hit_point(ray, face); 353 | let hit_normal = face.normal(); 354 | // diffuse stack 355 | let mut rgb_stack = [Vec3A::ZERO; 6]; 356 | let mut rgbi = 0isize; 357 | macro_rules! rgbpush { 358 | ($color:expr) => { 359 | rgb_stack[rgbi as usize] = $color; 360 | rgbi += 1; 361 | }; 362 | } 363 | macro_rules! rgbpop { 364 | () => { 365 | { 366 | rgbi -= (rgbi > 0) as isize; 367 | let result = rgb_stack[rgbi as usize]; 368 | result 369 | } 370 | }; 371 | } 372 | // reflectivity stack 373 | let mut ref_stack = [0.0; 5]; 374 | let mut refi = 0isize; 375 | macro_rules! refpush { 376 | ($reflection:expr) => { 377 | ref_stack[refi as usize] = $reflection; 378 | refi += 1; 379 | }; 380 | } 381 | macro_rules! refpop { 382 | () => { 383 | { 384 | refi -= (refi > 0) as isize; 385 | let result = ref_stack[refi as usize]; 386 | result 387 | } 388 | }; 389 | } 390 | let diffuse = self.calc_color_before_reflections(hit_point, hit_normal, hit.coord); 391 | if self.chunk.get_reflection(hit.coord.x, hit.coord.y, hit.coord.z) { 392 | rgbpush!(diffuse); 393 | let reflectivity = self.reflection.calculate(hit_normal, ray.dir); 394 | let mut reflect_dir = reflect(ray.dir, hit_normal); 395 | let mut reflect_ray = Ray3::new(hit_point, reflect_dir); 396 | refpush!(reflectivity); 397 | let last_step = steps - 1; 398 | let mut cur_dist = hit.distance; 399 | for _step in 0..steps { 400 | let Some(hit) = self.chunk.raycast(reflect_ray, far - cur_dist) else { 401 | // rgb_stack.push(self.sky_color(reflect_dir)); 402 | rgbpush!(self.sky_color(reflect_dir)); 403 | break; 404 | }; 405 | let Some(face) = hit.face else { 406 | // rgb_stack.push(Vec3A::Y); 407 | rgbpush!(Vec3A::Y); 408 | break; 409 | }; 410 | cur_dist += hit.distance; 411 | let hit_point = hit.get_hit_point(reflect_ray, face); 412 | let hit_normal = face.normal(); 413 | let diffuse = self.calc_color_before_reflections(hit_point, hit_normal, hit.coord); 414 | // rgb_stack.push(diffuse); 415 | rgbpush!(diffuse); 416 | if !self.chunk.get_reflection(hit.coord.x, hit.coord.y, hit.coord.z) { 417 | break; 418 | } 419 | let reflectivity = self.reflection.calculate(hit_normal, reflect_dir); 420 | reflect_dir = reflect(reflect_ray.dir, hit_normal); 421 | reflect_ray = Ray3::new(hit_point, reflect_dir); 422 | refpush!(reflectivity); 423 | } 424 | assert_eq!(rgbi, refi + 1); 425 | for i in 0..refi { 426 | let reflectivity = refpop!(); 427 | let (reflection, diffuse) = (rgbpop!(), rgbpop!()); 428 | let mix = combine_reflection(reflectivity, diffuse, reflection); 429 | rgbpush!(mix); 430 | } 431 | // while let Some(reflect) = ref_stack.pop() { 432 | // let (rhs, lhs) = (rgb_stack.pop().unwrap(), rgb_stack.pop().unwrap()); 433 | // let mix = combine_reflection(reflect, lhs, rhs); 434 | // rgb_stack.push(mix); 435 | // } 436 | let final_color = rgbpop!(); 437 | final_color 438 | } else { 439 | diffuse 440 | } 441 | } 442 | 443 | pub fn trace_reflections_iterative_wip(&self, ray: Ray3, near: f32, far: f32, steps: u16) -> Vec3A { 444 | let mut refstack = const { InlineStack::<3, f32>::new(::NAN) }; 445 | let mut rgbstack = const { InlineStack::<4, Vec3A>::new(Vec3A::ZERO) }; 446 | let mut ray = ray; 447 | let mut max_distance = far; 448 | let steps = steps + 1; 449 | let last_step = steps - 1; 450 | let mut near = near; 451 | for step in 0..steps { 452 | let Some(hit) = self.chunk.raycast(ray, max_distance) else { 453 | unsafe { 454 | rgbstack.push_unchecked(self.sky_color(ray.dir)); 455 | } 456 | break; 457 | }; 458 | let Some(face) = hit.face else { 459 | unsafe { 460 | // Green so that it's obvious that 461 | // no face was hit, which shouldn't happen. 462 | rgbstack.push_unchecked(Vec3A::Y); 463 | } 464 | break; 465 | }; 466 | max_distance += hit.distance; 467 | let hit_point = hit.get_hit_point(ray, face); 468 | let hit_normal = face.normal(); 469 | let diffuse = self.calc_color_before_reflections(hit_point, hit_normal, hit.coord); 470 | if step == last_step || !self.chunk.get_reflection(hit.coord.x, hit.coord.y, hit.coord.z) { 471 | #[inline(always)] 472 | fn on_edge(x: f32, y: f32) -> bool { 473 | x < 0.05 || y < 0.05 || x >= 0.95 || y >= 0.95 474 | } 475 | let hit_fract = hit_point.fract(); 476 | let diffuse = match face { 477 | Face::PosX | Face::NegX if on_edge(hit_fract.y, hit_fract.z) => diffuse * 0.1, 478 | Face::PosY | Face::NegY if on_edge(hit_fract.x, hit_fract.z) => diffuse * 0.1, 479 | Face::PosZ | Face::NegZ if on_edge(hit_fract.x, hit_fract.y) => diffuse * 0.1, 480 | _ => diffuse, 481 | }; 482 | unsafe { 483 | rgbstack.push_unchecked(diffuse); 484 | } 485 | break; 486 | } 487 | unsafe { 488 | rgbstack.push_unchecked(diffuse); 489 | } 490 | let reflectivity = self.reflection.calculate(hit_normal, ray.dir); 491 | let reflect_dir = reflect(ray.dir, hit_normal); 492 | ray = Ray3::new(hit_point, reflect_dir); 493 | unsafe { 494 | refstack.push_unchecked(reflectivity); 495 | } 496 | } 497 | let len = refstack.len(); 498 | for i in 0..len { 499 | unsafe { 500 | let reflectivity = refstack.pop_unchecked(); 501 | let (reflection, diffuse) = (rgbstack.pop_unchecked(), rgbstack.pop_unchecked()); 502 | let mix = combine_reflection(reflectivity, diffuse, reflection); 503 | rgbstack.push_unchecked(mix); 504 | } 505 | } 506 | unsafe { 507 | rgbstack.pop_unchecked() 508 | } 509 | } 510 | 511 | /// Trace reflections for exact number of steps and calculate the color. 512 | pub fn trace_reflections(&self, ray: Ray3, near: f32, far: f32, steps: u16) -> Vec3A { 513 | let Some(hit) = self.chunk.raycast(ray, far) else { 514 | // no hit, so return the sky color. 515 | return self.sky_color(ray.dir); 516 | }; 517 | let Some(face) = hit.face else { 518 | // If there was no hit face, return Red so that it's clear that no hit was made. 519 | // this is useful for debugging since the camera will most likely be outside of the bounds of the scene, so there should be no red. 520 | // If there is red visible, that means that the ray is penetrating too far into the cube. 521 | return Vec3A::X; 522 | }; 523 | // The ray is too close, so return Green. (for debugging purposes) 524 | if hit.distance < near { 525 | return Vec3A::Y; 526 | } 527 | 528 | let hit_point = hit.get_hit_point(ray, face); 529 | let hit_normal = face.normal(); 530 | /// easy way to mix up colors is to create a checkerboard pattern. 531 | 532 | let mut diffuse = self.calc_color_before_reflections(hit_point, hit_normal, hit.coord); 533 | // apply the checkerboard pattern. 534 | // diffuse = diffuse * checker_color; 535 | /// Edge detection. 536 | #[inline(always)] 537 | fn on_edge(x: f32, y: f32) -> bool { 538 | x < 0.05 || y < 0.05 || x >= 0.95 || y >= 0.95 539 | } 540 | /// Check if we need to trace more reflections. (this will test if the hit surface is reflective). 541 | if steps != 0 && self.chunk.get_reflection(hit.coord.x, hit.coord.y, hit.coord.z) { 542 | let reflect_dir = reflect(ray.dir, hit_normal); 543 | let reflect_ray = Ray3::new(hit_point, reflect_dir); 544 | let reflectivity = self.reflection.calculate(hit_normal, ray.dir); 545 | let reflection = self.trace_reflections(reflect_ray, 0.0, far - hit.distance, steps - 1); 546 | let final_color = combine_reflection(reflectivity, diffuse, reflection); 547 | final_color 548 | } else { 549 | // no reflection in this branch, so just return the diffuse color. 550 | // Edge detection to draw grid-lines. 551 | let hit_fract = hit_point.fract(); 552 | match face { 553 | Face::PosX | Face::NegX if on_edge(hit_fract.y, hit_fract.z) => diffuse = diffuse * 0.1, 554 | Face::PosY | Face::NegY if on_edge(hit_fract.x, hit_fract.z) => diffuse = diffuse * 0.1, 555 | Face::PosZ | Face::NegZ if on_edge(hit_fract.x, hit_fract.y) => diffuse = diffuse * 0.1, 556 | _ => (), 557 | } 558 | diffuse 559 | } 560 | } 561 | 562 | /// This is the meat of the program. Just a simple `trace_color` method that returns the color that a ray "receives". 563 | /// This includes lighting and reflection calculations. 564 | #[inline(always)] 565 | pub fn trace_color(&self, ray: Ray3, near: f32, far: f32) -> Vec3A { 566 | self.trace_reflections(ray, near, far, 5) 567 | // self.trace_reflections_iterative_wip(ray, near, far, 3) 568 | } 569 | 570 | /// Calculate the sky color given the ray direction. This will sample a cubemap. 571 | #[inline(always)] 572 | pub fn sky_color(&self, ray_dir: Vec3A) -> Vec3A { 573 | let rgb = self.skybox.sample_dir(ray_dir); 574 | let color = vec3a( 575 | math::byte_scalar(rgb.0[0]), 576 | math::byte_scalar(rgb.0[1]), 577 | math::byte_scalar(rgb.0[2]), 578 | ); 579 | self.lighting.ambient.apply(color * self.sky_color) 580 | } 581 | } 582 | 583 | /// For creating a 3D checkerboard pattern. 584 | #[inline(always)] 585 | fn checkerboard(x: i32, y: i32, z: i32) -> bool { 586 | ((x ^ y ^ z) & 1) != 0 587 | } 588 | 589 | pub fn raycast_scene() { 590 | println!("Starting."); 591 | let start = Instant::now(); 592 | let skybox = { 593 | let directory = PathBuf::from("./assets/textures/skybox_001/"); 594 | let top_path = directory.join("purp_top.png"); 595 | let bottom_path = directory.join("purp_bottom.png"); 596 | let left_path = directory.join("purp_left.png"); 597 | let right_path = directory.join("purp_right.png"); 598 | let front_path = directory.join("purp_front.png"); 599 | let back_path = directory.join("purp_back.png"); 600 | let pos_x = read_texture(right_path).unwrap(); 601 | let pos_y = read_texture(top_path).unwrap(); 602 | let pos_z = read_texture(back_path).unwrap(); 603 | let neg_x = read_texture(left_path).unwrap(); 604 | let neg_y = read_texture(bottom_path).unwrap(); 605 | let neg_z = read_texture(front_path).unwrap(); 606 | 607 | Cubemap::new(pos_x, pos_y, pos_z, neg_x, neg_y, neg_z) 608 | }; 609 | let elapsed = start.elapsed(); 610 | println!("Loaded Cubemap in {elapsed:.3?}"); 611 | /// The seed for PosColor. 612 | const SEED: u32 = 1205912; 613 | // let simplex = OpenSimplex::new(SEED); 614 | let rsimp = OpenSimplex::new(SEED + 0); 615 | let gsimp = OpenSimplex::new(SEED + 1); 616 | let bsimp = OpenSimplex::new(SEED + 2); 617 | 618 | let pos_color = PosColor { 619 | rsimp, 620 | gsimp, 621 | bsimp, 622 | scale: 1.0, 623 | }; 624 | let mut rng = StdRng::from_seed(make_seed(SEED as u64)); 625 | // Super Sampling Anti-aliasing. 626 | // Setting this to true means that the scene will render at twice the resolution then downsample to the target resolution. 627 | // Perhaps the more optimal way to do this would be to shoot for rays for each pixel instead of just one, but I haven't gotten that far yet. 628 | const SSAA: bool = false; 629 | let perm = Permutation::from_seed(make_seed(SEED as u64)); 630 | // I've left several grid sizes here to test out different resolutions. 631 | // let size = Size::new(512, 512); 632 | // let size = Size::new(2048, 2048); 633 | // let size = Size::new(640, 480); 634 | // let size = Size::new(1280, 720); 635 | let size = Size::new(1920, 1080); // FHD 636 | // let size = Size::new(1920*2, 1080*2); // 4K 637 | // let size = Size::new(1920*4, 1080*4); // 8K 638 | 639 | let size = if SSAA { 640 | Size::new(size.width * 2, size.height * 2) 641 | } else { 642 | size 643 | }; 644 | // Different camera views for the scene. 645 | // favorite cam 646 | // let mut cam = Camera::from_look_at(vec3a(-24.0, 70.0-12.0, 48.0), vec3a(32., 32.-12., 32.), 45.0f32.to_radians(), 1.0, 100.0, (size.width, size.height)); 647 | // let mut cam = Camera::from_look_at(vec3a(-24.0, 70.0-2.0, 48.0+20.0), vec3a(0., 42.5+10.0, 32.5+20.0), 45.0f32.to_radians(), 1.0, 100.0, (size.width, size.height)); 648 | let mut cam = Camera::from_look_at(vec3a(-24.0, 70.0-12.0, 48.0+10.0), vec3a(0., 42.5, 32.5+10.0), 90.0f32.to_radians(), 1.0, 100.0, (size.width, size.height)); 649 | // let mut cam = Camera::from_look_at(vec3a(-24.0, 70.0-12.0, 64.0+24.0), vec3a(32., 32.-12., 32.), 90.0f32.to_radians(), 1.0, 100.0, (size.width, size.height)); 650 | // let mut cam = Camera::from_look_at(vec3a(-24.0, 70.0-12.0, 64.0+24.0), vec3a(32., 32.-12., 32.), 45.0f32.to_radians(), 1.0, 100.0, (size.width, size.height)); 651 | // let mut cam = Camera::from_look_at(vec3a(42.0, 7.0, 42.0), vec3a(32., 5., 32.), 90.0f32.to_radians(), 1.0, 100.0, (size.width, size.height)); 652 | // let mut cam = Camera::from_look_at(vec3a(24.0, 24.0, 16.0), vec3a(32.0, 8.0, 32.0), 90.0f32.to_radians(), 1.0, 100.0, (size.width, size.height)); 653 | 654 | let mut chunk = Chunk::new(); 655 | let mut trace = TraceData { 656 | chunk, 657 | skybox, 658 | pos_color, 659 | lighting: Lighting { 660 | directional: DirectionalLight::new( 661 | vec3a(0.5, -1.0, -1.0).normalize(), 662 | vec3a(1.0, 1.0, 1.0), 663 | 1.0, 664 | ), 665 | ambient: AmbientLight::new( 666 | // color 667 | Vec3A::ONE, 668 | // intensity 669 | 1.0, 670 | ), 671 | shadow: 0.2, 672 | }, 673 | // sky_color: Vec3A::splat(0.4), 674 | // sky_color: Vec3A::splat(0.0), 675 | // sky_color: Vec3A::splat(1.0), 676 | // sky_color: Vec3A::splat(3.0), 677 | sky_color: vec3a(2.0, 3.0, 3.0), 678 | reflection: Reflection { reflectivity: 0.5 }, 679 | // reflection: Reflection { reflectivity: 1.0 }, 680 | reflection_steps: 3, 681 | }; 682 | 683 | let ray_calc = RayCalc::new(cam.fov, cam.screen_size); 684 | let cam_pos = cam.position; 685 | // by creating a reference, then moving it into the closure, we get better performance than if we didn't use a move closure and didn't use a reference. 686 | // I'm not sure why, but that's just how it is. 687 | let cam_ref = &cam; 688 | let cam_rot = cam.rotation_matrix(); 689 | let start = Instant::now(); 690 | // Ray precalculation. 691 | // This will create all of the rays for the camera with the given size. 692 | let rays = (0..size.width*size.height).into_par_iter().map(move |i| { 693 | let (x, y) = size.inv_index(i); 694 | // NDC (normalized device coordinate) calculation. 695 | let xy = vec2(x as f32, (size.height - y) as f32); 696 | let wh = vec2(size.width as f32, size.height as f32); 697 | let screen_pos = (xy / wh) - 0.5; 698 | let dir = ray_calc.calc_ray_dir(screen_pos); 699 | cam_rot * dir 700 | }).collect::>(); 701 | let elapsed = start.elapsed(); 702 | println!("Calculated {} rays in {elapsed:.3?}", rays.len()); 703 | 704 | // Much of the code after this point is toggleable code for fast iteration. If you want to get to the raytracing part, skip ahead. 705 | // I'll put up a big sign that says "RAYTRACING". 706 | 707 | /// This is for generating some simple terrain for testing purposes. 708 | code_toggle!([r] { 709 | let start = Instant::now(); 710 | for x in 0..64 { 711 | for z in 0..64 { 712 | let min_dist = x.min(z).min(63-x).min(63-z); 713 | let falloff = (min_dist as f32) / 32.0; 714 | let height = (perlin(&perm, x as f32 / 64.0, z as f32 / 64.0) * 0.5) + 0.5; 715 | let h = ((height * 64.0 716 | * falloff 717 | ) as i32).max(0); 718 | trace.chunk.fill_box((x, 0, z), (x+1, h, z+1), true); 719 | if rng.random::() { 720 | trace.chunk.set_reflection(x, h - 1, z, true); 721 | } 722 | } 723 | } 724 | let elapsed = start.elapsed(); 725 | println!("Generated terrain in {elapsed:.3?}"); 726 | }); 727 | fn box_at(start: (i32, i32, i32)) -> std::ops::Range<(i32, i32, i32)> { 728 | start..(start.0 + 6, start.1 + 6, start.2 + 6) 729 | } 730 | fn next_start(end: (i32, i32, i32)) -> (i32, i32, i32) { 731 | (end.0 - 3, end.1 - 3, end.2 - 3) 732 | } 733 | 734 | fn boxes(start: (i32, i32, i32), chunk: &mut Chunk, rng: &mut StdRng) { 735 | let mut r = box_at(start); 736 | while r.end.0 <= 64 && r.end.1 <= 64 && r.end.2 <= 64 { 737 | if rng.random::() 738 | || true 739 | { 740 | chunk.draw_box(r.start, r.end, true); 741 | chunk.draw_box_reflection(r.start, r.end, true); 742 | if false 743 | // || true 744 | { 745 | let end = (r.end.0, r.start.1 + 1, r.end.2); 746 | chunk.fill_box(r.start, end, true); 747 | chunk.fill_box_reflection(r.start, end, true); 748 | // let m = 1; 749 | // let start = ( 750 | // r.start.0 + m, r.start.1 + m, r.start.2 + m 751 | // ); 752 | // let end = ( 753 | // end.0 - m, end.1 + m, end.2 - m 754 | // ); 755 | // chunk.fill_box(start, end, true); 756 | // let m = 1; 757 | // let start = ( 758 | // start.0 + m, start.1 + m, start.2 + m 759 | // ); 760 | // let end = ( 761 | // end.0 - m, end.1 + m, end.2 - m 762 | // ); 763 | // chunk.fill_box(start, end, true); 764 | break; 765 | } 766 | } 767 | r = box_at(next_start(r.end)); 768 | } 769 | } 770 | code_toggle!([] { 771 | let start = Instant::now(); 772 | for y in 0..64/10 { 773 | for z in 0..64/10 { 774 | for x in 0..64/10 { 775 | let mut r = box_at((x * 10, y * 10, z * 10)); 776 | trace.chunk.draw_box(r.start, r.end, true); 777 | let end = (r.end.0, r.start.1 + 1, r.end.2); 778 | trace.chunk.fill_box(r.start, end, true); 779 | if (x == 0 || z == 5) { 780 | trace.chunk.fill_box_reflection(r.start, end, true); 781 | // trace.chunk.draw_box_reflection(r.start, r.end, false); 782 | } 783 | // boxes((x * 10, y * 10, z * 10), &mut trace.chunk); 784 | } 785 | } 786 | } 787 | // trace.chunk.set(2, 41+10, 32+20, true); 788 | // trace.chunk.set_reflection(2, 41+10, 32+20, true); 789 | trace.chunk.set(2, 41, 32+10, true); 790 | // trace.chunk.set_reflection(2, 41, 32, true); 791 | trace.chunk.fill_box((0, 46, 0), (64, 64, 64), false); 792 | let st = 56/2 - 4; 793 | let en = st + 8; 794 | let b = 45; 795 | let t = b + 8; 796 | trace.chunk.draw_box((st, b, st), (en, t, en), true); 797 | // (0, 46, 0, true) // left 798 | // (55, 46, 0, true) // top middle 799 | // (0, 47, 55, true) // bottom middle 800 | // (55, 46, 55, true) // right 801 | trace.chunk.fill_box((0, 45, 0), (56, 46, 56), true); 802 | trace.chunk.fill_box_reflection((0, 45, 0), (56, 46, 56), true); 803 | let elapsed = start.elapsed(); 804 | println!("Placed blocks in {elapsed:.3?}"); 805 | }); 806 | // Apartments 807 | code_toggle!([] { 808 | for z in 0..15 { 809 | for x in 0..15 { 810 | for y in 0..15 { 811 | if rng.random_bool(0.9) { 812 | continue; 813 | } 814 | let start = ivec3(x * 4, y * 4, z * 4); 815 | // match 816 | // // rng.random_range(0..3) 817 | // 2 818 | // { 819 | // 0 => {} 820 | // 1 => { 821 | // let plst = start; 822 | // let plend = start + ivec3(5, 1, 5); 823 | // trace.chunk.fill_box(plst.into(), plend.into(), true); 824 | // trace.chunk.fill_box_reflection(plst.into(), plend.into(), true); 825 | // } 826 | // 2 => { 827 | // let plst = start + ivec3(0, 4, 0); 828 | // let plend = plst + ivec3(5, 1, 5); 829 | // trace.chunk.fill_box(plst.into(), plend.into(), true); 830 | // trace.chunk.fill_box_reflection(plst.into(), plend.into(), true); 831 | // } 832 | // _ => unreachable!() 833 | // } 834 | // let end = start + ivec3(4, 1, 4); 835 | let end = start + ivec3(5, 5, 5); 836 | trace.chunk.fill_box(start.into(), end.into(), true); 837 | if rng.random::() { 838 | trace.chunk.fill_box_reflection(start.into(), end.into(), true); 839 | } 840 | // trace.chunk.fill_box((start.x, start.y, start.z), (end.x, end.y, end.z), true); 841 | // trace.chunk.fill_box_reflection((start.x+1, start.y, start.z+1), (end.x, end.y, end.z), true); 842 | // trace.chunk.fill_box((start.x, start.y + 1, start.z), (start.x+1, start.y + 4, start.z+1), true); 843 | // trace.chunk.fill_box_reflection((start.x, start.y + 1, start.z), (start.x+1, start.y + 4, start.z+1), true); 844 | } 845 | } 846 | } 847 | }); 848 | code_toggle!([] { 849 | for z in 0..64 { 850 | for x in 0..64 { 851 | for y in 0..64 { 852 | // let present = trace.chunk.get(x, y, z); 853 | // if present && checkerboard(x, y, z) { 854 | // } 855 | trace.chunk.set_reflection(x, y, z, true); 856 | } 857 | } 858 | } 859 | let st = 56/2 - 4; 860 | let en = st + 8; 861 | let b = 46; 862 | let t = b + 7; 863 | trace.chunk.fill_box_reflection((st, b, st), (en, t, en), false); 864 | trace.chunk.set_reflection(2, 41, 32+10, false); 865 | }); 866 | code_toggle!([] { 867 | const HELLO: [&'static str; 5] = [ 868 | "1010111010001000111000101010111011001000110", 869 | "1010100010001000101000101010101010101000101", 870 | "1110111010001000101000101010101011001000101", 871 | "1010100010001000101000101010101010101000101", 872 | "1010111011101110111000111110111010101110110", 873 | ]; 874 | trace.chunk.fill_box((0, 31, 0), (12, 31+12, 63), false); 875 | trace.chunk.fill_box((0, 30, 0), (63, 31, 63), true); 876 | for i in 0..HELLO.len() { 877 | for j in 0..HELLO[i].len() { 878 | let y = 36 - i as i32; 879 | let z = 21 + j as i32; 880 | if HELLO[i].as_bytes()[j] == b'1' { 881 | trace.chunk.set(0, y, z, true); 882 | } 883 | } 884 | } 885 | }); 886 | 887 | // Reflection scene 888 | code_toggle!([] { 889 | trace.chunk.fill_box((0, 0, 0), (64, 2, 64), true); 890 | trace.chunk.fill_box_reflection((26, 0, 26), (38, 2, 38), true); 891 | trace.chunk.fill_box((31, 4, 31), (33, 6, 33), true); 892 | trace.chunk.fill_box_reflection((31, 4, 31), (33, 6, 33), true); 893 | }); 894 | 895 | code_toggle!([] { 896 | boxes((0, 0, 0), &mut trace.chunk); 897 | trace.chunk.fill_box((0, 0+16, 0), (64, 1+16, 64), true); 898 | trace.chunk.fill_box((31, 1+16, 31), (32, 16+16, 32), true); 899 | }); 900 | 901 | code_toggle!([] { 902 | trace.chunk.fill_box((0, 0, 0), (64, 1, 64), true); 903 | for i in 0..4 { 904 | for j in 0..4 { 905 | for k in 0..4 { 906 | let start = (i * 4 + 16, j * 4, k * 4 + 16); 907 | let end = (start.0 + 5, start.1 + 5, start.2 + 5); 908 | trace.chunk.draw_box(start, end, true); 909 | } 910 | } 911 | } 912 | trace.chunk.fill_box((17, 1, 17), (48, 31, 48), false); 913 | }); 914 | 915 | // for x in 0..64 { 916 | // for z in 0..64 { 917 | // for y in 0..64 { 918 | // let b = trace.chunk.get(x, y, z); 919 | // let cb1 = checkerboard(x, y, z); 920 | // let cb2 = checkerboard(x/2, y/2, z/2); 921 | // let cb3 = checkerboard(x/4, y/4, z/4); 922 | // trace.chunk.set(x, y, z, b && cb1 && cb2 && cb3); 923 | // } 924 | // } 925 | // } 926 | 927 | let mut img = RgbImage::new(size.width, size.height); 928 | 929 | let near = 0.1; 930 | let far = 250.0; 931 | // the following commented out line is for normalized depth calculation (0 for closest, 1 for furthest). 932 | // let depth_mul = 1.0 / (far - near); // calculate depth with `ray_distance * depth_mul` 933 | // ############################################# 934 | // # RAYTRACING! # 935 | // ############################################# 936 | let start = std::time::Instant::now(); 937 | { 938 | let trace = &trace; 939 | let rays = rays.as_slice(); 940 | let counter = std::sync::Arc::new(AtomicU64::new(0)); 941 | let progress = indicatif::ProgressBar::new(rays.len() as u64); 942 | let prog = &progress; 943 | img.par_pixels_mut().enumerate().for_each(move |(i, pixel)| { 944 | let ray = Ray3::new(cam_pos, rays[i]); 945 | let color = trace.trace_color(ray, near, far); 946 | *pixel = rgb(color.x, color.y, color.z); 947 | // let count = counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed); 948 | // if count % 1000000 == 0 { 949 | // prog.set_position(count); 950 | // } 951 | return; 952 | }); 953 | prog.finish_and_clear(); 954 | } 955 | let elapsed = start.elapsed(); 956 | println!("Rendered {size} image in {elapsed:.3?}"); 957 | 958 | use image::*; 959 | const FILE_NAME: &str = "raytrace.png"; 960 | if SSAA { 961 | let size = Size::new(size.width/2, size.height/2); 962 | let start = Instant::now(); 963 | let img = DynamicImage::ImageRgb8(img); 964 | let resized = img.resize_exact(size.width, size.height, imageops::FilterType::Lanczos3); 965 | let elapsed = start.elapsed(); 966 | println!("SSAA downscale to {size} in {elapsed:?}"); 967 | resized.save(FILE_NAME).expect("Failed to save image."); 968 | println!("File saved to \"{FILE_NAME}\""); 969 | } else { 970 | img.save(FILE_NAME).expect("Failed to save image."); 971 | } 972 | } -------------------------------------------------------------------------------- /src/math.rs: -------------------------------------------------------------------------------- 1 | use core::f32; 2 | 3 | use glam::*; 4 | 5 | use crate::ray::Ray3; 6 | 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | pub enum Axis { 9 | X, 10 | Y, 11 | Z, 12 | } 13 | 14 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 15 | pub enum Face { 16 | PosX = 0, 17 | PosY = 1, 18 | PosZ = 2, 19 | NegX = 3, 20 | NegY = 4, 21 | NegZ = 5, 22 | } 23 | 24 | impl Face { 25 | #[inline] 26 | pub fn axis(self) -> Axis { 27 | match self { 28 | Face::PosX | Face::NegX => Axis::X, 29 | Face::PosY | Face::NegY => Axis::Y, 30 | Face::PosZ | Face::NegZ => Axis::Z, 31 | } 32 | } 33 | 34 | #[inline] 35 | pub const fn normal(self) -> Vec3A { 36 | match self { 37 | Face::PosX => Vec3A::X, 38 | Face::PosY => Vec3A::Y, 39 | Face::PosZ => Vec3A::Z, 40 | Face::NegX => Vec3A::NEG_X, 41 | Face::NegY => Vec3A::NEG_Y, 42 | Face::NegZ => Vec3A::NEG_Z, 43 | } 44 | } 45 | 46 | pub fn from_direction(dir: Vec3A) -> Self { 47 | let abs = dir.abs(); 48 | if abs.x >= abs.y { 49 | if abs.x >= abs.z { 50 | if dir.x.is_sign_negative() { 51 | Face::NegX 52 | } else { 53 | Face::PosX 54 | } 55 | } else { 56 | if dir.z.is_sign_negative() { 57 | Face::NegZ 58 | } else { 59 | Face::PosZ 60 | } 61 | } 62 | } else { 63 | if abs.y >= abs.z { 64 | if dir.y.is_sign_negative() { 65 | Face::NegY 66 | } else { 67 | Face::PosY 68 | } 69 | } else { 70 | if dir.z.is_sign_negative() { 71 | Face::NegZ 72 | } else { 73 | Face::PosZ 74 | } 75 | } 76 | } 77 | } 78 | 79 | #[inline] 80 | pub fn index(self) -> usize { 81 | self as usize 82 | } 83 | } 84 | include!("byte_to_f32.rs"); 85 | 86 | #[inline] 87 | pub const fn byte_scalar(byte: u8) -> f32 { 88 | BYTE_TO_F32[byte as usize] 89 | } 90 | 91 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 92 | pub enum Sign { 93 | Neg = -1, 94 | Zero = 0, 95 | Pos = 1, 96 | } 97 | 98 | #[inline] 99 | pub const fn scrunch(value: f32) -> f32 { 100 | 1.0 / value 101 | } 102 | 103 | #[inline] 104 | pub const fn scrunch_factor(cell_size: Vec3) -> Vec3 { 105 | vec3( 106 | scrunch(cell_size.x), 107 | scrunch(cell_size.y), 108 | scrunch(cell_size.z), 109 | ) 110 | } 111 | 112 | pub fn scrunch_vector(input: Vec3, cell_size: Vec3) -> Vec3 { 113 | // So the idea is that you have a cell_size, and you have a direction/position, and you want 114 | // to emulate a cell_size of (1.0, 1.0, 1.0), so you "scrunch" the direction/position 115 | // So that it's the equivalent of a cell_size of (1.0, 1.0, 1.0). 116 | input * scrunch_factor(cell_size) 117 | } 118 | 119 | pub fn next_face(point: Vec3, direction: Vec3, cell_size: Vec3, cell_offset: Vec3) -> (Face, f32) { 120 | let offset_point = point - cell_offset; 121 | let cell_offset = vec3( 122 | offset_point.x.rem_euclid(cell_size.x), 123 | offset_point.y.rem_euclid(cell_size.y), 124 | offset_point.z.rem_euclid(cell_size.z), 125 | ); 126 | let x_distance = if direction.x > 0.0 { 127 | let grid_remainder = cell_size.x - cell_offset.x; 128 | grid_remainder / direction.x 129 | } else if direction.x < 0.0 { 130 | if cell_offset.x == 0.0 { 131 | cell_size.x / -direction.x 132 | } else { 133 | cell_offset.x / -direction.x 134 | } 135 | } else { 136 | f32::INFINITY 137 | }; 138 | let y_distance = if direction.y > 0.0 { 139 | let grid_remainder = cell_size.y - cell_offset.y; 140 | grid_remainder / direction.y 141 | } else if direction.y < 0.0 { 142 | if cell_offset.y == 0.0 { 143 | cell_size.y / -direction.y 144 | } else { 145 | cell_offset.y / -direction.y 146 | } 147 | } else { 148 | f32::INFINITY 149 | }; 150 | let z_distance = if direction.z > 0.0 { 151 | let grid_remainder = cell_size.z - cell_offset.z; 152 | grid_remainder / direction.z 153 | } else if direction.z < 0.0 { 154 | if cell_offset.z == 0.0 { 155 | cell_size.z / -direction.z 156 | } else { 157 | cell_offset.z / -direction.z 158 | } 159 | } else { 160 | f32::INFINITY 161 | }; 162 | 163 | if x_distance < y_distance { 164 | if x_distance < z_distance { 165 | (if direction.x > 0.0 { Face::PosX } else { Face::NegX }, x_distance) 166 | } else { 167 | (if direction.z > 0.0 { Face::PosZ } else { Face::NegZ }, z_distance) 168 | } 169 | } else { 170 | if y_distance < z_distance { 171 | (if direction.y > 0.0 { Face::PosY } else { Face::NegY }, y_distance) 172 | } else { 173 | (if direction.z > 0.0 { Face::PosZ } else { Face::NegZ }, z_distance) 174 | } 175 | } 176 | } 177 | 178 | // pub fn raycast(point: Vec3, direction: Vec3, cell_size: Vec3, cell_offset: Vec3, step_count: usize, mut f: F) { 179 | // let mut dist_accum = 0.0; 180 | // let mut steps = 0; 181 | // let mut p = point; 182 | // while steps < step_count { 183 | // let (axis, dist) = next_face(p, direction, cell_size, cell_offset); 184 | // dist_accum += dist; 185 | // p = point + direction * dist_accum; 186 | // f(axis, p); 187 | // steps += 1; 188 | // } 189 | // } 190 | 191 | pub fn raycast bool>( 192 | ray: Ray3, 193 | cell_size: Vec3A, 194 | cell_offset: Vec3A, 195 | mut callback: F, 196 | ) { 197 | fn calc_step(cell_size: f32, magnitude: f32) -> f32 { 198 | cell_size / magnitude.abs().max(::MIN_POSITIVE) 199 | } 200 | let delta = vec3( 201 | calc_step(cell_size.x, ray.dir.x), 202 | calc_step(cell_size.y, ray.dir.y), 203 | calc_step(cell_size.z, ray.dir.z), 204 | ); 205 | 206 | let sign = ray.dir.signum(); 207 | let step = sign.as_ivec3(); 208 | let face = ( 209 | if step.x >= 0 { 210 | Face::NegX 211 | } else { 212 | Face::PosX 213 | }, 214 | if step.y >= 0 { 215 | Face::NegY 216 | } else { 217 | Face::PosY 218 | }, 219 | if step.z >= 0 { 220 | Face::NegZ 221 | } else { 222 | Face::PosZ 223 | }, 224 | ); 225 | 226 | let origin = ray.pos - cell_offset; 227 | let inner = origin.rem_euclid(cell_size); 228 | 229 | fn calc_t_max(step: i32, cell_size: f32, p: f32, magnitude: f32) -> f32 { 230 | if step > 0 { 231 | (cell_size - p) / magnitude.abs().max(::MIN_POSITIVE) 232 | } else if step < 0 { 233 | p / magnitude.abs().max(::MIN_POSITIVE) 234 | } else { 235 | f32::INFINITY 236 | } 237 | } 238 | let mut t_max = vec3( 239 | calc_t_max(step.x, cell_size.x, inner.x, ray.dir.x), 240 | calc_t_max(step.y, cell_size.y, inner.y, ray.dir.y), 241 | calc_t_max(step.z, cell_size.z, inner.z, ray.dir.z), 242 | ); 243 | 244 | let mut cell = (origin / cell_size).floor().as_ivec3(); 245 | callback(&cell, Face::PosY, 0.0); 246 | loop { 247 | if t_max.x <= t_max.y { 248 | if t_max.x <= t_max.z { 249 | cell.x += step.x; 250 | if callback(&cell, face.0, t_max.x) { 251 | return; 252 | } 253 | t_max.x += delta.x; 254 | } else { 255 | cell.z += step.z; 256 | if callback(&cell, face.2, t_max.z) { 257 | return; 258 | } 259 | t_max.z += delta.z; 260 | } 261 | } else { 262 | if t_max.y <= t_max.z { 263 | cell.y += step.y; 264 | if callback(&cell, face.1, t_max.y) { 265 | return; 266 | } 267 | t_max.y += delta.y; 268 | } else { 269 | cell.z += step.z; 270 | if callback(&cell, face.2, t_max.z) { 271 | return; 272 | } 273 | t_max.z += delta.z; 274 | } 275 | } 276 | } 277 | } 278 | 279 | // fn part1by1(n: u32) -> u32 { 280 | // var n = n & 0x0000ffff; 281 | // n = (n | (n << 8)) & 0x00ff00ff; 282 | // n = (n | (n << 4)) & 0x0f0f0f0f; 283 | // n = (n | (n << 2)) & 0x33333333; 284 | // n = (n | (n << 1)) & 0x55555555; 285 | // return n; 286 | // } 287 | 288 | // fn morton_encode(x: u32, y: u32) -> u32 { 289 | // return (part1by1(y) << 1) | part1by1(x); 290 | // } 291 | 292 | // fn chunk_index_flat(x: usize, y: usize, z: usize) -> usize { 293 | // x | (y << 4) | (z << 8) 294 | // } 295 | 296 | // Morton index for 2048x2048 297 | 298 | // fn chunk_index_morton(x: usize, y: usize, z: usize) -> usize { 299 | // #[inline(always)] 300 | // fn shutter(n: usize) -> usize { 301 | // // 0b1111 302 | // // 0b1010101 303 | // // 0b001001001001 304 | // let step1 = (n | (n << 4)) & 0b000011000011000011000011000011000011; 305 | // return (step1 | (step1 << 2)) & 0b001001001001001001001001001001001001; 306 | // } 307 | // shutter(x) | (shutter(y) << 1) | (shutter(z) << 2) 308 | // } 309 | 310 | // fn morton2_2048(x: u32, y: u32) -> u32 { 311 | // #[inline(always)] 312 | // fn shutter(n: u32) -> u32 { 313 | // // 0b 11 111 111 111 314 | // // 0b101010101010101010101 315 | // let step1 = (n | (n << 10)) & 0b0000000000011111111111; 316 | // } 317 | // } 318 | 319 | // fn chunk_index_morton(x: usize, y: usize, z: usize) -> usize { 320 | // #[inline(always)] 321 | // fn shutter(n: usize) -> usize { 322 | // // 0b1111100000000000111111 323 | // // 0b1100000011100000000000111000111 324 | // // 0b1100000011100000000000111000000111 325 | // let step1 = (n | (n << 11)) & 0b1111100000000000111111; 326 | // let step2 = (n | (n << 6)) & 0b1100000011100000000000111000000111; 327 | // (step1 | (step1 << 3)) & 0b001001001001001001001001001001001001001001001001001001001001001 328 | // } 329 | // shutter(x) | (shutter(y) << 1) | (shutter(z) << 2) 330 | // } 331 | 332 | // #[test] 333 | // fn morton_test() { 334 | // let morton = chunk_index_morton(8, 31, 31); 335 | // println!("Morton: {morton}"); 336 | // } 337 | 338 | #[test] 339 | fn to_i32_test() { 340 | let v = f32::NEG_INFINITY; 341 | println!("{}", v.signum() as i32); 342 | } 343 | 344 | #[test] 345 | fn scrunch_test() { 346 | let size = vec3(3.0, 2.0, 1.0); 347 | let p = vec3(1.0, 1.0, 1.0); 348 | let s = scrunch_vector(p, size); 349 | println!("{s}"); 350 | println!("{}", s * size); 351 | } 352 | 353 | // #[test] 354 | // fn raycast_test() { 355 | // let point = vec3(0.5, 0.5, 0.5); 356 | // let direction = vec3(1.0, 0.0, 0.0).normalize(); 357 | // let cell_size = Vec3::ONE; 358 | // let cell_offset = Vec3::ZERO; 359 | // // let mut counter = 0usize; 360 | // let start = std::time::Instant::now(); 361 | // // raycast(Ray3::new(point, direction), cell_size, cell_offset, |p, d| { 362 | // // println!("{p:?}, {d}"); 363 | // // let loc = point + direction * d; 364 | // // println!("Location: {loc:?}"); 365 | // // d < 100.0 366 | // // }); 367 | // // for _ in 0..256 { 368 | // // for _ in 0..256 { 369 | // // raycast(point, direction, cell_size, cell_offset, 10, |_, _, _| { 370 | // // // println!("{p} {i}"); 371 | // // counter += 1; 372 | // // true 373 | // // }); 374 | // // } 375 | // // } 376 | // println!("Elapsed: {:?}", start.elapsed()); 377 | // // println!("Counter: {counter}"); 378 | // } -------------------------------------------------------------------------------- /src/open_simplex.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use super::perlin::{Permutation, lerp, make_seed}; 4 | 5 | #[rustfmt::skip] 6 | const GRADIENTS_2D: [(f32, f32); 8] = [ 7 | (5.0, 2.0), (2.0, 5.0), (-5.0, 2.0), (-2.0, 5.0), 8 | (5.0, -2.0), (2.0, -5.0), (-5.0, -2.0), (-2.0, -5.0), 9 | ]; 10 | 11 | const SQRT3: f32 = 1.73205080757; 12 | const F2: f32 = 0.5 * (SQRT3 - 1.0); 13 | const G2: f32 = (3.0 - SQRT3) / 6.0; 14 | 15 | pub fn open_simplex_2d(permutation: &Permutation, x: f32, y: f32) -> f32 { 16 | let s = (x + y) * F2; 17 | let xs = x + s; 18 | let ys = y + s; 19 | 20 | let i = xs.floor() as isize; 21 | let j = ys.floor() as isize; 22 | 23 | // Unskew to get original coordinates 24 | let t = (i + j) as f32 * G2; 25 | let x0 = x - (i as f32 - t); 26 | let y0 = y - (j as f32 - t); 27 | 28 | // Determine simplex corner order 29 | let (i1, j1) = if x0 > y0 { (1, 0) } else { (0, 1) }; 30 | 31 | // Second corner coordinates 32 | let x1 = x0 - i1 as f32 + G2; 33 | let y1 = y0 - j1 as f32 + G2; 34 | 35 | // Third corner coordinates 36 | let x2 = x0 - 1.0 + 2.0 * G2; 37 | let y2 = y0 - 1.0 + 2.0 * G2; 38 | 39 | // Hash gradient indices 40 | let ii = (i & 255) as usize; 41 | let jj = (j & 255) as usize; 42 | let gi0 = permutation.get(ii + permutation.get(jj)) & 7; 43 | let gi1 = permutation.get(ii + i1 + permutation.get(jj + j1)) & 7; 44 | let gi2 = permutation.get(ii + 1 + permutation.get(jj + 1)) & 7; 45 | 46 | // Computate dot products 47 | let mut n0 = 0.0; 48 | let mut n1 = 0.0; 49 | let mut n2 = 0.0; 50 | 51 | let t0 = 0.5 - x0 * x0 - y0 * y0; 52 | if t0 > 0.0 { 53 | let g = GRADIENTS_2D[gi0]; 54 | n0 = (t0 * t0) * (t0 * t0) * (g.0 * x0 + g.1 * y0); 55 | } 56 | 57 | let t1 = 0.5 - x1 * x1 - y1 * y1; 58 | if t1 > 0.0 { 59 | let g = GRADIENTS_2D[gi1]; 60 | n1 = (t1 * t1) * (t1 * t1) * (g.0 * x1 + g.1 * y1); 61 | } 62 | 63 | let t2 = 0.5 - x2 * x2 - y2 * y2; 64 | if t2 > 0.0 { 65 | let g = GRADIENTS_2D[gi2]; 66 | n2 = (t2 * t2) * (t2 * t2) * (g.0 * x2 + g.1 * y2); 67 | } 68 | 69 | // Sum contributions and scale 70 | // 18.541 was the value I found to scale the best. It's not perfect, but there is no perfect value. It's close enough. 71 | 18.541 * (n0 + n1 + n2) 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use image::Rgba; 77 | 78 | use super::*; 79 | #[test] 80 | fn open_simplex_test() { 81 | let permutation = Permutation::from_seed(make_seed(09512)); 82 | 83 | let mut img = image::RgbaImage::new(512, 512); 84 | let mut min = f32::INFINITY; 85 | let mut max = f32::NEG_INFINITY; 86 | for y in 0..512 { 87 | for x in 0..512 { 88 | // let (xf, yf) = make_uv(x, y); 89 | // let noise = perlin(&perm, xf * 10.0, yf * 10.0); 90 | let noise = open_simplex_2d(&permutation, (x as f32 + 0.0) * 0.01, (y as f32 + 0.0) * 0.01); 91 | if noise < min { 92 | min = noise; 93 | } 94 | if noise > max { 95 | max = noise; 96 | } 97 | let norm = ((noise + 1.0) / 2.0).clamp(0.0, 1.0); 98 | let gray = (norm * 255.0) as u8; 99 | img.put_pixel(x, y, Rgba([gray, gray, gray, 255])); 100 | } 101 | } 102 | println!("Min: {min:.7}\nMax: {max:.7}"); 103 | img.save("noise.png").expect("Failed to save image."); 104 | } 105 | } -------------------------------------------------------------------------------- /src/perlin.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | use rand::prelude::*; 3 | use rand::seq::SliceRandom; 4 | use sha2::Digest; 5 | use sha2::Sha256; 6 | 7 | pub fn lerp(a: f32, b: f32, t: f32) -> f32 { 8 | a + (b - a) * t 9 | } 10 | 11 | pub fn make_seed(value: u64) -> [u8; 32] { 12 | use std::io::Write; 13 | let mut hasher = Sha256::default(); 14 | hasher.write_all(&value.to_le_bytes()).expect("Failed to write."); 15 | hasher.finalize().into() 16 | } 17 | 18 | pub struct Permutation { 19 | // Duplicated for cache locality when overflow happens. Removes the need for bitwise instructions. 20 | array: Box<[u8; 512]> 21 | } 22 | 23 | impl Permutation { 24 | pub fn get(&self, index: usize) -> usize { 25 | self.array[index] as usize 26 | } 27 | 28 | pub fn from_seed(seed: [u8; 32]) -> Self { 29 | let mut rng = StdRng::from_seed(seed); 30 | let mut array: [u8; 256] = std::array::from_fn(|i| i as u8); 31 | array.shuffle(&mut rng); 32 | let array: Box<[u8; 512]> = Box::new(std::array::from_fn(move |i| array[i & 255])); 33 | Self { 34 | array 35 | } 36 | 37 | } 38 | } 39 | 40 | fn fade(t: f32) -> f32 { 41 | t * t * t * (t * (t * 6.0 - 15.0) + 10.0) 42 | } 43 | 44 | fn grad(hash: usize, x: f32, y: f32) -> f32 { 45 | let h = hash & 7; 46 | let u = if h < 4 { x } else { y }; 47 | let v = if h < 4 { y } else { x }; 48 | (if (h & 1) == 0 { u } else { -u }) + (if (h & 2) == 0 { v } else { -v }) 49 | } 50 | 51 | pub fn perlin(permutation: &Permutation, x: f32, y: f32) -> f32 { 52 | let x0 = x.floor() as usize & 255; 53 | let y0 = y.floor() as usize & 255; 54 | let x1 = x0 + 1; 55 | let y1 = y0 + 1; 56 | 57 | let xf = x.fract(); 58 | let yf = y.fract(); 59 | 60 | let u = fade(xf); 61 | let v = fade(yf); 62 | 63 | let a = permutation.get(x0) + y0; 64 | let b = permutation.get(x1) + y0; 65 | let c = permutation.get(x0) + y1; 66 | let d = permutation.get(x1) + y1; 67 | 68 | let x1_interp = lerp(grad(permutation.get(a), xf, yf), grad(permutation.get(b), xf - 1.0, yf), u); 69 | let x2_interp = lerp(grad(permutation.get(c), xf, yf - 1.0), grad(permutation.get(d), xf - 1.0, yf - 1.0), u); 70 | 71 | lerp(x1_interp, x2_interp, v) 72 | } 73 | 74 | fn make_uv(x: u32, y: u32) -> (f32, f32) { 75 | (x as f32 / 511.0, y as f32 / 511.0) 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use std::io::Write; 81 | 82 | use image::Rgba; 83 | 84 | use super::*; 85 | #[test] 86 | fn perlin_test() { 87 | let perm = Permutation::from_seed(make_seed(1531096152109)); 88 | let mut img = image::RgbaImage::new(512, 512); 89 | 90 | for y in 0..512 { 91 | for x in 0..512 { 92 | // let (xf, yf) = make_uv(x, y); 93 | // let noise = perlin(&perm, xf * 10.0, yf * 10.0); 94 | let noise = perlin(&perm, (x as f32 + 0.1) * 0.03, (y as f32 + 0.1) * 0.03); 95 | let norm = (noise + 1.0) / 2.0; 96 | let gray = (norm * 255.0) as u8; 97 | img.put_pixel(x, y, Rgba([gray, gray, gray, 255])); 98 | } 99 | } 100 | 101 | img.save("noise.png").expect("Failed to save image."); 102 | } 103 | } -------------------------------------------------------------------------------- /src/ray.rs: -------------------------------------------------------------------------------- 1 | use glam::*; 2 | 3 | #[derive(Debug, Default, Clone, Copy, PartialEq)] 4 | pub struct Ray3 { 5 | pub pos: Vec3A, 6 | pub dir: Vec3A, 7 | } 8 | 9 | impl Ray3 { 10 | /// Creates a new [Ray3]. 11 | /// 12 | /// This does not normalize the direction, so make sure you normalize 13 | /// that first. 14 | #[inline(always)] 15 | pub fn new(pos: Vec3A, dir: Vec3A) -> Self { 16 | Self { 17 | pos, 18 | dir, 19 | } 20 | } 21 | 22 | #[inline(always)] 23 | pub fn from_target(pos: Vec3A, target: Vec3A) -> Self { 24 | Self { 25 | pos, 26 | dir: (target - pos).normalize(), 27 | } 28 | } 29 | 30 | #[inline(always)] 31 | pub fn invert_dir(self) -> Self { 32 | Self { 33 | pos: self.pos, 34 | dir: -self.dir, 35 | } 36 | } 37 | 38 | #[inline(always)] 39 | pub fn point_on_ray(&self, distance: f32) -> Vec3A { 40 | (self.dir * distance) + self.pos 41 | } 42 | } --------------------------------------------------------------------------------