├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Geometric.obj ├── README.md ├── Room.obj ├── TODO.txt ├── benches ├── cast.rs ├── main.rs ├── ray.rs └── vec3.rs ├── cobblestone_normal.jpg ├── grid.jpg ├── output.png ├── specular.jpeg ├── src ├── bin │ └── main.rs ├── cast.rs ├── color.rs ├── fidelity_consts.rs ├── frame.rs ├── illumination.rs ├── intersection.rs ├── lib.rs ├── material.rs ├── matrix.rs ├── mesh.rs ├── mtl_parser.rs ├── obj_parser.rs ├── object.rs ├── plane.rs ├── ray.rs ├── scenes.rs ├── sphere.rs ├── texture.rs ├── utils.rs └── vec3.rs ├── test.obj ├── texture.jpg ├── tree.mtl └── tree.obj /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | **/*.rs.bk 3 | .DS_Store -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler32" 5 | version = "1.0.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "arrayvec" 10 | version = "0.4.12" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 14 | ] 15 | 16 | [[package]] 17 | name = "atty" 18 | version = "0.2.13" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "0.1.7" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | 30 | [[package]] 31 | name = "bitflags" 32 | version = "1.2.1" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | 35 | [[package]] 36 | name = "bstr" 37 | version = "0.2.8" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | dependencies = [ 40 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 42 | "regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 43 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 44 | ] 45 | 46 | [[package]] 47 | name = "byteorder" 48 | version = "1.3.2" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | 51 | [[package]] 52 | name = "c2-chacha" 53 | version = "0.2.2" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | dependencies = [ 56 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 58 | ] 59 | 60 | [[package]] 61 | name = "cast" 62 | version = "0.2.3" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | dependencies = [ 65 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 66 | ] 67 | 68 | [[package]] 69 | name = "cfg-if" 70 | version = "0.1.10" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | 73 | [[package]] 74 | name = "clap" 75 | version = "2.33.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | dependencies = [ 78 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 79 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 80 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 81 | ] 82 | 83 | [[package]] 84 | name = "color_quant" 85 | version = "1.0.1" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | 88 | [[package]] 89 | name = "crc32fast" 90 | version = "1.2.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | dependencies = [ 93 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 94 | ] 95 | 96 | [[package]] 97 | name = "criterion" 98 | version = "0.3.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | dependencies = [ 101 | "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 103 | "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", 104 | "criterion-plot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 108 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 109 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 110 | "rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 111 | "rand_xoshiro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 112 | "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 113 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 114 | "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "tinytemplate 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 117 | "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", 118 | ] 119 | 120 | [[package]] 121 | name = "criterion-plot" 122 | version = "0.4.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | dependencies = [ 125 | "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 126 | "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", 127 | ] 128 | 129 | [[package]] 130 | name = "crossbeam" 131 | version = "0.7.2" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | dependencies = [ 134 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 135 | "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 136 | "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 137 | "crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 138 | "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 139 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 140 | ] 141 | 142 | [[package]] 143 | name = "crossbeam-channel" 144 | version = "0.3.9" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | dependencies = [ 147 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 148 | ] 149 | 150 | [[package]] 151 | name = "crossbeam-deque" 152 | version = "0.7.1" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | dependencies = [ 155 | "crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 156 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 157 | ] 158 | 159 | [[package]] 160 | name = "crossbeam-epoch" 161 | version = "0.7.2" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | dependencies = [ 164 | "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 166 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 167 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 168 | "memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 169 | "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 170 | ] 171 | 172 | [[package]] 173 | name = "crossbeam-queue" 174 | version = "0.1.2" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | dependencies = [ 177 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 178 | ] 179 | 180 | [[package]] 181 | name = "crossbeam-utils" 182 | version = "0.6.6" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | dependencies = [ 185 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 186 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 187 | ] 188 | 189 | [[package]] 190 | name = "csv" 191 | version = "1.1.1" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | dependencies = [ 194 | "bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 195 | "csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 196 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 197 | "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 198 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 199 | ] 200 | 201 | [[package]] 202 | name = "csv-core" 203 | version = "0.1.6" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | dependencies = [ 206 | "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 207 | ] 208 | 209 | [[package]] 210 | name = "deflate" 211 | version = "0.7.20" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | dependencies = [ 214 | "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 215 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 216 | ] 217 | 218 | [[package]] 219 | name = "either" 220 | version = "1.5.3" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | 223 | [[package]] 224 | name = "getrandom" 225 | version = "0.1.12" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | dependencies = [ 228 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 229 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 230 | "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 231 | ] 232 | 233 | [[package]] 234 | name = "gif" 235 | version = "0.10.3" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | dependencies = [ 238 | "color_quant 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 239 | "lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 240 | ] 241 | 242 | [[package]] 243 | name = "image" 244 | version = "0.22.3" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | dependencies = [ 247 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 248 | "gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)", 249 | "jpeg-decoder 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 250 | "num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 251 | "num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 252 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 253 | "png 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", 254 | "scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 255 | "tiff 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 256 | ] 257 | 258 | [[package]] 259 | name = "inflate" 260 | version = "0.4.5" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | dependencies = [ 263 | "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 264 | ] 265 | 266 | [[package]] 267 | name = "itertools" 268 | version = "0.8.2" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | dependencies = [ 271 | "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 272 | ] 273 | 274 | [[package]] 275 | name = "itoa" 276 | version = "0.4.4" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | 279 | [[package]] 280 | name = "jpeg-decoder" 281 | version = "0.1.16" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | dependencies = [ 284 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 285 | "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 286 | ] 287 | 288 | [[package]] 289 | name = "lazy_static" 290 | version = "1.4.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | 293 | [[package]] 294 | name = "libc" 295 | version = "0.2.65" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | 298 | [[package]] 299 | name = "lzw" 300 | version = "0.10.0" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | 303 | [[package]] 304 | name = "memchr" 305 | version = "2.2.1" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | dependencies = [ 308 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 309 | ] 310 | 311 | [[package]] 312 | name = "memoffset" 313 | version = "0.5.1" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | dependencies = [ 316 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 317 | ] 318 | 319 | [[package]] 320 | name = "nodrop" 321 | version = "0.1.14" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | 324 | [[package]] 325 | name = "num-derive" 326 | version = "0.2.5" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | dependencies = [ 329 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 330 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 331 | "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", 332 | ] 333 | 334 | [[package]] 335 | name = "num-integer" 336 | version = "0.1.41" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | dependencies = [ 339 | "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 340 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 341 | ] 342 | 343 | [[package]] 344 | name = "num-iter" 345 | version = "0.1.39" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | dependencies = [ 348 | "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 349 | "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 350 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 351 | ] 352 | 353 | [[package]] 354 | name = "num-rational" 355 | version = "0.2.2" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | dependencies = [ 358 | "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 359 | "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 360 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 361 | ] 362 | 363 | [[package]] 364 | name = "num-traits" 365 | version = "0.2.8" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | dependencies = [ 368 | "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 369 | ] 370 | 371 | [[package]] 372 | name = "num_cpus" 373 | version = "1.10.1" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | dependencies = [ 376 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 377 | ] 378 | 379 | [[package]] 380 | name = "png" 381 | version = "0.15.0" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | dependencies = [ 384 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 385 | "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 386 | "deflate 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)", 387 | "inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 388 | ] 389 | 390 | [[package]] 391 | name = "ppv-lite86" 392 | version = "0.2.5" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | 395 | [[package]] 396 | name = "proc-macro2" 397 | version = "0.4.30" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | dependencies = [ 400 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 401 | ] 402 | 403 | [[package]] 404 | name = "proc-macro2" 405 | version = "1.0.6" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | dependencies = [ 408 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 409 | ] 410 | 411 | [[package]] 412 | name = "quote" 413 | version = "0.6.13" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | dependencies = [ 416 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 417 | ] 418 | 419 | [[package]] 420 | name = "quote" 421 | version = "1.0.2" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | dependencies = [ 424 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 425 | ] 426 | 427 | [[package]] 428 | name = "rand" 429 | version = "0.7.2" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | dependencies = [ 432 | "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 433 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 434 | "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 435 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 436 | "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 437 | "rand_pcg 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 438 | ] 439 | 440 | [[package]] 441 | name = "rand_chacha" 442 | version = "0.2.1" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | dependencies = [ 445 | "c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 446 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 447 | ] 448 | 449 | [[package]] 450 | name = "rand_core" 451 | version = "0.5.1" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | dependencies = [ 454 | "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 455 | ] 456 | 457 | [[package]] 458 | name = "rand_hc" 459 | version = "0.2.0" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | dependencies = [ 462 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 463 | ] 464 | 465 | [[package]] 466 | name = "rand_os" 467 | version = "0.2.2" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | dependencies = [ 470 | "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 471 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 472 | ] 473 | 474 | [[package]] 475 | name = "rand_pcg" 476 | version = "0.2.0" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | dependencies = [ 479 | "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 480 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 481 | ] 482 | 483 | [[package]] 484 | name = "rand_xoshiro" 485 | version = "0.3.1" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | dependencies = [ 488 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 489 | ] 490 | 491 | [[package]] 492 | name = "rayon" 493 | version = "1.2.0" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | dependencies = [ 496 | "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 497 | "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 498 | "rayon-core 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 499 | ] 500 | 501 | [[package]] 502 | name = "rayon-core" 503 | version = "1.6.0" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | dependencies = [ 506 | "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 507 | "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 508 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 509 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 510 | "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 511 | ] 512 | 513 | [[package]] 514 | name = "raytracer" 515 | version = "0.1.0" 516 | dependencies = [ 517 | "criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 518 | "crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 519 | "image 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)", 520 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 521 | "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 522 | ] 523 | 524 | [[package]] 525 | name = "regex-automata" 526 | version = "0.1.8" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | dependencies = [ 529 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 530 | ] 531 | 532 | [[package]] 533 | name = "rustc_version" 534 | version = "0.2.3" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | dependencies = [ 537 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 538 | ] 539 | 540 | [[package]] 541 | name = "ryu" 542 | version = "1.0.2" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | 545 | [[package]] 546 | name = "same-file" 547 | version = "1.0.5" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | dependencies = [ 550 | "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 551 | ] 552 | 553 | [[package]] 554 | name = "scoped_threadpool" 555 | version = "0.1.9" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | 558 | [[package]] 559 | name = "scopeguard" 560 | version = "1.0.0" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | 563 | [[package]] 564 | name = "semver" 565 | version = "0.9.0" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | dependencies = [ 568 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 569 | ] 570 | 571 | [[package]] 572 | name = "semver-parser" 573 | version = "0.7.0" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | 576 | [[package]] 577 | name = "serde" 578 | version = "1.0.104" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | 581 | [[package]] 582 | name = "serde_derive" 583 | version = "1.0.104" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | dependencies = [ 586 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 587 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 588 | "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", 589 | ] 590 | 591 | [[package]] 592 | name = "serde_json" 593 | version = "1.0.44" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | dependencies = [ 596 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 597 | "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 598 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 599 | ] 600 | 601 | [[package]] 602 | name = "syn" 603 | version = "0.15.44" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | dependencies = [ 606 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 607 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 608 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 609 | ] 610 | 611 | [[package]] 612 | name = "syn" 613 | version = "1.0.11" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | dependencies = [ 616 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 617 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 618 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 619 | ] 620 | 621 | [[package]] 622 | name = "textwrap" 623 | version = "0.11.0" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | dependencies = [ 626 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 627 | ] 628 | 629 | [[package]] 630 | name = "tiff" 631 | version = "0.3.1" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | dependencies = [ 634 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 635 | "lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 636 | "num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 637 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 638 | ] 639 | 640 | [[package]] 641 | name = "tinytemplate" 642 | version = "1.0.3" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | dependencies = [ 645 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 646 | "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", 647 | ] 648 | 649 | [[package]] 650 | name = "unicode-width" 651 | version = "0.1.7" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | 654 | [[package]] 655 | name = "unicode-xid" 656 | version = "0.1.0" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | 659 | [[package]] 660 | name = "unicode-xid" 661 | version = "0.2.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | 664 | [[package]] 665 | name = "walkdir" 666 | version = "2.2.9" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | dependencies = [ 669 | "same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 670 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 671 | "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 672 | ] 673 | 674 | [[package]] 675 | name = "wasi" 676 | version = "0.7.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | 679 | [[package]] 680 | name = "winapi" 681 | version = "0.3.8" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | dependencies = [ 684 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 685 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 686 | ] 687 | 688 | [[package]] 689 | name = "winapi-i686-pc-windows-gnu" 690 | version = "0.4.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | 693 | [[package]] 694 | name = "winapi-util" 695 | version = "0.1.2" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | dependencies = [ 698 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 699 | ] 700 | 701 | [[package]] 702 | name = "winapi-x86_64-pc-windows-gnu" 703 | version = "0.4.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | 706 | [metadata] 707 | "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" 708 | "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" 709 | "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" 710 | "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" 711 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 712 | "checksum bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245" 713 | "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 714 | "checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" 715 | "checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" 716 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 717 | "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" 718 | "checksum color_quant 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" 719 | "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 720 | "checksum criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "938703e165481c8d612ea3479ac8342e5615185db37765162e762ec3523e2fc6" 721 | "checksum criterion-plot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eccdc6ce8bbe352ca89025bee672aa6d24f4eb8c53e3a8b5d1bc58011da072a2" 722 | "checksum crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2d818a4990769aac0c7ff1360e233ef3a41adcb009ebb2036bf6915eb0f6b23c" 723 | "checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa" 724 | "checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" 725 | "checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" 726 | "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" 727 | "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" 728 | "checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d" 729 | "checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c" 730 | "checksum deflate 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)" = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" 731 | "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 732 | "checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571" 733 | "checksum gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)" = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" 734 | "checksum image 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4be8aaefbe7545dc42ae925afb55a0098f226a3fe5ef721872806f44f57826" 735 | "checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" 736 | "checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" 737 | "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 738 | "checksum jpeg-decoder 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "c1aae18ffeeae409c6622c3b6a7ee49792a7e5a062eea1b135fbb74e301792ba" 739 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 740 | "checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" 741 | "checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" 742 | "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" 743 | "checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" 744 | "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 745 | "checksum num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2" 746 | "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" 747 | "checksum num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e" 748 | "checksum num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2885278d5fe2adc2f75ced642d52d879bffaceb5a2e0b1d4309ffdfb239b454" 749 | "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" 750 | "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" 751 | "checksum png 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8422b27bb2c013dd97b9aef69e161ce262236f49aaf46a0489011c8ff0264602" 752 | "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" 753 | "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 754 | "checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" 755 | "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 756 | "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 757 | "checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" 758 | "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" 759 | "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 760 | "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 761 | "checksum rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a788ae3edb696cfcba1c19bfd388cc4b8c21f8a408432b199c072825084da58a" 762 | "checksum rand_pcg 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e196346cbbc5c70c77e7b4926147ee8e383a38ee4d15d58a08098b169e492b6" 763 | "checksum rand_xoshiro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e18c91676f670f6f0312764c759405f13afb98d5d73819840cf72a518487bff" 764 | "checksum rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83a27732a533a1be0a0035a111fe76db89ad312f6f0347004c220c57f209a123" 765 | "checksum rayon-core 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98dcf634205083b17d0861252431eb2acbfb698ab7478a2d20de07954f47ec7b" 766 | "checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9" 767 | "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 768 | "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" 769 | "checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" 770 | "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 771 | "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" 772 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 773 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 774 | "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" 775 | "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" 776 | "checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" 777 | "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" 778 | "checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" 779 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 780 | "checksum tiff 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b7c2cfc4742bd8a32f2e614339dd8ce30dbcf676bb262bd63a2327bc5df57d" 781 | "checksum tinytemplate 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "57a3c6667d3e65eb1bc3aed6fd14011c6cbc3a0665218ab7f5daf040b9ec371a" 782 | "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" 783 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 784 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 785 | "checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e" 786 | "checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" 787 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 788 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 789 | "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" 790 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 791 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "raytracer" 3 | version = "0.1.0" 4 | authors = ["Brandon Smith "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "raytracer" 9 | path = "src/lib.rs" 10 | 11 | [[bin]] 12 | name = "raytracer" 13 | path = "src/bin/main.rs" 14 | 15 | [profile.dev] 16 | opt-level = 3 17 | 18 | [dependencies] 19 | image = "0.22.3" 20 | crossbeam = "0.7.2" 21 | lazy_static = "1.4.0" 22 | #flame = "0.2.2" 23 | #flamer = "0.4.0" 24 | 25 | [dependencies.rand] 26 | version = "0.7.2" 27 | features = ["small_rng"] 28 | 29 | [dev-dependencies] 30 | criterion = "0.3" 31 | 32 | [[bench]] 33 | name = "main" 34 | harness = false 35 | -------------------------------------------------------------------------------- /Geometric.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.81 (sub 16) OBJ File: 'Geometric.blend' 2 | # www.blender.org 3 | mtllib Geometric.mtl 4 | o Cube 5 | v 1.000000 1.000000 -1.000000 6 | v 1.000000 -1.000000 -1.000000 7 | v 1.000000 1.000000 1.000000 8 | v 1.000000 -1.000000 1.000000 9 | v 2.000000 0.000000 -1.000000 10 | v 2.000000 0.000000 1.000000 11 | v -1.000000 1.000000 0.000000 12 | v 0.000000 0.000000 0.000000 13 | v 2.000000 1.000000 0.000000 14 | v 0.000000 1.000000 -1.000000 15 | v 2.000000 -1.000000 0.000000 16 | v 2.000000 0.000000 0.000000 17 | v 0.000000 -1.000000 1.000000 18 | v 0.000000 1.000000 1.000000 19 | v 0.000000 0.000000 -1.000000 20 | v 0.000000 0.000000 2.000000 21 | v 0.000000 -1.000000 0.000000 22 | v 0.000000 1.000000 0.000000 23 | v 1.000000 0.000000 1.000000 24 | v 1.000000 1.000000 0.000000 25 | v 1.000000 0.000000 -1.000000 26 | v 1.000000 -1.000000 0.000000 27 | v -1.000000 0.000000 1.000000 28 | v 0.000000 1.500000 0.000000 29 | v 1.000000 1.500000 0.000000 30 | v 1.500000 -1.000000 0.500000 31 | v 0.500000 -1.000000 -0.500000 32 | v 1.500000 -1.500000 0.500000 33 | v 1.000000 -1.500000 0.000000 34 | v 0.500000 -1.500000 -0.500000 35 | vt 0.250000 0.625000 36 | vt 0.375000 0.750000 37 | vt 0.250000 0.750000 38 | vt 0.500000 0.625000 39 | vt 0.625000 0.625000 40 | vt 0.500000 0.750000 41 | vt 0.625000 0.375000 42 | vt 0.500000 0.437500 43 | vt 0.500000 0.375000 44 | vt 0.375000 0.375000 45 | vt 0.375000 0.625000 46 | vt 0.500000 0.875000 47 | vt 0.500000 0.625000 48 | vt 0.375000 0.875000 49 | vt 0.750000 0.625000 50 | vt 0.750000 0.625000 51 | vt 0.750000 0.750000 52 | vt 0.375000 0.500000 53 | vt 0.500000 0.500000 54 | vt 0.625000 0.500000 55 | vt 0.250000 0.562500 56 | vt 0.250000 0.562500 57 | vt 0.750000 0.500000 58 | vt 0.687500 0.625000 59 | vt 0.250000 0.500000 60 | vt 0.500000 0.812500 61 | vt 0.625000 0.750000 62 | vt 0.375000 0.687500 63 | vt 0.625000 0.875000 64 | vt 0.750000 0.625000 65 | vt 0.312500 0.625000 66 | vt 0.375000 0.687500 67 | vt 0.500000 0.875000 68 | vt 0.687500 0.625000 69 | vt 0.312500 0.625000 70 | vt 0.312500 0.562500 71 | vn 0.0000 -1.0000 0.0000 72 | vn 1.0000 0.0000 0.0000 73 | vn 0.0000 0.0000 -1.0000 74 | vn -0.5774 -0.5774 -0.5774 75 | vn 0.0000 1.0000 0.0000 76 | vn 0.5774 -0.5774 -0.5774 77 | vn 0.5774 0.5774 -0.5774 78 | vn -0.7071 0.0000 -0.7071 79 | vn -1.0000 0.0000 0.0000 80 | vn 0.0000 0.0000 1.0000 81 | vn -0.5774 -0.5774 0.5774 82 | vn 0.5774 0.5774 0.5774 83 | vn 0.5774 -0.5774 0.5774 84 | vn 0.7071 0.0000 0.7071 85 | vn -0.5774 0.5774 0.5774 86 | vn -0.5774 0.5774 -0.5774 87 | vn 0.0000 0.8944 -0.4472 88 | vn 0.0000 0.8944 0.4472 89 | vn 0.4082 -0.8165 -0.4082 90 | vn -0.4082 -0.8165 0.4082 91 | vn -0.8944 -0.4472 0.0000 92 | usemtl Material 93 | s off 94 | f 17/1/1 4/2/1 13/3/1 95 | f 12/4/2 9/5/2 6/6/2 96 | f 10/7/3 21/8/3 15/9/3 97 | f 2/10/3 15/9/3 21/8/3 98 | f 11/11/2 12/4/2 6/6/2 99 | f 23/12/4 8/13/4 13/14/4 100 | f 18/15/5 7/16/5 14/17/5 101 | f 2/18/6 5/19/6 11/11/6 102 | f 5/19/7 1/20/7 9/5/7 103 | f 17/1/8 27/21/8 30/22/8 104 | f 18/15/5 10/23/5 7/16/5 105 | f 8/13/9 10/7/9 15/9/9 106 | f 1/20/5 20/24/5 9/5/5 107 | f 15/9/4 2/25/4 27/21/4 108 | f 4/2/10 19/26/10 13/14/10 109 | f 10/7/4 8/13/4 7/16/4 110 | f 8/13/11 14/17/11 7/16/11 111 | f 6/6/10 3/27/10 19/26/10 112 | f 9/5/5 20/24/5 3/27/5 113 | f 6/6/12 9/5/12 3/27/12 114 | f 11/11/2 5/19/2 12/4/2 115 | f 9/5/2 12/4/2 5/19/2 116 | f 6/6/13 26/28/13 11/11/13 117 | f 19/26/10 4/2/10 6/6/10 118 | f 19/26/10 3/27/10 14/29/10 119 | f 18/15/9 14/17/9 24/30/9 120 | f 3/27/5 20/24/5 14/17/5 121 | f 2/18/3 21/8/3 5/19/3 122 | f 21/8/3 1/20/3 5/19/3 123 | f 22/31/1 2/18/1 11/11/1 124 | f 26/28/14 4/2/14 28/32/14 125 | f 13/14/9 8/13/9 17/1/9 126 | f 23/12/15 16/33/15 14/17/15 127 | f 13/14/11 16/33/11 23/12/11 128 | f 14/17/16 8/13/16 23/12/16 129 | f 1/20/8 18/15/8 24/30/8 130 | f 18/15/5 1/20/5 10/23/5 131 | f 25/34/17 1/20/17 24/30/17 132 | f 14/17/18 25/34/18 24/30/18 133 | f 14/17/14 20/24/14 25/34/14 134 | f 20/24/2 1/20/2 25/34/2 135 | f 2/18/19 29/35/19 30/36/19 136 | f 2/18/2 22/31/2 29/35/2 137 | f 22/31/10 17/1/10 29/35/10 138 | f 4/2/20 29/35/20 28/32/20 139 | f 30/36/20 29/35/20 17/1/20 140 | f 28/32/19 29/35/19 11/11/19 141 | f 4/2/9 22/31/9 29/35/9 142 | f 11/11/14 26/28/14 28/32/14 143 | f 27/21/8 2/25/8 30/22/8 144 | f 22/31/3 11/11/3 29/35/3 145 | f 14/29/12 16/33/12 19/26/12 146 | f 13/14/13 19/26/13 16/33/13 147 | f 17/1/1 22/31/1 4/2/1 148 | f 10/7/3 1/20/3 21/8/3 149 | f 27/21/8 17/1/8 8/13/8 150 | f 8/13/21 15/9/21 27/21/21 151 | f 6/6/13 4/2/13 26/28/13 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | An in-progress raytracer in Rust. Current output: 2 | 3 | ![Current output](https://github.com/brundonsmith/raytracer/raw/master/output.png) -------------------------------------------------------------------------------- /Room.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.81 (sub 16) OBJ File: 'Room.blend' 2 | # www.blender.org 3 | mtllib Room.mtl 4 | o Plane 5 | v -4.970529 0.000000 4.970529 6 | v 4.970529 0.000000 4.970529 7 | v -4.970529 0.000000 -4.970529 8 | v 4.970529 0.000000 -4.970529 9 | v -4.970529 -0.172907 4.339009 10 | v 4.970529 -0.172907 4.339009 11 | v -4.970529 4.970529 4.339009 12 | v 4.970529 4.970529 4.339009 13 | v -4.970529 -0.172907 -4.339009 14 | v 4.970529 -0.172907 -4.339009 15 | v -4.970529 4.970529 -4.339009 16 | v 4.970529 4.970529 -4.339009 17 | v -4.339009 -0.172906 -4.970528 18 | v -4.339008 -0.172906 4.970529 19 | v -4.339009 4.970529 -4.970528 20 | v -4.339008 4.970529 4.970529 21 | v 4.339009 -0.172906 -4.970528 22 | v 4.339008 -0.172906 4.970529 23 | v 4.339009 4.970529 -4.970528 24 | v 4.339008 4.970529 4.970529 25 | vt 1.000000 0.000000 26 | vt 0.000000 1.000000 27 | vt 0.000000 0.000000 28 | vt 1.000000 0.000000 29 | vt 0.000000 1.000000 30 | vt 0.000000 0.000000 31 | vt 1.000000 0.000000 32 | vt 0.000000 1.000000 33 | vt 0.000000 0.000000 34 | vt 1.000000 0.000000 35 | vt 0.000000 1.000000 36 | vt 0.000000 0.000000 37 | vt 1.000000 0.000000 38 | vt 0.000000 1.000000 39 | vt 0.000000 0.000000 40 | vt 1.000000 1.000000 41 | vt 1.000000 1.000000 42 | vt 1.000000 1.000000 43 | vt 1.000000 1.000000 44 | vt 1.000000 1.000000 45 | vn 0.0000 1.0000 0.0000 46 | vn 0.0000 0.0000 1.0000 47 | vn -1.0000 0.0000 0.0000 48 | usemtl None 49 | s off 50 | f 2/1/1 3/2/1 1/3/1 51 | f 6/4/2 7/5/2 5/6/2 52 | f 10/7/2 11/8/2 9/9/2 53 | f 14/10/3 15/11/3 13/12/3 54 | f 18/13/3 19/14/3 17/15/3 55 | f 2/1/1 4/16/1 3/2/1 56 | f 6/4/2 8/17/2 7/5/2 57 | f 10/7/2 12/18/2 11/8/2 58 | f 14/10/3 16/19/3 15/11/3 59 | f 18/13/3 20/20/3 19/14/3 60 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | - Work out remaining weirdness with normal maps (has to do with object orientation) 2 | - Get diffuse/specular interaction working properly 3 | - Mesh UV mapping 4 | - Denoising trick (increase roughness on each bounce) 5 | - Transparency 6 | - Atmospheric scattering 7 | - Bloom lighting 8 | -------------------------------------------------------------------------------- /benches/cast.rs: -------------------------------------------------------------------------------- 1 | 2 | use criterion::{black_box, Criterion}; 3 | 4 | extern crate rand; 5 | use rand::prelude::*; 6 | use rand::{thread_rng}; 7 | use rand::rngs::SmallRng; 8 | 9 | use raytracer::ray::Ray; 10 | use raytracer::vec3::Vec3; 11 | use raytracer::color::Color; 12 | use raytracer::material::Material; 13 | use raytracer::sphere::Sphere; 14 | use raytracer::mesh::{Mesh}; 15 | use raytracer::plane::Plane; 16 | use raytracer::texture::Texture; 17 | use raytracer::matrix::Matrix; 18 | use raytracer::object::{ObjectEnum}; 19 | 20 | const TEST_RAY_1: Ray = Ray { 21 | origin: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 22 | direction: Vec3 { x: -0.5, y: -0.5, z: -1.0 } 23 | }; 24 | 25 | pub fn cast_ray_1(c: &mut Criterion) { 26 | let mut meta_rng = thread_rng(); 27 | let mut rng = SmallRng::from_rng(&mut meta_rng).unwrap(); 28 | let objs = construct_room_scene(); 29 | 30 | c.bench_function("cast_ray_1([TEST_RAY_1], [objs], [rng], 3)", |b| 31 | b.iter(|| raytracer::cast::cast_ray(black_box(&TEST_RAY_1), &objs, &mut rng, black_box(3)))); 32 | } 33 | 34 | 35 | const TEST_RAY_2: Ray = Ray { 36 | origin: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 37 | direction: Vec3 { x: 0.0, y: -0.5, z: -1.0 } 38 | }; 39 | 40 | pub fn cast_ray_2(c: &mut Criterion) { 41 | let mut meta_rng = thread_rng(); 42 | let mut rng = SmallRng::from_rng(&mut meta_rng).unwrap(); 43 | let objs = construct_room_scene(); 44 | 45 | c.bench_function("cast_ray_2([TEST_RAY_2], [objs], [rng], 3)", |b| 46 | b.iter(|| raytracer::cast::cast_ray(black_box(&TEST_RAY_2), &objs, &mut rng, black_box(3)))); 47 | } 48 | 49 | 50 | const TEST_RAY_3: Ray = Ray { 51 | origin: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 52 | direction: Vec3 { x: -0.5, y: 0.0, z: -1.0 } 53 | }; 54 | 55 | pub fn cast_ray_3(c: &mut Criterion) { 56 | let mut meta_rng = thread_rng(); 57 | let mut rng = SmallRng::from_rng(&mut meta_rng).unwrap(); 58 | let objs = construct_room_scene(); 59 | 60 | c.bench_function("cast_ray_3([TEST_RAY_3], [objs], [rng], 3)", |b| 61 | b.iter(|| raytracer::cast::cast_ray(black_box(&TEST_RAY_3), &objs, &mut rng, black_box(3)))); 62 | } 63 | 64 | 65 | fn construct_room_scene() -> Vec { 66 | let mut objs: Vec = Vec::new(); 67 | 68 | // spheres 69 | objs.push(ObjectEnum::Sphere(Sphere::new( 70 | Vec3 { x: 3.0, y: -3.0, z: -13.0 }, 71 | 1.0, 72 | Material { 73 | texture_albedo: None, 74 | texture_specular: None, 75 | texture_normal: None, 76 | texture_emission_color: Some(Texture::Solid(Color(0.0, 1.0, 1.0))), 77 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))), 78 | } 79 | ))); 80 | 81 | objs.push(ObjectEnum::Sphere(Sphere::new( 82 | Vec3 { x: -2.0, y: 0.0, z: -8.0 }, 83 | 1.0, 84 | Material { 85 | texture_albedo: None, 86 | texture_specular: Some(Texture::Solid(Color(1.0,1.0,1.0))), 87 | texture_normal: None, 88 | texture_emission_color: None, 89 | texture_emission_intensity: None, 90 | } 91 | ))); 92 | 93 | // mesh 94 | objs.push(ObjectEnum::Mesh(Mesh::from_obj( 95 | "/Users/brundolf/git/raytracer/test.obj", 96 | &(Matrix::translation(&Vec3 { x: 0.0, y: -3.0, z: -10.0 }) 97 | * Matrix::rotation_y(std::f32::consts::PI) 98 | * Matrix::scale(&Vec3::from_scalar(0.5))), 99 | None 100 | ))); 101 | 102 | // ceiling 103 | objs.push(ObjectEnum::Plane(Plane::new( 104 | Vec3 { x: 0.0, y: 5.0, z: 0.0, }, 105 | Vec3 { x: 0.0, y: -1.0, z: 0.0 }, 106 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 107 | Material { 108 | texture_albedo: None, 109 | texture_specular: None, 110 | texture_normal: None, 111 | texture_emission_color: Some(Texture::Solid(Color(1.0, 0.95, 0.8))), 112 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))), 113 | } 114 | ))); 115 | 116 | // floor 117 | objs.push(ObjectEnum::Plane(Plane::new( 118 | Vec3 { x: 0.0, y: -5.0, z: 0.0, }, 119 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 120 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 121 | Material { 122 | texture_albedo: Some(Texture::from_image("/Users/brundolf/git/raytracer/texture.jpg")), 123 | texture_specular: None, 124 | texture_normal: None, 125 | texture_emission_color: None, 126 | texture_emission_intensity: None, 127 | } 128 | ))); 129 | 130 | 131 | // left wall 132 | objs.push(ObjectEnum::Plane(Plane::new( 133 | Vec3 { x: -5.0, y: 0.0, z: 0.0, }, 134 | Vec3 { x: 1.0, y: 0.0, z: 0.0 }, 135 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 136 | Material { 137 | texture_albedo: Some(Texture::Solid(Color(1.0,0.0,0.0))), 138 | texture_specular: None, 139 | texture_normal: None, 140 | texture_emission_color: None, 141 | texture_emission_intensity: None, 142 | } 143 | ))); 144 | 145 | // right wall 146 | objs.push(ObjectEnum::Plane(Plane::new( 147 | Vec3 { x: 5.0, y: 0.0, z: 0.0, }, 148 | Vec3 { x: -1.0, y: 0.0, z: 0.0 }, 149 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 150 | Material { 151 | texture_albedo: Some(Texture::Solid(Color(0.0, 1.0, 0.0))), 152 | texture_specular: None, 153 | texture_normal: None, 154 | texture_emission_color: None, 155 | texture_emission_intensity: None, 156 | } 157 | ))); 158 | 159 | // back wall 160 | objs.push(ObjectEnum::Plane(Plane::new( 161 | Vec3 { x: 0.0, y: 0.0, z: -15.0, }, 162 | Vec3 { x: 0.0, y: 0.0, z: 1.0 }, 163 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 164 | Material { 165 | texture_albedo: Some(Texture::Solid(Color(1.0,1.0,1.0))), 166 | texture_specular: None, 167 | texture_normal: None, 168 | texture_emission_color: None, 169 | texture_emission_intensity: None, 170 | } 171 | ))); 172 | 173 | // near wall 174 | objs.push(ObjectEnum::Plane(Plane::new( 175 | Vec3 { x: 0.0, y: 0.0, z: 1.0, }, 176 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 177 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 178 | Material { 179 | texture_albedo: Some(Texture::Solid(Color(0.0,0.0,1.0))), 180 | texture_specular: None, 181 | texture_normal: None, 182 | texture_emission_color: None, 183 | texture_emission_intensity: None, 184 | } 185 | ))); 186 | 187 | return objs; 188 | } -------------------------------------------------------------------------------- /benches/main.rs: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * NOTE: Always run with the following settings for consistent results: 4 | * 5 | * 6 | // Image resolution, in pixels. 7 | pub const RESOLUTION: usize = 256; 8 | 9 | // Number of sample rays to cast for diffuse/specular illumination 10 | pub const SAMPLE_COUNT: usize = 16; 11 | 12 | // Max number of indirect bounces to make 13 | pub const BOUNCES: u8 = 2; 14 | 15 | // How many chunks the image should be split up into, for multithreading 16 | pub const CELLS: usize = 64; // must be the square of an integer 17 | 18 | pub const PREVIEW_MODE: bool = false; 19 | */ 20 | 21 | use criterion::{criterion_group, criterion_main}; 22 | 23 | mod cast; 24 | criterion_group!(benches_cast, 25 | cast::cast_ray_1, 26 | cast::cast_ray_2, 27 | cast::cast_ray_3); 28 | 29 | mod ray; 30 | criterion_group!(benches_ray, 31 | ray::random_direction); 32 | 33 | mod vec3; 34 | criterion_group!(benches_vec3, 35 | vec3::angles, 36 | vec3::normalized, 37 | vec3::angle, 38 | vec3::projected_on, 39 | vec3::rotated_around); 40 | 41 | criterion_main!(benches_cast, benches_ray, benches_vec3); 42 | -------------------------------------------------------------------------------- /benches/ray.rs: -------------------------------------------------------------------------------- 1 | 2 | use criterion::{black_box, Criterion}; 3 | 4 | extern crate rand; 5 | use rand::prelude::*; 6 | use rand::{thread_rng}; 7 | use rand::rngs::SmallRng; 8 | 9 | use raytracer::ray::Ray; 10 | use raytracer::vec3::Vec3; 11 | 12 | const ORIGIN: Vec3 = Vec3 { x: 1.0, y: 1.0, z: 1.0 }; 13 | 14 | pub fn random_direction(c: &mut Criterion) { 15 | let mut meta_rng = thread_rng(); 16 | let mut rng = SmallRng::from_rng(&mut meta_rng).unwrap(); 17 | 18 | c.bench_function("random_direction(Vec3 { x: 1.0, y: 1.0, z: 1.0 }, [rng])", |b| 19 | b.iter(|| Ray::random_direction(black_box(ORIGIN), &mut rng))); 20 | } -------------------------------------------------------------------------------- /benches/vec3.rs: -------------------------------------------------------------------------------- 1 | 2 | use criterion::{black_box, Criterion}; 3 | 4 | use raytracer::vec3::Vec3; 5 | 6 | const VEC_BASIC: Vec3 = Vec3 { x: 1.0, y: 1.0, z: 1.0 }; 7 | const VEC_ASKEW: Vec3 = Vec3 { x: 0.5, y: 0.8, z: 1.5 }; 8 | 9 | pub fn angles(c: &mut Criterion) { 10 | c.bench_function("Vec3 { x: 1.0, y: 1.0, z: 1.0 }.angles()", |b| 11 | b.iter(|| black_box(black_box(VEC_BASIC).angles()))); 12 | } 13 | 14 | pub fn normalized(c: &mut Criterion) { 15 | c.bench_function("Vec3 { x: 1.0, y: 1.0, z: 1.0 }.normalized()", |b| 16 | b.iter(|| black_box(black_box(VEC_BASIC).normalized()))); 17 | } 18 | 19 | pub fn angle(c: &mut Criterion) { 20 | c.bench_function("Vec3 { x: 1.0, y: 1.0, z: 1.0 }.angle()", |b| 21 | b.iter(|| black_box(black_box(VEC_BASIC).angle(black_box(&VEC_ASKEW))))); 22 | } 23 | 24 | pub fn projected_on(c: &mut Criterion) { 25 | c.bench_function("Vec3 { x: 1.0, y: 1.0, z: 1.0 }.projected_on(Vec3 { x: 0.5, y: 0.8, z: 1.5 })", |b| 26 | b.iter(|| black_box(black_box(VEC_BASIC).projected_on(black_box(&VEC_ASKEW))))); 27 | } 28 | 29 | pub fn rotated_around(c: &mut Criterion) { 30 | c.bench_function("Vec3 { x: 1.0, y: 1.0, z: 1.0 }.rotated_around(Vec3 { x: 0.5, y: 0.8, z: 1.5 }, 25.3)", |b| 31 | b.iter(|| black_box(black_box(VEC_BASIC).rotated_around(black_box(&VEC_ASKEW), black_box(25.3))))); 32 | } -------------------------------------------------------------------------------- /cobblestone_normal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brundonsmith/raytracer/63e1cd2d7636c5e7c95f65817aa7325748a23405/cobblestone_normal.jpg -------------------------------------------------------------------------------- /grid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brundonsmith/raytracer/63e1cd2d7636c5e7c95f65817aa7325748a23405/grid.jpg -------------------------------------------------------------------------------- /output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brundonsmith/raytracer/63e1cd2d7636c5e7c95f65817aa7325748a23405/output.png -------------------------------------------------------------------------------- /specular.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brundonsmith/raytracer/63e1cd2d7636c5e7c95f65817aa7325748a23405/specular.jpeg -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_doc_comments)] 2 | 3 | use std::fs::File; 4 | 5 | extern crate rand; 6 | use rand::prelude::*; 7 | use rand::{thread_rng}; 8 | use rand::rngs::SmallRng; 9 | extern crate image; 10 | use image::{ImageBuffer, Rgb}; 11 | extern crate crossbeam; 12 | //extern crate flame; 13 | #[macro_use] 14 | //extern crate flamer; 15 | 16 | use std::sync::{Arc, Mutex}; 17 | use std::io::Write; 18 | use std::time::{Instant}; 19 | 20 | use raytracer::fidelity_consts::{RESOLUTION_X,RESOLUTION_Y,BOUNCES,THREADS,TOTAL_BUFFER_SIZE,PIXELS_PER_THREAD}; 21 | use raytracer::frame::Frame; 22 | use raytracer::utils::clamp; 23 | use raytracer::scenes::{ 24 | construct_reflect_scene, 25 | construct_room_scene, 26 | construct_plane_texture_test, 27 | construct_sphere_texture_test, 28 | construct_wallpaper_scene, 29 | construct_wallpaper_scene_2, 30 | construct_tree_scene}; 31 | use raytracer::cast::cast_ray; 32 | use raytracer::color::Color; 33 | use raytracer::object::ObjectEnum; 34 | 35 | 36 | fn main() { 37 | let ray_frame = ray_trace(); 38 | write_image(&ray_frame); 39 | } 40 | 41 | // Do the thing! 42 | fn ray_trace<'a>() -> Frame { 43 | 44 | println!("Tracing scene..."); 45 | 46 | let start_time = Instant::now(); 47 | 48 | // Create list of objects 49 | let objs: Vec = construct_room_scene(); 50 | 51 | // Create frame 52 | let mut frame = Frame::new(); 53 | let mut threads_done = 0; 54 | 55 | // Create thread wrappers 56 | let frame_mutex_arc: Arc> = Arc::new(Mutex::new(&mut frame)); 57 | let objs_arc: Arc<&Vec> = Arc::new(&objs); 58 | let threads_done_mutex_arc = Arc::new(Mutex::new(&mut threads_done)); 59 | 60 | // ray_trace_segment(&mut frame, &objs, 0, 0, RESOLUTION, RESOLUTION); 61 | 62 | crossbeam::scope(move |scope| { 63 | print!("0.00%"); 64 | std::io::stdout().flush().ok().expect(""); 65 | 66 | let mut meta_rng = thread_rng(); 67 | 68 | for thread in 0..THREADS { 69 | let start_index = thread * PIXELS_PER_THREAD; 70 | let objs_arc_clone = objs_arc.clone(); 71 | let frame_mutex_arc_clone = frame_mutex_arc.clone(); 72 | let threads_done_mutex_arc_clone = threads_done_mutex_arc.clone(); 73 | let rng = SmallRng::from_rng(&mut meta_rng).unwrap(); 74 | 75 | scope.spawn(move |_| { 76 | ray_trace_segment( 77 | frame_mutex_arc_clone, 78 | objs_arc_clone, 79 | rng, 80 | start_index, 81 | usize::min(start_index + PIXELS_PER_THREAD, TOTAL_BUFFER_SIZE) 82 | ); 83 | 84 | let mut threads_done = threads_done_mutex_arc_clone.lock().unwrap(); 85 | **threads_done = (**threads_done) + 1; 86 | 87 | print!("\r{}% ", format!("{:.*}", 2, (**threads_done as f32 / THREADS as f32) * 100.0)); 88 | std::io::stdout().flush().ok().expect(""); 89 | }); 90 | } 91 | }).unwrap(); 92 | 93 | println!("Total time: {}s", Instant::now().duration_since(start_time).as_millis() as f32 / 1000.0); 94 | println!("done"); 95 | 96 | return frame; 97 | } 98 | 99 | /** 100 | * Raytrace one square sub-portion of the image (exists to facilitate threading) 101 | */ 102 | fn ray_trace_segment(frame_mutex: Arc>, objs: Arc<&Vec>, mut rng: SmallRng, start: usize, end: usize) { 103 | let mut buffer = [Color(0.0,0.0,0.0); PIXELS_PER_THREAD]; 104 | let range = end - start; 105 | 106 | // Cast ray from each pixel 107 | for i in 0..range { 108 | let xy = Frame::pos_from_index(i + start); 109 | let ray = Frame::pixel_to_ray(&xy); 110 | 111 | let illumination = cast_ray(&ray, &objs, &mut rng, BOUNCES); 112 | 113 | buffer[i] = illumination.color * clamp(illumination.intensity, 0.0, 1.0); 114 | } 115 | 116 | let mut frame = frame_mutex.lock().unwrap(); 117 | for i in 0..range { 118 | frame.buffer[i + start] = buffer[i]; 119 | } 120 | std::mem::drop(frame); 121 | 122 | if start == 0 { 123 | // flame::dump_html(&mut File::create("target/flame-graph.html").unwrap()).unwrap(); 124 | } 125 | } 126 | 127 | /** 128 | * Write a frame to a PNG file 129 | */ 130 | fn write_image(ray_frame: &Frame) { 131 | println!("Writing to png..."); 132 | 133 | let mut image: ImageBuffer::,Vec> = ImageBuffer::new(RESOLUTION_X as u32, RESOLUTION_Y as u32); 134 | 135 | for x in 0..RESOLUTION_X { 136 | for y in 0..RESOLUTION_Y { 137 | let color = ray_frame.get(x, y); 138 | image.get_pixel_mut(x as u32, y as u32).0 = color.to_u8(); 139 | } 140 | } 141 | 142 | image.save("output.png").unwrap(); 143 | 144 | println!("done"); 145 | } 146 | -------------------------------------------------------------------------------- /src/cast.rs: -------------------------------------------------------------------------------- 1 | 2 | use rand::rngs::SmallRng; 3 | //use flamer::flame; 4 | 5 | use crate::ray::Ray; 6 | use crate::intersection::Intersection; 7 | use crate::illumination::{Illumination}; 8 | use crate::color::Color; 9 | use crate::object::{Object,ObjectEnum}; 10 | 11 | // misc 12 | const BACKGROUND_ILLUMINATION: Illumination = Illumination { color: Color(0.0, 0.0, 0.0), intensity: 0.0 }; 13 | //const GLOBAL_LIGHT_DIRECTION: Vec3 = Vec3{ x: 1.0, y: 1.0, z: -1.0 }; 14 | 15 | 16 | /** 17 | * Cast a single ray, from a pixel or from a bounce 18 | */ 19 | //#[flame] 20 | pub fn cast_ray(ray: &Ray, objs: &Vec, rng: &mut SmallRng, bounces_remaining: u8) -> Illumination { 21 | let mut nearest_intersection: Option = None; 22 | let mut nearest_object: Option<&ObjectEnum> = None; 23 | 24 | 25 | // Find nearest object intersection 26 | for index in 0..objs.len() { 27 | if let Some(intersection) = objs[index].intersection(&ray) { 28 | if intersection.distance < nearest_intersection.as_ref().map(|int| int.distance).unwrap_or(std::f32::INFINITY) { 29 | nearest_intersection = Some(intersection); 30 | nearest_object = Some(&objs[index]); 31 | } 32 | } 33 | } 34 | 35 | // Compute total illumination at this intersection 36 | let nearest_illumination: Illumination = nearest_object 37 | .map(|obj| obj.shade(ray, objs, rng, bounces_remaining)) 38 | .unwrap_or(BACKGROUND_ILLUMINATION); 39 | 40 | return nearest_illumination; 41 | } 42 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::utils::{avg,clamp}; 3 | 4 | #[derive(Debug, Copy, Clone, PartialEq)] 5 | pub struct Color(pub f32, pub f32, pub f32); 6 | 7 | impl Color { 8 | 9 | pub fn gray(value: f32) -> Self { 10 | Self(value, value, value) 11 | } 12 | 13 | pub fn to_u8(&self) -> [u8;3] { 14 | [ 15 | clamp(self.0 * 255.0, 0.0, 255.0) as u8, 16 | clamp(self.1 * 255.0, 0.0, 255.0) as u8, 17 | clamp(self.2 * 255.0, 0.0, 255.0) as u8 18 | ] 19 | } 20 | } 21 | 22 | impl std::ops::Add for Color { 23 | type Output = Color; 24 | 25 | fn add(self, other: Color) -> Self::Output { 26 | Color ( 27 | self.0 + other.0, 28 | self.1 + other.1, 29 | self.2 + other.2, 30 | ) 31 | } 32 | } 33 | 34 | impl std::ops::Mul for Color { 35 | type Output = Color; 36 | 37 | fn mul(self, other: Color) -> Self::Output { 38 | Color ( 39 | avg(self.0, other.0), 40 | avg(self.1, other.1), 41 | avg(self.2, other.2), 42 | ) 43 | } 44 | } 45 | 46 | impl std::ops::Mul for Color { 47 | type Output = Color; 48 | 49 | fn mul(self, scale: f32) -> Self::Output { 50 | Color ( 51 | self.0 * scale, 52 | self.1 * scale, 53 | self.2 * scale, 54 | ) 55 | } 56 | } 57 | 58 | impl std::ops::Div for Color { 59 | type Output = Color; 60 | 61 | fn div(self, divisor: usize) -> Self::Output { 62 | Color ( 63 | self.0 / divisor as f32, 64 | self.1 / divisor as f32, 65 | self.2 / divisor as f32, 66 | ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/fidelity_consts.rs: -------------------------------------------------------------------------------- 1 | 2 | // Image resolution, in pixels. 3 | // 3072 x 1920; 16:10 4 | pub const ASPECT_RATIO: f32 = 16.0 / 10.0; 5 | pub const RESOLUTION_Y: usize = 64; // 1920 6 | pub const RESOLUTION_X: usize = 1 + (RESOLUTION_Y as f32 * ASPECT_RATIO) as usize; 7 | 8 | // Number of sample rays to cast for diffuse/specular illumination 9 | pub const SAMPLE_COUNT: usize = 32; 10 | 11 | // Max number of indirect bounces to make 12 | pub const BOUNCES: u8 = 2; 13 | 14 | // How many chunks the image should be split up into, for multithreading 15 | pub const THREADS: usize = 256; 16 | 17 | pub const PREVIEW_MODE: bool = false; 18 | 19 | 20 | // derived for utility 21 | pub const TOTAL_BUFFER_SIZE: usize = RESOLUTION_X * RESOLUTION_Y; 22 | pub const PIXELS_PER_THREAD: usize = TOTAL_BUFFER_SIZE / THREADS; -------------------------------------------------------------------------------- /src/frame.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::color::Color; 3 | use crate::vec3::Vec3; 4 | use crate::matrix::{Matrix,IDENTITY}; 5 | use crate::ray::Ray; 6 | use crate::fidelity_consts::{RESOLUTION_X,RESOLUTION_Y,TOTAL_BUFFER_SIZE}; 7 | 8 | 9 | const CAMERA_POSITION: Vec3 = Vec3 { 10 | x: 0.0, 11 | y: 0.0, 12 | z: 0.0 13 | }; 14 | 15 | // construct within a function so as to further scope these constants 16 | fn construct_camera_matrix() -> Matrix { 17 | let camera_height: f32 = 2.0; 18 | let camera_width: f32 = camera_height * 1.6; 19 | let focal_length: f32 = 2.0; 20 | 21 | return IDENTITY 22 | //* Matrix::rotation_x(-1.0 / 4.0 * std::f32::consts::PI) 23 | * Matrix::translation(&Vec3 { 24 | x: -1.0 * camera_width / 2.0 + CAMERA_POSITION.x, 25 | y: camera_height / 2.0 + CAMERA_POSITION.y, 26 | z: -1.0 * focal_length + CAMERA_POSITION.z 27 | }) 28 | * Matrix::scale(&Vec3 { 29 | x: camera_width / RESOLUTION_X as f32, 30 | y: -1.0 * camera_height / RESOLUTION_Y as f32, 31 | z: 1.0 32 | }); 33 | } 34 | 35 | // HACK: .cos() isn't a const fn, for some reason, so lazy_static is 36 | // required to initialize this matrix 37 | lazy_static! { 38 | static ref CAMERA_MARTIX: Matrix = construct_camera_matrix(); 39 | } 40 | 41 | pub struct Frame { 42 | pub buffer: Box<[Color]>, 43 | } 44 | 45 | impl Frame { 46 | 47 | pub fn new() -> Self { 48 | Frame { 49 | buffer: vec![Color(0.0,0.0,0.0); TOTAL_BUFFER_SIZE].into_boxed_slice(), 50 | } 51 | } 52 | 53 | pub fn set(&mut self, x: usize, y: usize, color: Color) { 54 | self.buffer[Frame::index(x,y)] = color; 55 | } 56 | 57 | pub fn get(&self, x: usize, y: usize) -> Color { 58 | self.buffer[Frame::index(x,y)] 59 | } 60 | 61 | fn index(x: usize, y: usize) -> usize { 62 | x + y * RESOLUTION_X 63 | } 64 | 65 | pub fn pos_from_index(index: usize) -> (usize,usize) { 66 | (index % RESOLUTION_X, index / RESOLUTION_X) 67 | } 68 | 69 | /** 70 | * Find the world position of a pixel in this frame. 71 | */ 72 | fn pixel_to_world(pixel: &(usize,usize)) -> Vec3 { 73 | Vec3 { 74 | x: pixel.0 as f32, 75 | y: pixel.1 as f32, 76 | z: 0.0 77 | }.transformed(&CAMERA_MARTIX) 78 | } 79 | 80 | /** 81 | * Initialize a ray projecting out from one pixel in this frame 82 | * (incorporating not only origin position, but direction from 83 | * the focal point). 84 | */ 85 | pub fn pixel_to_ray(pixel: &(usize,usize)) -> Ray { 86 | let pixel_position = Frame::pixel_to_world(&pixel); 87 | let mut direction = &pixel_position - &CAMERA_POSITION; 88 | direction.normalize(); 89 | 90 | Ray { 91 | origin: pixel_position, 92 | direction: direction 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /src/illumination.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::color::Color; 3 | use crate::fidelity_consts::{SAMPLE_COUNT}; 4 | use crate::utils::lerp; 5 | 6 | #[derive(Debug, Copy, Clone, PartialEq)] 7 | pub struct Illumination { 8 | pub color: Color, 9 | pub intensity: f32, 10 | } 11 | 12 | impl Illumination { 13 | pub fn new() -> Self { 14 | Illumination { 15 | color: Color(0.0, 0.0, 0.0), 16 | intensity: 0.0, 17 | } 18 | } 19 | pub fn combined(a: &Illumination, b: &Illumination) -> Self { 20 | let total_intensity = a.intensity + b.intensity; 21 | 22 | let t = a.intensity / total_intensity; 23 | 24 | Illumination { 25 | //(a.color * (a.intensity / total_intensity)) + (b.color * (b.intensity / total_intensity)) 26 | color: Color( 27 | lerp(a.color.0, b.color.0, t), 28 | lerp(a.color.1, b.color.1, t), 29 | lerp(a.color.2, b.color.2, t), 30 | ), 31 | intensity: total_intensity 32 | } 33 | } 34 | } 35 | 36 | //pub fn integrate<'a,I: Iterator>(samples: I) -> Illumination { 37 | pub fn integrate(samples: &[Illumination;SAMPLE_COUNT]) -> Illumination { 38 | let mut lum = Illumination::new(); 39 | 40 | // HACK: Do a true weighted-average on colors eventually (scale by brightness) 41 | let mut samples_with_illum = 0.0; 42 | 43 | for index in 0..SAMPLE_COUNT { 44 | let sample = samples[index]; 45 | 46 | //if sample.intensity > 0.001 { 47 | lum.color.0 += sample.color.0; 48 | lum.color.1 += sample.color.1; 49 | lum.color.2 += sample.color.2; 50 | 51 | samples_with_illum += 1.0; 52 | //} 53 | 54 | lum.intensity += sample.intensity; 55 | } 56 | 57 | lum.color.0 /= samples_with_illum; 58 | lum.color.1 /= samples_with_illum; 59 | lum.color.2 /= samples_with_illum; 60 | 61 | lum.intensity /= SAMPLE_COUNT as f32; 62 | 63 | return lum; 64 | } -------------------------------------------------------------------------------- /src/intersection.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::utils::clamp; 3 | use crate::vec3::Vec3; 4 | use crate::utils::{PI_OVER_TWO}; 5 | 6 | pub struct Intersection { 7 | pub distance: f32, 8 | pub position: Vec3, 9 | pub normal: Vec3, 10 | pub direction: Vec3, 11 | reflected_direction: Option, 12 | } 13 | 14 | impl Intersection { 15 | 16 | pub fn new(distance: f32, position: Vec3, normal: Vec3, direction: Vec3) -> Self { 17 | Self { 18 | distance, 19 | position, 20 | normal, 21 | direction, 22 | reflected_direction: None, 23 | } 24 | } 25 | 26 | /** 27 | * The angle between the intersecting ray and the normal at that point 28 | */ 29 | pub fn incident_angle(&self) -> f32 { 30 | (&self.direction * -1.0).angle(&self.normal) 31 | } 32 | 33 | /** 34 | * The "directness" of the incident, between 0 and 1 35 | */ 36 | pub fn incident_amount(&self) -> f32 { 37 | clamp(self.incident_angle() / PI_OVER_TWO, 0.0, 1.0) 38 | } 39 | 40 | pub fn reflected_direction(&mut self) -> &Vec3 { 41 | if self.reflected_direction.is_none() { 42 | //R=2(N⋅L)N−L 43 | self.reflected_direction = Some(&self.direction - &(&self.normal * (2.0 * &(&self.normal * &self.direction)))); 44 | } 45 | 46 | return self.reflected_direction.as_ref().unwrap(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | #[macro_use] 3 | extern crate lazy_static; 4 | 5 | pub mod color; 6 | pub mod fidelity_consts; 7 | pub mod frame; 8 | pub mod illumination; 9 | pub mod intersection; 10 | pub mod material; 11 | pub mod matrix; 12 | pub mod mtl_parser; 13 | pub mod object; 14 | pub mod plane; 15 | pub mod ray; 16 | pub mod scenes; 17 | pub mod sphere; 18 | pub mod texture; 19 | pub mod utils; 20 | pub mod vec3; 21 | pub mod mesh; 22 | pub mod obj_parser; 23 | pub mod cast; -------------------------------------------------------------------------------- /src/material.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | 3 | use rand::rngs::SmallRng; 4 | //use flamer::flame; 5 | 6 | use crate::{illumination::{Illumination,integrate}}; 7 | use crate::texture::Texture; 8 | use crate::color::Color; 9 | use crate::vec3::Vec3; 10 | use crate::intersection::Intersection; 11 | use crate::cast::{cast_ray}; 12 | use crate::fidelity_consts::{SAMPLE_COUNT,PREVIEW_MODE}; 13 | use crate::ray::Ray; 14 | use crate::utils::{PI_OVER_TWO}; 15 | use crate::object::{ObjectEnum}; 16 | 17 | 18 | const BACKGROUND_ILLUMINATION: Illumination = Illumination { color: Color(0.0, 0.0, 0.0), intensity: 0.0 }; 19 | 20 | pub struct Material { 21 | pub texture_albedo: Option, 22 | pub texture_specular: Option, 23 | pub texture_normal: Option, 24 | pub texture_emission_color: Option, 25 | pub texture_emission_intensity: Option, 26 | } 27 | 28 | const PREVIEW_DIRECTION: Vec3 = Vec3 { x: 1.0, y: 1.0, z: 1.0 }; 29 | 30 | impl Material { 31 | 32 | pub fn new() -> Self { 33 | Self { 34 | texture_albedo: None, 35 | texture_specular: None, 36 | texture_normal: None, 37 | texture_emission_color: None, 38 | texture_emission_intensity: None, 39 | } 40 | } 41 | 42 | // #[flame("Material")] 43 | pub fn shade(&self, intersection: &mut Intersection, uv: (f32,f32), objs: &Vec, rng: &mut SmallRng, bounces_remaining: u8) -> Illumination { 44 | match &self.texture_emission_intensity { 45 | Some(tex) => Illumination { 46 | color: self.texture_emission_color.as_ref().map(|col| col.color_at(uv)) 47 | .unwrap_or(Color(1.0, 1.0, 1.0)), 48 | intensity: tex.color_at(uv).0 49 | }, 50 | None => { 51 | if bounces_remaining == 0 { 52 | BACKGROUND_ILLUMINATION 53 | } else if PREVIEW_MODE { 54 | let base_color = self.texture_albedo.as_ref().map(|texture| texture.color_at(uv)).unwrap_or(Color(1.0, 1.0, 1.0)); 55 | let adjustment = 1.0 - (intersection.normal.angle(&PREVIEW_DIRECTION) / PI); 56 | Illumination { 57 | color: base_color * adjustment, 58 | intensity: 1.0 59 | } 60 | } else { 61 | let diffuse_illumination: Option = self.texture_albedo.as_ref().map(|texture| { 62 | let surface_color = texture.color_at(uv); 63 | let sample_rays = get_sample_rays(intersection.position, &intersection.normal, rng, PI_OVER_TWO); 64 | 65 | let mut samples = [Illumination::new();SAMPLE_COUNT]; 66 | for i in 0..SAMPLE_COUNT { 67 | samples[i] = cast_ray(&sample_rays[i], objs, rng, bounces_remaining - 1); 68 | } 69 | 70 | let illumination = integrate(&samples); 71 | 72 | Illumination { 73 | color: surface_color * illumination.color, 74 | intensity: illumination.intensity 75 | } 76 | }); 77 | 78 | let specular_illumination: Option = self.texture_specular.as_ref().map(|texture| { 79 | let specularity = texture.color_at(uv).0; 80 | let reflected = *intersection.reflected_direction(); 81 | 82 | if specularity > 0.99 { 83 | // if reflection is nearly perfect, just cast a single sample ray to avoid work 84 | cast_ray(&Ray { 85 | origin: intersection.position, 86 | direction: reflected 87 | }, objs, rng, bounces_remaining - 1) 88 | } else { 89 | let sample_rays = get_sample_rays(intersection.position, &reflected, rng, (1.0 - specularity) * PI_OVER_TWO); 90 | 91 | let mut samples = [Illumination::new();SAMPLE_COUNT]; 92 | for i in 0..SAMPLE_COUNT { 93 | samples[i] = cast_ray(&sample_rays[i], objs, rng, bounces_remaining - 1); 94 | } 95 | 96 | integrate(&samples) 97 | } 98 | }); 99 | 100 | match diffuse_illumination { 101 | Some(diffuse) => { 102 | match specular_illumination { 103 | Some(specular_illumination) => 104 | Illumination::combined(&diffuse, &specular_illumination), 105 | None => diffuse 106 | } 107 | }, 108 | None => { 109 | match specular_illumination { 110 | Some(specular_illumination) => specular_illumination, 111 | None => BACKGROUND_ILLUMINATION 112 | } 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | fn get_sample_rays(position: Vec3, direction: &Vec3, rng: &mut SmallRng, range: f32) -> [Ray;SAMPLE_COUNT] { 122 | let mut rays = [Ray::new();SAMPLE_COUNT]; 123 | 124 | let mut i = 0; 125 | while i < SAMPLE_COUNT { 126 | let ray = Ray::random_direction(position, rng); 127 | 128 | // HACK: Figure out a way to *generate* rays that are already within our desired area 129 | if ray.direction.angle(direction) < range { 130 | rays[i] = ray; 131 | i += 1; 132 | } 133 | } 134 | 135 | return rays; 136 | } 137 | -------------------------------------------------------------------------------- /src/matrix.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::vec3::Vec3; 3 | 4 | const SIZE: usize = 4; 5 | const BUFFER_SIZE: usize = SIZE * SIZE; 6 | pub const IDENTITY: Matrix = Matrix([ 7 | 1.0, 0.0, 0.0, 0.0, 8 | 0.0, 1.0, 0.0, 0.0, 9 | 0.0, 0.0, 1.0, 0.0, 10 | 0.0, 0.0, 0.0, 1.0 11 | ]); 12 | 13 | 14 | 15 | #[derive(Copy, Clone, PartialEq)] 16 | pub struct Matrix( [f32; BUFFER_SIZE] ); 17 | 18 | impl Matrix { 19 | 20 | pub fn new() -> Self { 21 | IDENTITY.clone() 22 | } 23 | 24 | pub fn translation(offset: &Vec3) -> Self { 25 | Self([ 26 | 1.0, 0.0, 0.0, offset.x, 27 | 0.0, 1.0, 0.0, offset.y, 28 | 0.0, 0.0, 1.0, offset.z, 29 | 0.0, 0.0, 0.0, 1.0 30 | ]) 31 | } 32 | 33 | pub fn rotation_x(theta: f32) -> Self { 34 | Self([ 35 | 1.0, 0.0, 0.0, 0.0, 36 | 0.0, theta.cos(), -1.0 * theta.sin(), 0.0, 37 | 0.0, theta.sin(), theta.cos(), 0.0, 38 | 0.0, 0.0, 0.0, 1.0 39 | ]) 40 | } 41 | 42 | pub fn rotation_y(theta: f32) -> Self { 43 | Self([ 44 | theta.cos(), 0.0, theta.sin(), 0.0, 45 | 0.0, 1.0, 0.0, 0.0, 46 | -1.0 * theta.sin(), 0.0, theta.cos(), 0.0, 47 | 0.0, 0.0, 0.0, 1.0 48 | ]) 49 | } 50 | 51 | pub fn rotation_z(theta: f32) -> Self { 52 | Self([ 53 | theta.cos(), -1.0 * theta.sin(), 0.0, 0.0, 54 | theta.sin(), theta.cos(), 0.0, 0.0, 55 | 0.0, 0.0, 1.0, 0.0, 56 | 0.0, 0.0, 0.0, 1.0 57 | ]) 58 | } 59 | 60 | pub fn scale(scale: &Vec3) -> Self { 61 | Self([ 62 | scale.x, 0.0, 0.0, 0.0, 63 | 0.0, scale.y, 0.0, 0.0, 64 | 0.0, 0.0, scale.z, 0.0, 65 | 0.0, 0.0, 0.0, 1.0 66 | ]) 67 | } 68 | 69 | pub fn from_to_rotation(from: &Vec3, to: &Vec3) -> Self { 70 | let mut w = from.cross(&to); 71 | w.normalize(); 72 | 73 | let k = Self([ 74 | 0.0, -1.0 * w.z, w.y, 0.0, 75 | w.z, 0.0, -1.0 * w.x, 0.0, 76 | -1.0 * w.y, w.x, 0.0, 0.0, 77 | 0.0, 0.0, 0.0, 1.0 78 | ]); 79 | 80 | let theta = from.angle(&to); 81 | 82 | 83 | return &(&IDENTITY + &(&k * theta.sin())) + &(&(&k * &k) * (1.0 - theta.cos())); 84 | } 85 | 86 | // core ops 87 | fn index_for(&self, row: usize, col: usize) -> usize { 88 | row * SIZE + col 89 | } 90 | 91 | pub fn get(&self, row: usize, col: usize) -> f32 { 92 | self.0[self.index_for(row, col)] 93 | } 94 | 95 | pub fn get_mut(&mut self, row: usize, col: usize) -> &mut f32 { 96 | &mut self.0[self.index_for(row, col)] 97 | } 98 | 99 | pub fn set(&mut self, row: usize, col: usize, val: f32) { 100 | self.0[self.index_for(row, col)] = val; 101 | } 102 | 103 | // other ops 104 | pub fn invert(&mut self) { 105 | unimplemented!(); 106 | } 107 | 108 | pub fn inverse(&self) -> Matrix { 109 | let mut result = self.clone(); 110 | result.invert(); 111 | return result; 112 | } 113 | 114 | pub fn transpose(&mut self) { 115 | for r in 0..SIZE { 116 | for c in 0..r { 117 | let temp = self.get(r, c); 118 | self.set(r, c, self.get(c, r)); 119 | self.set(c, r, temp); 120 | } 121 | } 122 | } 123 | 124 | pub fn transposition(&self) -> Matrix { 125 | let mut result = self.clone(); 126 | result.transpose(); 127 | return result; 128 | } 129 | } 130 | 131 | // standard traits 132 | impl std::ops::Add for &Matrix { 133 | type Output = Matrix; 134 | 135 | fn add(self, other: Self) -> Self::Output { 136 | let mut result = Self::Output::new(); 137 | 138 | for i in 0..BUFFER_SIZE { 139 | result.0[i] = self.0[i] + other.0[i]; 140 | } 141 | 142 | return result; 143 | } 144 | } 145 | impl std::ops::Sub for &Matrix { 146 | type Output = Matrix; 147 | 148 | fn sub(self, other: Self) -> Self::Output { 149 | let mut result = Self::Output::new(); 150 | 151 | for i in 0..BUFFER_SIZE { 152 | result.0[i] = self.0[i] - other.0[i]; 153 | } 154 | 155 | return result; 156 | } 157 | } 158 | impl std::ops::Mul for &Matrix { 159 | type Output = Matrix; 160 | 161 | fn mul(self, other: Self) -> Self::Output { 162 | let mut result = Self::Output::new(); 163 | 164 | for r in 0..SIZE { 165 | for c in 0..SIZE { 166 | let mut val = 0.0; 167 | 168 | for n in 0..SIZE { 169 | val += self.get(r, n) * other.get(n, c); 170 | } 171 | 172 | result.set(r, c, val); 173 | } 174 | } 175 | 176 | return result; 177 | } 178 | } 179 | impl std::ops::Mul for Matrix { 180 | type Output = Matrix; 181 | 182 | fn mul(self, other: Self) -> Self::Output { 183 | &self * &other 184 | } 185 | } 186 | 187 | impl std::ops::Mul for &Matrix { 188 | type Output = Matrix; 189 | 190 | fn mul(self, scale: f32) -> Self::Output { 191 | let mut result = Self::Output::new(); 192 | 193 | for i in 0..BUFFER_SIZE { 194 | result.0[i] = self.0[i] * scale; 195 | } 196 | 197 | return result; 198 | } 199 | } 200 | impl std::fmt::Debug for Matrix { 201 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 202 | write!(f, "Matrix([\n\t{}, {}, {}, {},\n\t{}, {}, {}, {},\n\t{}, {}, {}, {},\n\t{}, {}, {}, {},])", 203 | self.0[0], self.0[1], self.0[2], self.0[3], 204 | self.0[4], self.0[5], self.0[6], self.0[7], 205 | self.0[8], self.0[9], self.0[10], self.0[11], 206 | self.0[12], self.0[13], self.0[14], self.0[15],) 207 | } 208 | } 209 | 210 | #[test] 211 | fn test_mul() { 212 | let mat_1 = Matrix([ 213 | 5.0, 2.0, 8.0, 3.0, 214 | 7.0, 3.0, 10.0, 3.0, 215 | 9.0, 3.0, 2.0, 4.0, 216 | 10.0, 8.0, 3.0, 8.0 217 | ]); 218 | 219 | let mat_2 = Matrix([ 220 | 3.0, 12.0, 9.0, 3.0, 221 | 10.0, 1.0, 10.0, 12.0, 222 | 12.0, 4.0, 12.0, 4.0, 223 | 18.0, 9.0, 2.0, 10.0 224 | ]); 225 | 226 | assert_eq!(&mat_1 * &mat_2, Matrix([ 227 | 210.0, 93.0, 171.0, 105.0, 228 | 267.0, 149.0, 146.0, 169.0, 229 | 236.0, 104.0, 172.0, 128.0, 230 | 271.0, 149.0, 268.0, 169.0 231 | ])); 232 | } 233 | 234 | #[test] 235 | fn test_from_to() { 236 | // NOTE: This will fail because of floating point comparison, but it should be 237 | // clear from the output whether or not it did what it was supposed to 238 | let from = Vec3 { x: 0.2, y: 0.1, z: 0.3 }.normalized(); 239 | let to = Vec3 { x: 0.5, y: -0.1, z: 0.4 }.normalized(); 240 | 241 | let rotation = Matrix::from_to_rotation(&from, &to); 242 | 243 | let transformed = from.transformed(&rotation); 244 | 245 | let to = Vec3 { x: round_3_decimals(to.x), y: round_3_decimals(to.y), z: round_3_decimals(to.z) }; 246 | let transformed = Vec3 { x: round_3_decimals(transformed.x), y: round_3_decimals(transformed.y), z: round_3_decimals(transformed.z) }; 247 | 248 | assert_eq!(&to, &transformed); 249 | } 250 | 251 | fn round_3_decimals(x: f32) -> f32 { 252 | (x * 1000.0).round() / 1000.0 253 | } -------------------------------------------------------------------------------- /src/mesh.rs: -------------------------------------------------------------------------------- 1 | use rand::rngs::SmallRng; 2 | //use flamer::flame; 3 | use std::fs; 4 | use std::collections::HashMap; 5 | 6 | use crate::vec3::Vec3; 7 | use crate::ray::Ray; 8 | use crate::object::{Object,ObjectEnum}; 9 | use crate::intersection::Intersection; 10 | use crate::material::Material; 11 | use crate::utils::{plane_intersection,adjusted_for_normal,color_to_normal}; 12 | use crate::sphere::Sphere; 13 | use crate::obj_parser::{parse,LineType}; 14 | use crate::mtl_parser::{load_and_parse}; 15 | use crate::matrix::Matrix; 16 | use crate::illumination::Illumination; 17 | use crate::texture::Texture; 18 | use crate::color::Color; 19 | 20 | const DEFAULT_MATERIAL: Material = Material { 21 | texture_albedo: Some(Texture::Solid(Color(1.0,1.0,1.0))), 22 | texture_specular: None, 23 | texture_normal: None, 24 | texture_emission_color: None, 25 | texture_emission_intensity: None, 26 | }; 27 | 28 | #[derive(Debug, Clone, PartialEq)] 29 | pub struct Face { 30 | pub v0: usize, 31 | pub v1: usize, 32 | pub v2: usize, 33 | 34 | pub mat: Option, 35 | pub normal: Vec3, 36 | } 37 | 38 | pub struct Mesh { 39 | materials: Vec, 40 | default_material: Material, 41 | 42 | vertices: Vec, 43 | faces: Vec, 44 | uv_coords: Vec<(f32,f32)>, 45 | 46 | bounding_sphere: Sphere, 47 | } 48 | 49 | impl Mesh { 50 | 51 | pub fn from_obj(path: &str, transform: &Matrix, default_material: Option) -> Self { 52 | let data = fs::read_to_string(path).expect("Failed to open mesh file"); 53 | 54 | println!("Loading obj..."); 55 | 56 | let mut vertices = Vec::new(); 57 | let mut faces = Vec::new(); 58 | let uv_coords = Vec::new(); 59 | 60 | let mut materials: Vec = Vec::new(); 61 | let mut material_names: HashMap = HashMap::new(); 62 | 63 | let mut current_mat: Option = None; 64 | for line in parse(&data) { 65 | match line { 66 | LineType::Vertex(x, y, z) => vertices.push(Vec3 { x, y, z }.transformed(transform)), 67 | LineType::Face(v0, v1, v2) => { 68 | 69 | let vert0 = &vertices[v0.0]; 70 | let vert1 = &vertices[v1.0]; 71 | let vert2 = &vertices[v2.0]; 72 | let normal = triangle_normal(&vert0, &vert1, &vert2); 73 | 74 | faces.push(Face { 75 | v0: v0.0, 76 | v1: v1.0, 77 | v2: v2.0, 78 | 79 | mat: current_mat, 80 | normal, 81 | }); 82 | }, 83 | LineType::MTLib(file) => { 84 | let segments: Vec<&str> = path.split("/").collect(); 85 | let mut local_dir = String::new(); 86 | for i in 0..segments.len() - 1 { 87 | local_dir += segments[i]; 88 | local_dir += "/"; 89 | } 90 | 91 | for (name, mat) in load_and_parse(&(local_dir + &file)) { 92 | material_names.insert(name, materials.len()); 93 | materials.push(mat); 94 | } 95 | }, 96 | LineType::UseMaterial(name) => current_mat = material_names.get(&name).map(|x| *x), 97 | _ => () 98 | } 99 | } 100 | 101 | let bounding_sphere = get_bounding_sphere(&vertices); 102 | 103 | return Self { 104 | materials, 105 | default_material: default_material.unwrap_or(DEFAULT_MATERIAL), 106 | vertices, 107 | faces, 108 | uv_coords, 109 | bounding_sphere, 110 | } 111 | } 112 | 113 | fn inner_intersection(&self, ray: &Ray) -> Option<(Intersection,usize)> { 114 | // flame::start("Mesh::sphere_intersect"); 115 | //let sphere_intersect = self.bounding_sphere.intersection(ray); 116 | // flame::end("Mesh::sphere_intersect"); 117 | 118 | if self.bounding_sphere.intersection(ray).is_none() { 119 | return None; 120 | } else { 121 | let mut nearest_intersection: Option<(Intersection,usize)> = None; 122 | 123 | for face_index in 0..self.faces.len() { 124 | let face = &self.faces[face_index]; 125 | 126 | let vert0 = &self.vertices[face.v0]; 127 | let vert1 = &self.vertices[face.v1]; 128 | let vert2 = &self.vertices[face.v2]; 129 | 130 | let distance_squared = nearest_intersection.as_ref().map(|i| i.0.distance * i.0.distance); 131 | if distance_squared.is_none() || 132 | (vert0 - &ray.origin).len_squared() < distance_squared.unwrap() || 133 | (vert1 - &ray.origin).len_squared() < distance_squared.unwrap() || 134 | (vert2 - &ray.origin).len_squared() < distance_squared.unwrap() { 135 | 136 | // flame::start("Mesh::plane_intersection"); 137 | let plane_intersect = plane_intersection(&vert0, &face.normal, ray); 138 | // flame::end("Mesh::plane_intersection"); 139 | match plane_intersect { 140 | Some(intersection) => { 141 | if intersection.distance > 0.0 && nearest_intersection.as_ref().map(|nearest| intersection.distance < nearest.0.distance).unwrap_or(true) { 142 | let edge0 = vert1 - vert0; 143 | let edge1 = vert2 - vert1; 144 | let edge2 = vert0 - vert2; 145 | let c0 = &intersection.position - vert0; 146 | let c1 = &intersection.position - vert1; 147 | let c2 = &intersection.position - vert2; 148 | if face.normal.dot(&edge0.cross(&c0)) > 0.0 && 149 | face.normal.dot(&edge1.cross(&c1)) > 0.0 && 150 | face.normal.dot(&edge2.cross(&c2)) > 0.0 { 151 | 152 | nearest_intersection = Some((intersection, face_index)); // P is inside the triangle 153 | } 154 | } 155 | }, 156 | None => () 157 | }; 158 | } 159 | 160 | } 161 | 162 | return nearest_intersection; 163 | } 164 | } 165 | 166 | fn material_for_face_index(&self, index: usize) -> &Material { 167 | let face = &self.faces[index]; 168 | return face.mat.map(|i| self.materials.get(i).unwrap_or(&self.default_material)).unwrap_or(&self.default_material); 169 | } 170 | } 171 | 172 | fn get_bounding_sphere(vertices: &Vec) -> Sphere { 173 | let mut min = Vec3::new(); 174 | let mut max = Vec3::new(); 175 | 176 | for v in vertices { 177 | min.x = f32::min(min.x, v.x); 178 | min.y = f32::min(min.y, v.y); 179 | min.z = f32::min(min.z, v.z); 180 | 181 | max.x = f32::max(max.x, v.x); 182 | max.y = f32::max(max.y, v.y); 183 | max.z = f32::max(max.z, v.z); 184 | } 185 | 186 | let center = Vec3 { 187 | x: (min.x + max.x) / 2.0, 188 | y: (min.y + max.y) / 2.0, 189 | z: (min.z + max.z) / 2.0, 190 | }; 191 | 192 | let mut radius = 0.0; 193 | for v in vertices { 194 | radius = f32::max(radius, (v - ¢er).len()); 195 | } 196 | 197 | return Sphere::new(center, radius + 0.001, Material::new()); 198 | } 199 | 200 | impl Object for Mesh { 201 | 202 | // #[flame("Mesh")] 203 | fn intersection(&self, ray: &Ray) -> Option { 204 | self.inner_intersection(ray) 205 | .map(|mut intersection| { 206 | 207 | self.material_for_face_index(intersection.1).texture_normal.as_ref().map(|texture_normal| { 208 | let normal_color = texture_normal.color_at(self.texture_coordinate(&intersection.0.position)); 209 | intersection.0.normal = adjusted_for_normal(&intersection.0.normal, &color_to_normal(&normal_color)); 210 | }); 211 | 212 | intersection.0 213 | }) 214 | } 215 | 216 | // #[flame("Mesh")] 217 | fn texture_coordinate(&self, point: &Vec3) -> (f32,f32) { 218 | // TODO 219 | (0.0, 0.0) 220 | } 221 | 222 | // #[flame("Mesh")] 223 | fn shade(&self, ray: &Ray, objs: &Vec, rng: &mut SmallRng, bounces_remaining: u8) -> Illumination { 224 | let mut intersection = self.inner_intersection(ray).unwrap(); 225 | 226 | let material = self.material_for_face_index(intersection.1); 227 | 228 | let uv = self.texture_coordinate(&intersection.0.position); 229 | 230 | material.shade(&mut intersection.0, uv, objs, rng, bounces_remaining) 231 | } 232 | } 233 | 234 | fn triangle_normal(vert0: &Vec3, vert1: &Vec3, vert2: &Vec3) -> Vec3 { 235 | (vert1 - vert0).cross(&(vert2 - vert0)) 236 | } -------------------------------------------------------------------------------- /src/mtl_parser.rs: -------------------------------------------------------------------------------- 1 | 2 | // http://paulbourke.net/dataformats/mtl/ 3 | 4 | use std::fs; 5 | use std::collections::HashMap; 6 | 7 | use crate::material::Material; 8 | use crate::color::Color; 9 | use crate::texture::Texture; 10 | 11 | pub fn load_and_parse(path: &str) -> HashMap { 12 | let data = fs::read_to_string(path); 13 | 14 | println!("Loading mtl..."); 15 | 16 | let mats = match data { 17 | Ok(contents) => parse(&contents), 18 | Err(_) => { 19 | println!("WARNING: Failed to open materials file \"{}\"", path); 20 | HashMap::new() 21 | } 22 | }; 23 | 24 | return mats; 25 | } 26 | 27 | pub fn parse(obj: &str) -> HashMap { 28 | let mut materials = HashMap::new(); 29 | 30 | let mut mat: Option<(String,Material)> = None; 31 | for line in obj.split("\n") { 32 | let segments: Vec<&str> = line.trim().split(" ").collect(); 33 | 34 | match segments[0] { 35 | "newmtl" => { 36 | mat.map(|material| { 37 | materials.insert(material.0, material.1); 38 | }); 39 | 40 | mat = Some(( 41 | String::from(segments[1]), 42 | Material { 43 | texture_albedo: None, 44 | texture_specular: None, 45 | texture_normal: None, 46 | texture_emission_color: None, 47 | texture_emission_intensity: None, 48 | } 49 | )); 50 | }, 51 | "Kd" => { 52 | mat.as_mut().unwrap().1.texture_albedo = Some(Texture::Solid(Color( 53 | segments[1].parse().ok().unwrap(), 54 | segments[2].parse().ok().unwrap(), 55 | segments[3].parse().ok().unwrap() 56 | ))); 57 | }, 58 | 59 | /* specular tint 60 | "Ks" => { 61 | segments[1].parse().ok(), 62 | segments[2].parse().ok(), 63 | segments[3].parse().ok() 64 | },*/ 65 | 66 | /* specular angle 67 | "Ns" => { 68 | segments[1].parse().ok() 69 | },*/ 70 | 71 | /* 72 | "illum" => 73 | 74 | 0 Color on and Ambient off 75 | 1 Color on and Ambient on 76 | 2 Highlight on 77 | 3 Reflection on and Ray trace on 78 | 4 Transparency: Glass on 79 | Reflection: Ray trace on 80 | 5 Reflection: Fresnel on and Ray trace on 81 | 6 Transparency: Refraction on 82 | Reflection: Fresnel off and Ray trace on 83 | 7 Transparency: Refraction on 84 | Reflection: Fresnel on and Ray trace on 85 | 8 Reflection on and Ray trace off 86 | 9 Transparency: Glass on 87 | Reflection: Ray trace off 88 | 10 Casts shadows onto invisible surfaces 89 | */ 90 | 91 | _ => () 92 | } 93 | } 94 | 95 | mat.map(|material| { 96 | materials.insert(material.0, material.1); 97 | }); 98 | 99 | return materials; 100 | } 101 | -------------------------------------------------------------------------------- /src/obj_parser.rs: -------------------------------------------------------------------------------- 1 | 2 | pub fn parse(obj: &str) -> Vec { 3 | let mut lines = Vec::new(); 4 | 5 | for line in obj.split("\n") { 6 | lines.push(parse_line(line)); 7 | } 8 | 9 | return lines; 10 | } 11 | 12 | pub fn parse_line(line: &str) -> LineType { 13 | let segments: Vec<&str> = line.trim().split(" ").collect(); 14 | 15 | match segments[0] { 16 | "#" => LineType::Comment(String::from(segments[1])), 17 | "o" => LineType::Object(String::from(segments[1])), 18 | "v" => LineType::Vertex( 19 | segments[1].parse().ok().unwrap(), 20 | segments[2].parse().ok().unwrap(), 21 | segments[3].parse().ok().unwrap()), 22 | "vn" => LineType::VertexNormal( 23 | segments[1].parse().ok().unwrap(), 24 | segments[2].parse().ok().unwrap(), 25 | segments[3].parse().ok().unwrap()), 26 | "vt" => LineType::VertexTexture( 27 | segments[1].parse().ok().unwrap(), 28 | segments[2].parse().ok().unwrap()), 29 | "f" => LineType::Face( 30 | parse_face_vertex(segments[1]), 31 | parse_face_vertex(segments[2]), 32 | parse_face_vertex(segments[3])), 33 | "mtllib" => LineType::MTLib(String::from(segments[1])), 34 | "usemtl" => LineType::UseMaterial(String::from(segments[1])), 35 | 36 | _ => LineType::Unknown 37 | } 38 | } 39 | 40 | fn parse_face_vertex(segment: &str) -> FaceVertex { 41 | let nums: Vec<&str> = segment.trim().split("/").collect(); 42 | 43 | FaceVertex( 44 | nums[0].parse::().ok().unwrap() - 1, // vertices are 1-indexed 45 | nums[1].parse().ok(), 46 | nums[2].parse().ok()) 47 | } 48 | 49 | #[derive(Debug, Clone, PartialEq)] 50 | pub enum LineType { 51 | Comment(String), 52 | Object(String), 53 | Vertex(f32, f32, f32), 54 | VertexNormal(f32, f32, f32), 55 | VertexTexture(f32, f32), 56 | Face(FaceVertex, FaceVertex, FaceVertex), 57 | MTLib(String), 58 | UseMaterial(String), 59 | 60 | Unknown, 61 | } 62 | 63 | #[derive(Debug, Copy, Clone, PartialEq)] 64 | pub struct FaceVertex (pub usize, pub Option, pub Option); -------------------------------------------------------------------------------- /src/object.rs: -------------------------------------------------------------------------------- 1 | 2 | use rand::rngs::SmallRng; 3 | 4 | use crate::ray::Ray; 5 | use crate::vec3::Vec3; 6 | use crate::intersection::Intersection; 7 | use crate::illumination::Illumination; 8 | 9 | use crate::plane::Plane; 10 | use crate::sphere::Sphere; 11 | use crate::mesh::Mesh; 12 | 13 | pub trait Object { 14 | 15 | /** 16 | * Get information about the point where a ray intersects this object, 17 | * if it does at all. 18 | */ 19 | fn intersection(&self, ray: &Ray) -> Option; 20 | 21 | /** 22 | * Get the UV coordinate on this object's texture for a given 23 | * world-space coordinate. 24 | */ 25 | fn texture_coordinate(&self, point: &Vec3) -> (f32,f32); 26 | 27 | 28 | fn shade(&self, ray: &Ray, objs: &Vec, rng: &mut SmallRng, bounces_remaining: u8) -> Illumination; 29 | } 30 | 31 | pub enum ObjectEnum { 32 | Plane(Plane), 33 | Sphere(Sphere), 34 | Mesh(Mesh) 35 | } 36 | 37 | impl Object for ObjectEnum { 38 | fn intersection(&self, ray: &Ray) -> Option { 39 | match self { 40 | ObjectEnum::Plane(data) => data.intersection(ray), 41 | ObjectEnum::Sphere(data) => data.intersection(ray), 42 | ObjectEnum::Mesh(data) => data.intersection(ray), 43 | } 44 | } 45 | fn texture_coordinate(&self, point: &Vec3) -> (f32,f32) { 46 | match self { 47 | ObjectEnum::Plane(data) => data.texture_coordinate(point), 48 | ObjectEnum::Sphere(data) => data.texture_coordinate(point), 49 | ObjectEnum::Mesh(data) => data.texture_coordinate(point), 50 | } 51 | } 52 | fn shade(&self, ray: &Ray, objs: &Vec, rng: &mut SmallRng, bounces_remaining: u8) -> Illumination { 53 | match self { 54 | ObjectEnum::Plane(data) => data.shade(ray, objs, rng, bounces_remaining), 55 | ObjectEnum::Sphere(data) => data.shade(ray, objs, rng, bounces_remaining), 56 | ObjectEnum::Mesh(data) => data.shade(ray, objs, rng, bounces_remaining), 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/plane.rs: -------------------------------------------------------------------------------- 1 | 2 | use rand::rngs::SmallRng; 3 | //use flamer::flame; 4 | 5 | use crate::vec3::Vec3; 6 | use crate::ray::Ray; 7 | use crate::object::{Object,ObjectEnum}; 8 | use crate::intersection::Intersection; 9 | use crate::material::Material; 10 | use crate::utils::{plane_intersection,PI_OVER_TWO,adjusted_for_normal,color_to_normal}; 11 | use crate::illumination::Illumination; 12 | 13 | pub struct Plane { 14 | pub position: Vec3, 15 | pub normal: Vec3, 16 | pub material: Material, 17 | projected_bias: Vec3, 18 | rotated_projected_bias: Vec3, 19 | } 20 | 21 | impl Plane { 22 | 23 | pub fn new(position: Vec3, normal: Vec3, bias: Vec3, material: Material) -> Self { 24 | let projected_bias = projection(&position, &normal, &bias).normalized(); 25 | 26 | Self { 27 | position, 28 | normal: normal.normalized(), 29 | material, 30 | projected_bias: projected_bias.normalized(), 31 | rotated_projected_bias: projected_bias.rotated_around(&normal, -1.0 * PI_OVER_TWO).normalized() 32 | } 33 | } 34 | 35 | pub fn projection(&self, point: &Vec3) -> Vec3 { 36 | projection(&self.position, &self.normal, point) 37 | } 38 | } 39 | 40 | impl Object for Plane { 41 | 42 | // #[flame("Plane")] 43 | fn intersection(&self, ray: &Ray) -> Option { 44 | plane_intersection(&self.position, &self.normal, ray) 45 | .map(|mut intersection| { 46 | 47 | self.material.texture_normal.as_ref().map(|texture_normal| { 48 | let normal_color = texture_normal.color_at(self.texture_coordinate(&intersection.position)); 49 | intersection.normal = adjusted_for_normal(&intersection.normal, &color_to_normal(&normal_color)); 50 | }); 51 | 52 | intersection 53 | }) 54 | } 55 | 56 | // #[flame("Plane")] 57 | fn texture_coordinate(&self, point: &Vec3) -> (f32,f32) { 58 | let point_projected_on_plane = self.projection(point); 59 | 60 | let proj_y = point_projected_on_plane.projected_on(&self.projected_bias); 61 | let difference_vec_y = &point_projected_on_plane - &proj_y; 62 | let u = difference_vec_y.x.signum() * difference_vec_y.len(); 63 | 64 | let proj_x = point_projected_on_plane.projected_on(&self.rotated_projected_bias); 65 | let difference_vec_x = &point_projected_on_plane - &proj_x; 66 | let v = difference_vec_x.y.signum() * difference_vec_x.len(); 67 | 68 | (u - u.floor(), v - v.floor()) 69 | } 70 | 71 | // #[flame("Plane")] 72 | fn shade(&self, ray: &Ray, objs: &Vec, rng: &mut SmallRng, bounces_remaining: u8) -> Illumination { 73 | let mut intersection = self.intersection(ray).unwrap(); 74 | let uv = self.texture_coordinate(&intersection.position); 75 | 76 | self.material.shade( 77 | &mut intersection, 78 | uv, 79 | objs, 80 | rng, 81 | bounces_remaining 82 | ) 83 | } 84 | } 85 | 86 | fn projection(origin: &Vec3, normal: &Vec3, point: &Vec3) -> Vec3 { 87 | point - &point.projected_on(&(origin + normal)) 88 | } -------------------------------------------------------------------------------- /src/ray.rs: -------------------------------------------------------------------------------- 1 | 2 | use rand::Rng; 3 | use rand::rngs::SmallRng; 4 | //use flamer::flame; 5 | 6 | use crate::vec3::Vec3; 7 | use crate::utils::{TWO_PI,PI_OVER_TWO}; 8 | 9 | #[derive(Debug, Copy, Clone)] 10 | pub struct Ray { 11 | pub origin: Vec3, 12 | pub direction: Vec3 13 | } 14 | 15 | impl Ray { 16 | 17 | pub fn new() -> Self { 18 | Self { 19 | origin: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 20 | direction: Vec3 { x: 1.0, y: 0.0, z: 0.0 }, 21 | } 22 | } 23 | 24 | // #[flame("Ray")] 25 | pub fn random_direction(origin: Vec3, rng: &mut SmallRng) -> Self { 26 | Self { 27 | origin, 28 | direction: Vec3::from_angles( 29 | rng.gen_range(0.0, TWO_PI), 30 | rng.gen_range(-1.0 * PI_OVER_TWO, PI_OVER_TWO), 31 | ) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/scenes.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::color::Color; 3 | use crate::material::Material; 4 | use crate::vec3::Vec3; 5 | use crate::sphere::Sphere; 6 | use crate::mesh::{Mesh}; 7 | use crate::plane::Plane; 8 | use crate::texture::Texture; 9 | use crate::matrix::Matrix; 10 | use crate::object::ObjectEnum; 11 | 12 | pub fn construct_reflect_scene() -> Vec { 13 | let mut objs: Vec = Vec::new(); 14 | 15 | objs.push(ObjectEnum::Sphere(Sphere::new( 16 | Vec3 { x: 5.0, y: 5.0, z: -12.0 }, 17 | 5.0, 18 | Material { 19 | texture_albedo: None,//Some(Texture::Solid(Color::gray(1.0))), 20 | texture_specular: None,//Some(Texture::Solid(Color::gray(1.0))), 21 | texture_normal: None, 22 | texture_emission_color: None, 23 | texture_emission_intensity: Some(Texture::Solid(Color::gray(10.0))) 24 | } 25 | ))); 26 | 27 | objs.push(ObjectEnum::Plane(Plane::new( 28 | Vec3 { x: 0.0, y: -1.5, z: 0.0, }, 29 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 30 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 31 | Material { 32 | texture_albedo: Some(Texture::Solid(Color::gray(1.0))), 33 | texture_specular: None, 34 | texture_normal: Some(Texture::from_image("/Users/brundolf/git/raytracer/cobblestone_normal.jpg")), 35 | texture_emission_color: None, 36 | texture_emission_intensity: None, 37 | } 38 | ))); 39 | 40 | return objs; 41 | } 42 | 43 | pub fn construct_material_scene() -> Vec { 44 | let mut objs: Vec = Vec::new(); 45 | 46 | objs.push(ObjectEnum::Sphere(Sphere::new( 47 | Vec3 { x: 0.0, y: 0.0, z: -12.0 }, 48 | 1.0, 49 | Material { 50 | texture_albedo: None,//Some(Texture::Solid(Color::gray(1.0))), 51 | texture_specular: None,//Some(Texture::Solid(Color::gray(1.0))), 52 | texture_normal: None, 53 | texture_emission_color: Some(Texture::Solid(Color(1.0,0.0,0.0))), 54 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))) 55 | } 56 | ))); 57 | 58 | // ceiling 59 | objs.push(ObjectEnum::Plane(Plane::new( 60 | Vec3 { x: 0.0, y: 5.0, z: 0.0, }, 61 | Vec3 { x: 0.0, y: -1.0, z: 0.0 }, 62 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 63 | Material { 64 | texture_albedo: None,//Some(Box::new(TextureSolid { color: Color(1.0, 0.95, 0.8) })), 65 | texture_specular: None, 66 | texture_normal: None, 67 | texture_emission_color: None, 68 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))), 69 | } 70 | ))); 71 | 72 | objs.push(ObjectEnum::Plane(Plane::new( 73 | Vec3 { x: 0.0, y: -1.5, z: 0.0, }, 74 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 75 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 76 | Material { 77 | texture_albedo: None,//Some(Texture::Solid(Color::gray(1.0))), 78 | texture_specular: Some(Texture::from_image("/Users/brundolf/git/raytracer/CobblestoneSpecular.jpg")), 79 | texture_normal: None, 80 | texture_emission_color: None, 81 | texture_emission_intensity: None, 82 | } 83 | ))); 84 | 85 | return objs; 86 | } 87 | 88 | pub fn construct_tree_scene() -> Vec { 89 | let mut objs: Vec = Vec::new(); 90 | 91 | objs.push(ObjectEnum::Mesh(Mesh::from_obj( 92 | "/Users/brundolf/git/raytracer/tree.obj", 93 | &(Matrix::translation(&Vec3 { x: 0.0, y: 0.0, z: -3.0 }) 94 | * Matrix::rotation_y(std::f32::consts::PI / -4.0)), 95 | None 96 | ))); 97 | 98 | objs.push(ObjectEnum::Plane(Plane::new( 99 | Vec3 { x: 2.0, y: 0.0, z: 2.0, }, 100 | Vec3 { x: -1.0, y: 0.0, z: -1.0 }, 101 | Vec3 { x: 1.0, y: 0.0, z: -1.0 }, 102 | Material { 103 | texture_albedo: None, 104 | texture_specular: None, 105 | texture_normal: None, 106 | texture_emission_color: Some(Texture::Solid(Color(1.0, 0.52, 0.17))), 107 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))) 108 | } 109 | ))); 110 | 111 | return objs; 112 | } 113 | 114 | pub fn construct_room_scene() -> Vec { 115 | let mut objs: Vec = Vec::new(); 116 | 117 | // spheres 118 | objs.push(ObjectEnum::Sphere(Sphere::new( 119 | Vec3 { x: 3.0, y: -3.0, z: -13.0 }, 120 | 3.0, 121 | Material { 122 | texture_albedo: Some(Texture::Solid(Color::gray(1.0))), 123 | texture_specular: None,//Some(Texture::Solid(Color::gray(1.0))), 124 | texture_normal: Some(Texture::from_image("/Users/brundolf/git/raytracer/cobblestone_normal.jpg")), 125 | texture_emission_color: Some(Texture::Solid(Color(0.0, 1.0, 1.0))), 126 | texture_emission_intensity: Some(Texture::Solid(Color::gray(5.0))) 127 | } 128 | ))); 129 | 130 | objs.push(ObjectEnum::Sphere(Sphere::new( 131 | Vec3 { x: -2.0, y: 0.0, z: -8.0 }, 132 | 1.0, 133 | Material { 134 | texture_albedo: None,//Some(Texture::Solid(Color::gray(1.0))), 135 | texture_specular: Some(Texture::Solid(Color::gray(1.0))), 136 | texture_normal: None,//Some(Texture::from_image("/Users/brundolf/git/raytracer/cobblestone_normal.jpg")), 137 | texture_emission_color: None, 138 | texture_emission_intensity: None, 139 | } 140 | ))); 141 | 142 | /* 143 | objs.push(Box::new(Mesh::from_obj( 144 | "/Users/brundolf/git/raytracer/test.obj", 145 | &(Matrix::translation(&Vec3 { x: 0.0, y: -3.0, z: -10.0 }) 146 | * Matrix::rotation_y(std::f32::consts::PI) 147 | * Matrix::scale(&Vec3::from_scalar(0.5))), 148 | None 149 | )));*/ 150 | 151 | // ceiling 152 | objs.push(ObjectEnum::Plane(Plane::new( 153 | Vec3 { x: 0.0, y: 5.0, z: 0.0, }, 154 | Vec3 { x: 0.0, y: -1.0, z: 0.0 }, 155 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 156 | Material { 157 | texture_albedo: Some(Texture::Solid(Color::gray(1.0))), 158 | texture_specular: None, 159 | texture_normal: None, 160 | texture_emission_color: None,//Some(Texture::Solid(Color(1.0, 0.95, 0.8))), 161 | texture_emission_intensity: None,//Some(Texture::Solid(Color(1.0, 0.95, 0.8))), 162 | } 163 | ))); 164 | 165 | // floor 166 | objs.push(ObjectEnum::Plane(Plane::new( 167 | Vec3 { x: 0.0, y: -5.0, z: 0.0, }, 168 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 169 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 170 | Material { 171 | texture_albedo: Some(Texture::Solid(Color::gray(1.0))), 172 | texture_specular: None, 173 | texture_normal: Some(Texture::from_image("/Users/brundolf/git/raytracer/cobblestone_normal.jpg")), 174 | texture_emission_color: None, 175 | texture_emission_intensity: None, 176 | } 177 | ))); 178 | 179 | 180 | // left wall 181 | objs.push(ObjectEnum::Plane(Plane::new( 182 | Vec3 { x: -5.0, y: 0.0, z: 0.0, }, 183 | Vec3 { x: 1.0, y: 0.0, z: 0.0 }, 184 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 185 | Material { 186 | texture_albedo: Some(Texture::Solid(Color(1.0,0.0,0.0))), 187 | texture_specular: None, 188 | texture_normal: Some(Texture::from_image("/Users/brundolf/git/raytracer/cobblestone_normal.jpg")), 189 | texture_emission_color: None, 190 | texture_emission_intensity: None, 191 | } 192 | ))); 193 | 194 | // right wall 195 | objs.push(ObjectEnum::Plane(Plane::new( 196 | Vec3 { x: 5.0, y: 0.0, z: 0.0, }, 197 | Vec3 { x: -1.0, y: 0.0, z: 0.0 }, 198 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 199 | Material { 200 | texture_albedo: Some(Texture::Solid(Color(0.0, 1.0, 0.0))), 201 | texture_specular: None,//Some(Texture::Solid(Color::gray(1.0))), 202 | texture_normal: Some(Texture::from_image("/Users/brundolf/git/raytracer/cobblestone_normal.jpg")), 203 | texture_emission_color: None, 204 | texture_emission_intensity: None, 205 | } 206 | ))); 207 | 208 | // back wall 209 | objs.push(ObjectEnum::Plane(Plane::new( 210 | Vec3 { x: 0.0, y: 0.0, z: -15.0, }, 211 | Vec3 { x: 0.0, y: 0.0, z: 1.0 }, 212 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 213 | Material { 214 | texture_albedo: Some(Texture::Solid(Color::gray(1.0))), 215 | texture_specular: None, 216 | texture_normal: None,//Some(Texture::from_image("/Users/brundolf/git/raytracer/cobblestone_normal.jpg")), 217 | texture_emission_color: None, 218 | texture_emission_intensity: None, 219 | } 220 | ))); 221 | 222 | // near wall 223 | objs.push(ObjectEnum::Plane(Plane::new( 224 | Vec3 { x: 0.0, y: 0.0, z: 1.0, }, 225 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 226 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 227 | Material { 228 | texture_albedo: Some(Texture::Solid(Color(0.0,0.0,1.0))), 229 | texture_specular: None, 230 | texture_normal: None, 231 | texture_emission_color: None, 232 | texture_emission_intensity: None, 233 | } 234 | ))); 235 | 236 | return objs; 237 | } 238 | 239 | 240 | /* 241 | for _ in 0..10 { 242 | objs.push(Box::new(Sphere::new( 243 | Vec3 { 244 | x: (rand::random::() % 10) as f32 - 5.0, 245 | y: (rand::random::() % 10) as f32 - 5.0, 246 | z: (rand::random::() % 10) as f32 - 15.0, 247 | }, 248 | 1.0, 249 | Material { 250 | texture_albedo: Some(Texture::Procedural(&checker)), 251 | texture_specular: None, 252 | texture_emission: None, 253 | } 254 | ))) 255 | }*/ 256 | 257 | 258 | pub fn construct_plane_texture_test() -> Vec { 259 | let mut objs: Vec = Vec::new(); 260 | 261 | objs.push(ObjectEnum::Plane(Plane::new( 262 | Vec3 { x: 0.0, y: 5.0, z: 0.0, }, 263 | Vec3 { x: 0.0, y: -1.0, z: 0.0 }, 264 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 265 | Material { 266 | texture_albedo: None, 267 | texture_specular: None, 268 | texture_normal: None, 269 | texture_emission_color: None, 270 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))), 271 | } 272 | ))); 273 | 274 | objs.push(ObjectEnum::Plane(Plane::new( 275 | Vec3 { x: 0.0, y: -1.5, z: 0.0, }, 276 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 277 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 278 | Material { 279 | texture_albedo: Some(Texture::from_image("/Users/brundolf/git/raytracer/grid.jpg")), 280 | texture_specular: None, 281 | texture_normal: None, 282 | texture_emission_color: None, 283 | texture_emission_intensity: None, 284 | } 285 | ))); 286 | 287 | return objs; 288 | } 289 | 290 | pub fn construct_sphere_texture_test() -> Vec { 291 | let mut objs: Vec = Vec::new(); 292 | 293 | objs.push(ObjectEnum::Sphere(Sphere::new( 294 | Vec3 { x: 0.0, y: 0.0, z: -5.0 }, 295 | 1.0, 296 | Material { 297 | texture_albedo: Some(Texture::from_image("C:\\Users\\Brundon\\git\\raytracer\\texture.jpg")), 298 | texture_specular: None,//Some(Texture::Solid(Color::gray(1.0))), 299 | texture_normal: None, 300 | texture_emission_color: None, 301 | texture_emission_intensity: None, 302 | } 303 | ))); 304 | 305 | 306 | // ceiling 307 | objs.push(ObjectEnum::Plane(Plane::new( 308 | Vec3 { x: 0.0, y: 5.0, z: 0.0, }, 309 | Vec3 { x: 0.0, y: -1.0, z: 0.0 }, 310 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 311 | Material { 312 | texture_albedo: None, 313 | texture_specular: None, 314 | texture_normal: None, 315 | texture_emission_color: None, 316 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))), 317 | } 318 | ))); 319 | 320 | 321 | // floor 322 | objs.push(ObjectEnum::Plane(Plane::new( 323 | Vec3 { x: 0.0, y: -5.0, z: 0.0, }, 324 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 325 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 326 | Material { 327 | texture_albedo: None, 328 | texture_specular: None, 329 | texture_normal: None, 330 | texture_emission_color: None, 331 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))), 332 | } 333 | ))); 334 | 335 | 336 | // left wall 337 | objs.push(ObjectEnum::Plane(Plane::new( 338 | Vec3 { x: -5.0, y: 0.0, z: 0.0, }, 339 | Vec3 { x: 1.0, y: 0.0, z: 0.0 }, 340 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 341 | Material { 342 | texture_albedo: None, 343 | texture_specular: None, 344 | texture_normal: None, 345 | texture_emission_color: None, 346 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))), 347 | } 348 | ))); 349 | 350 | // right wall 351 | objs.push(ObjectEnum::Plane(Plane::new( 352 | Vec3 { x: 5.0, y: 0.0, z: 0.0, }, 353 | Vec3 { x: -1.0, y: 0.0, z: 0.0 }, 354 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 355 | Material { 356 | texture_albedo: None, 357 | texture_specular: None, 358 | texture_normal: None, 359 | texture_emission_color: None, 360 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))), 361 | } 362 | ))); 363 | 364 | // back wall 365 | objs.push(ObjectEnum::Plane(Plane::new( 366 | Vec3 { x: 0.0, y: 0.0, z: -15.0, }, 367 | Vec3 { x: 0.0, y: 0.0, z: 1.0 }, 368 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 369 | Material { 370 | texture_albedo: None, 371 | texture_specular: None, 372 | texture_normal: None, 373 | texture_emission_color: None, 374 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))), 375 | } 376 | ))); 377 | 378 | // near wall 379 | objs.push(ObjectEnum::Plane(Plane::new( 380 | Vec3 { x: 0.0, y: 0.0, z: 1.0, }, 381 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 382 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 383 | Material { 384 | texture_albedo: None, 385 | texture_specular: None, 386 | texture_normal: None, 387 | texture_emission_color: None, 388 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))), 389 | } 390 | ))); 391 | 392 | return objs; 393 | } 394 | 395 | pub fn construct_wallpaper_scene() -> Vec { 396 | let mut objs: Vec = Vec::new(); 397 | 398 | // floor 399 | objs.push(ObjectEnum::Plane(Plane::new( 400 | Vec3 { x: 0.0, y: -5.0, z: 0.0, }, 401 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 402 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 403 | Material { 404 | texture_albedo: Some(Texture::Solid(Color::gray(1.0))), 405 | texture_specular: None, 406 | texture_normal: None, 407 | texture_emission_color: None, 408 | texture_emission_intensity: None, 409 | } 410 | ))); 411 | 412 | for x_inc in 0..COUNT_X { 413 | for z_inc in 0..COUNT_Z { 414 | let x = x_inc as f32 * SPACING; 415 | let z = z_inc as f32 * -1.0 * SPACING; 416 | let y = (&(&Vec3 { x: x, y: ORIGIN.y, z: z } - &ORIGIN) * (1.0 / 50.0)).len_squared(); 417 | 418 | objs.push(ObjectEnum::Sphere(Sphere::new( 419 | &ORIGIN + &Vec3 { x, y, z }, 420 | 0.5, 421 | Material { 422 | texture_albedo: None, 423 | texture_specular: None, 424 | texture_normal: None, 425 | texture_emission_color: Some(Texture::Solid(Color(0.0, z_inc as f32 / 4.0, x_inc as f32 / 8.0))), 426 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))) 427 | } 428 | ))); 429 | } 430 | } 431 | 432 | return objs; 433 | } 434 | 435 | pub fn construct_wallpaper_scene_2() -> Vec { 436 | let mut objs: Vec = Vec::new(); 437 | 438 | objs.push(ObjectEnum::Mesh(Mesh::from_obj( 439 | "/Users/brundolf/git/raytracer/Geometric.obj", 440 | &(Matrix::translation(&Vec3 { x: -0.5, y: 0.0, z: -8.0 }) 441 | * Matrix::rotation_x(std::f32::consts::PI * -1.0 / 8.0) 442 | * Matrix::rotation_y(std::f32::consts::PI * -1.0 / 8.0) 443 | /* 444 | Matrix::rotation_y(std::f32::consts::PI * 5.0 / 4.0) * 445 | Matrix::rotation_x(std::f32::consts::PI * 1.0 / 4.0)*/), 446 | Some(Material { 447 | texture_albedo: None, 448 | texture_specular: Some(Texture::Solid(Color::gray(0.8))), 449 | texture_normal: None, 450 | texture_emission_color: None, 451 | texture_emission_intensity: None, 452 | }) 453 | ))); 454 | 455 | for a in 0..8 { 456 | for b in 0..4 { 457 | let a_portion = a as f32 / 8.0; 458 | let b_portion = b as f32 / 4.0; 459 | 460 | let longitude = (2.0 * std::f32::consts::PI) * a_portion + (std::f32::consts::PI / 8.0); 461 | let latitude = std::f32::consts::PI * b_portion - std::f32::consts::PI / 2.0; 462 | 463 | println!("{}, {}", longitude, latitude); 464 | 465 | let pos = &Vec3::from_angles(longitude, latitude) * 3.0; 466 | 467 | objs.push(ObjectEnum::Sphere(Sphere::new( 468 | &Vec3 { x: 0.0, y: 0.0, z: -8.0 } + &pos, 469 | 0.5, 470 | Material { 471 | texture_albedo: None, 472 | texture_specular: None, 473 | texture_normal: None, 474 | texture_emission_color: Some(Texture::Solid(Color(1.0, 0.0, 0.0))), 475 | texture_emission_intensity: Some(Texture::Solid(Color::gray(5.0))) 476 | } 477 | ))); 478 | } 479 | } 480 | 481 | return objs; 482 | } 483 | 484 | pub fn construct_wallpaper_scene_3() -> Vec { 485 | let mut objs: Vec = Vec::new(); 486 | 487 | // sky 488 | /* 489 | objs.push(ObjectEnum::Sphere(Sphere::new( 490 | Vec3 { x: 0.0, y: -3.0, z: -5.0 }, 491 | 1000.0, 492 | Material { 493 | texture_albedo: None, 494 | texture_specular: None, 495 | texture_normal: None, 496 | texture_emission_color: Some(Texture::Solid(Color(0.8470588235294118, 0.9529411764705882, 1.0))), 497 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))), 498 | } 499 | )));*/ 500 | 501 | // sun 502 | /* 503 | objs.push(ObjectEnum::Sphere(Sphere::new( 504 | Vec3 { x: 10.0, y: 10.0, z: 10.0 }, 505 | 5.0, 506 | Material { 507 | texture_albedo: None, 508 | texture_specular: None, 509 | texture_normal: None, 510 | texture_emission_color: Some(Texture::Solid(Color(1.0, 0.95, 0.8))), 511 | texture_emission_intensity: Some(Texture::Solid(Color::gray(10.0))), 512 | } 513 | )));*/ 514 | 515 | 516 | // ceiling 517 | 518 | objs.push(ObjectEnum::Plane(Plane::new( 519 | Vec3 { x: 0.0, y: 5.0, z: 0.0, }, 520 | Vec3 { x: 0.0, y: -1.0, z: 0.0 }, 521 | Vec3 { x: 0.0, y: 0.0, z: -1.0 }, 522 | Material { 523 | texture_albedo: None, 524 | texture_specular: None, 525 | texture_normal: None, 526 | texture_emission_color: Some(Texture::Solid(Color(0.8470588235294118, 0.9529411764705882, 1.0))), 527 | texture_emission_intensity: Some(Texture::Solid(Color::gray(1.0))), 528 | } 529 | ))); 530 | 531 | // room 532 | objs.push(ObjectEnum::Mesh(Mesh::from_obj( 533 | "/Users/brundolf/git/raytracer/Room.obj", 534 | &Matrix::translation(&Vec3 { x: 0.0, y: -3.0, z: -5.0 }), 535 | Some(Material { 536 | texture_albedo: Some(Texture::Solid(Color::gray(1.0))), 537 | texture_specular: None, 538 | texture_normal: None, 539 | texture_emission_color: None, 540 | texture_emission_intensity: None, 541 | }) 542 | ))); 543 | 544 | return objs; 545 | } 546 | 547 | const COUNT_X: usize = 8; 548 | const COUNT_Z: usize = 4; 549 | const SPACING: f32 = 2.0; 550 | const ORIGIN: Vec3 = Vec3 { 551 | x: -1.0 * ((COUNT_X - 1) as f32 * SPACING / 2.0), 552 | z: -12.0, 553 | y: -4.0 554 | }; -------------------------------------------------------------------------------- /src/sphere.rs: -------------------------------------------------------------------------------- 1 | use rand::rngs::SmallRng; 2 | //use flamer::flame; 3 | use std::f32::consts::PI; 4 | 5 | use crate::vec3::Vec3; 6 | use crate::ray::Ray; 7 | use crate::object::{Object,ObjectEnum}; 8 | use crate::intersection::Intersection; 9 | use crate::material::Material; 10 | use crate::illumination::Illumination; 11 | use crate::utils::{TWO_PI,color_to_normal,adjusted_for_normal}; 12 | 13 | pub struct Sphere { 14 | position: Vec3, 15 | radius: f32, 16 | material: Material, 17 | radius_squared: f32, 18 | } 19 | 20 | impl Sphere { 21 | 22 | pub fn new(position: Vec3, radius: f32, material: Material) -> Self { 23 | Self { 24 | position, 25 | radius, 26 | material, 27 | radius_squared: radius * radius, 28 | } 29 | } 30 | 31 | /* 32 | pub fn contains(&self, vec: &Vec3) -> bool { 33 | (vec - &self.position).len_squared() < self.radius_squared 34 | }*/ 35 | 36 | /* 37 | pub fn surface_point(&self, latitude: f32, longitude: f32) -> Vec3 { 38 | let lat_cos = latitude.cos(); 39 | let lon_sin = longitude.sin(); 40 | let lat_sin = latitude.sin(); 41 | 42 | &self.position + &(&Vec3 { 43 | x: lat_cos * lon_sin, 44 | y: lat_cos, 45 | z: lat_sin * lon_sin 46 | } * self.radius) 47 | } 48 | */ 49 | } 50 | 51 | impl Object for Sphere { 52 | 53 | // #[flame("Sphere")] 54 | fn intersection(&self, ray: &Ray) -> Option { 55 | 56 | // analytic solution 57 | let l: Vec3 = &ray.origin - &self.position; 58 | let a: f32 = ray.direction.dot(&ray.direction); 59 | let b: f32 = 2.0 * ray.direction.dot(&l); 60 | let c: f32 = l.dot(&l) - self.radius_squared; 61 | 62 | match solve_quadratic(a, b, c) { 63 | Some((mut t0, t1)) => { 64 | 65 | if t0 < 0.0 { 66 | t0 = t1; // if t0 is negative, let's use t1 instead 67 | 68 | if t0 < 0.0 { 69 | return None; // both t0 and t1 are negative 70 | } 71 | } 72 | 73 | let distance = t0; 74 | let position = &ray.origin + &(&ray.direction * distance); 75 | let mut normal = &position - &self.position; 76 | normal.normalize(); 77 | 78 | if let Some(texture_normal) = self.material.texture_normal.as_ref() { 79 | let normal_color = texture_normal.color_at(self.texture_coordinate(&position)); 80 | normal = adjusted_for_normal(&normal, &color_to_normal(&normal_color)); 81 | } 82 | 83 | Some(Intersection::new( 84 | distance, 85 | &position + &(&normal * 0.001), // offset to avoid floating-point error 86 | normal, 87 | ray.direction, 88 | )) 89 | }, 90 | None => None 91 | } 92 | } 93 | 94 | // #[flame("Sphere")] 95 | fn texture_coordinate(&self, point: &Vec3) -> (f32,f32) { 96 | let relative_point = point - &self.position; 97 | 98 | let longitude = (relative_point.z / relative_point.x).atan(); 99 | let continuous_longitude = longitude 100 | + if relative_point.x < 0.0 { PI } else { 0.0 } 101 | + if relative_point.x >= 0.0 && relative_point.z < 0.0 { TWO_PI } else { 0.0 }; 102 | 103 | let latitude = (relative_point.y / self.radius).acos(); 104 | 105 | let u = continuous_longitude / TWO_PI; 106 | let v = 1.0 - latitude / PI; 107 | 108 | // Some extra tiling (4x4) 109 | return ( 110 | (u * 4.0) - (u * 4.0).floor(), 111 | (v * 4.0) - (v * 4.0).floor() 112 | ); 113 | } 114 | 115 | // #[flame("Sphere")] 116 | fn shade(&self, ray: &Ray, objs: &Vec, rng: &mut SmallRng, bounces_remaining: u8) -> Illumination { 117 | let mut intersection = self.intersection(ray).unwrap(); 118 | let uv = self.texture_coordinate(&intersection.position); 119 | 120 | self.material.shade( 121 | &mut intersection, 122 | uv, 123 | objs, 124 | rng, 125 | bounces_remaining 126 | ) 127 | } 128 | } 129 | 130 | 131 | fn solve_quadratic(a: f32, b: f32, c: f32) -> Option<(f32,f32)> { 132 | let discr = b * b - 4.0 * a * c; 133 | 134 | let mut t0: f32; 135 | let mut t1: f32; 136 | 137 | if discr < 0.0 { 138 | return None; 139 | } else if discr == 0.0 {//discr.abs() < 0.0001 { 140 | t0 = -0.5 * b / a; 141 | t1 = t0; 142 | } else { 143 | let q = 144 | if b > 0.0 { 145 | -0.5 * (b + discr.sqrt()) 146 | } else { 147 | -0.5 * (b - discr.sqrt()) 148 | }; 149 | 150 | t0 = q / a; 151 | t1 = c / q; 152 | } 153 | 154 | if t0 > t1 { 155 | std::mem::swap(&mut t0, &mut t1); 156 | } 157 | 158 | return Some((t0, t1)); 159 | } 160 | -------------------------------------------------------------------------------- /src/texture.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::fs::File; 3 | use std::io::BufReader; 4 | use image::{DynamicImage,ImageFormat,GenericImageView,load}; 5 | 6 | use crate::color::Color; 7 | 8 | 9 | pub enum Texture { 10 | Solid(Color), 11 | Image(DynamicImage), 12 | Procedural(&'static (dyn Send + Sync + Fn((f32,f32)) -> Color)), 13 | } 14 | 15 | impl Texture { 16 | 17 | pub fn from_image(path: &str) -> Self { 18 | let f = File::open(path).expect("Failed to open texture file"); 19 | let f = BufReader::new(f); 20 | let image = load(f, ImageFormat::JPEG).expect("Failed to load image"); 21 | 22 | Texture::Image(image) 23 | } 24 | 25 | pub fn color_at(&self, uv: (f32,f32)) -> Color { 26 | match self { 27 | Texture::Solid(color) => *color, 28 | Texture::Image(image) => { 29 | let x = uv_to_pixel(uv.0, image.dimensions().0); 30 | let y = uv_to_pixel(uv.1, image.dimensions().1); 31 | let p = image.get_pixel(x, y); 32 | 33 | Color( 34 | u8_to_f32(p[0]), 35 | u8_to_f32(p[1]), 36 | u8_to_f32(p[2])) 37 | } 38 | Texture::Procedural(func) => func(uv), 39 | } 40 | } 41 | } 42 | 43 | fn uv_to_pixel(uv: f32, dim: u32) -> u32 { 44 | (uv * dim as f32).floor() as u32 45 | } 46 | 47 | fn u8_to_f32(val: u8) -> f32 { 48 | val as f32 / 255.0 49 | } 50 | 51 | /** 52 | * Procedural texture callback for a checkerboard pattern 53 | */ 54 | pub fn checker(uv: (f32,f32)) -> Color { 55 | let scaled_u = (uv.0 * 4.0) as u32; 56 | let u_is_base = scaled_u % 2 == 0; 57 | 58 | let scaled_v = (uv.1 * 4.0) as u32; 59 | let v_is_base = scaled_v % 2 == 0; 60 | 61 | return if u_is_base == v_is_base { Color(1.0, 1.0, 1.0) } 62 | else { Color(0.5, 0.5, 0.5) } 63 | } 64 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | 3 | use crate::vec3::Vec3; 4 | use crate::ray::Ray; 5 | use crate::intersection::Intersection; 6 | use crate::color::Color; 7 | use crate::matrix::Matrix; 8 | 9 | pub const TWO_PI: f32 = PI * 2.0; 10 | pub const PI_OVER_TWO: f32 = PI / 2.0; 11 | 12 | 13 | pub fn clamp(val: T, min: T, max: T) -> T { 14 | if val < min { min } 15 | else if val > max { max } 16 | else { val } 17 | } 18 | 19 | pub fn avg(a: f32, b: f32) -> f32 { 20 | (a + b) / 2.0 21 | } 22 | 23 | pub fn lerp(a: f32, b: f32, t: f32) -> f32 { 24 | a + t * (b - a) 25 | } 26 | 27 | pub fn plane_intersection(position: &Vec3, normal: &Vec3, ray: &Ray) -> Option { 28 | let numerator = (position - &ray.origin).dot(&normal); 29 | let denominator = ray.direction.dot(&normal); 30 | let distance = numerator / denominator; 31 | 32 | if distance > 0.0 { 33 | let point = &ray.origin + &(&ray.direction * distance); 34 | 35 | Some(Intersection::new( 36 | distance, 37 | &point + &(normal * 0.001), // offset to avoid floating-point error 38 | *normal, 39 | ray.direction, 40 | )) 41 | } else { 42 | None 43 | } 44 | } 45 | 46 | const FORWARD: Vec3 = Vec3 { x: 0.0, y: 0.0, z: -1.0 }; 47 | 48 | pub fn color_to_normal(color: &Color) -> Vec3 { 49 | Vec3 { 50 | x: color.0 * 2.0 - 1.0, 51 | y: color.1 * 2.0 - 1.0, 52 | z: (color.2 * 2.0 - 1.0) * -1.0, 53 | } 54 | } 55 | 56 | pub fn adjusted_for_normal(original_normal: &Vec3, normal_from_map: &Vec3) -> Vec3 { 57 | let transformation = Matrix::from_to_rotation(&FORWARD, &normal_from_map); 58 | original_normal.transformed(&transformation) 59 | } 60 | -------------------------------------------------------------------------------- /src/vec3.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::matrix::Matrix; 3 | 4 | #[derive(Debug, Copy, Clone, PartialEq)] 5 | pub struct Vec3 { 6 | pub x: f32, 7 | pub y: f32, 8 | pub z: f32 9 | } 10 | 11 | impl Vec3 { 12 | 13 | pub fn new() -> Self { 14 | Self { x: 0.0, y: 0.0, z: 0.0 } 15 | } 16 | 17 | pub fn from_scalar(val: f32) -> Self { 18 | Vec3 { 19 | x: val, 20 | y: val, 21 | z: val 22 | } 23 | } 24 | 25 | /// alpha is "around", beta is "away" 26 | pub fn from_angles(alpha: f32, beta: f32) -> Self { 27 | Self { 28 | x: alpha.cos() * beta.cos(), 29 | y: beta.sin(), 30 | z: alpha.sin() * beta.cos(), 31 | } 32 | } 33 | 34 | pub fn angles(&self) -> (f32,f32) { 35 | let normalized = self.normalized(); 36 | let beta = normalized.y.asin(); 37 | let alpha = (normalized.x / beta.cos()).acos(); 38 | 39 | return (alpha, beta); 40 | } 41 | 42 | pub fn len(&self) -> f32 { 43 | self.len_squared().sqrt() 44 | } 45 | 46 | pub fn len_squared(&self) -> f32 { 47 | self.x * self.x + 48 | self.y * self.y + 49 | self.z * self.z 50 | } 51 | 52 | pub fn normalize(&mut self) { 53 | let len = self.len(); 54 | self.scale(1.0 / len); 55 | } 56 | 57 | pub fn normalized(&self) -> Vec3 { 58 | let mut result = self.clone(); 59 | result.normalize(); 60 | return result; 61 | } 62 | 63 | pub fn transformed(&self, matrix: &Matrix) -> Self { 64 | Vec3 { 65 | x: matrix.get(0, 0) * self.x + 66 | matrix.get(0, 1) * self.y + 67 | matrix.get(0, 2) * self.z + 68 | matrix.get(0, 3), 69 | y: matrix.get(1, 0) * self.x + 70 | matrix.get(1, 1) * self.y + 71 | matrix.get(1, 2) * self.z + 72 | matrix.get(1, 3), 73 | z: matrix.get(2, 0) * self.x + 74 | matrix.get(2, 1) * self.y + 75 | matrix.get(2, 2) * self.z + 76 | matrix.get(2, 3) 77 | } 78 | } 79 | 80 | pub fn scale(&mut self, scale: f32) { 81 | self.x *= scale; 82 | self.y *= scale; 83 | self.z *= scale; 84 | } 85 | 86 | pub fn dot(&self, other: &Self) -> f32 { 87 | self.x * other.x + 88 | self.y * other.y + 89 | self.z * other.z 90 | } 91 | 92 | pub fn cross(&self, other: &Vec3) -> Vec3 { 93 | Vec3 { 94 | x: self.y * other.z - self.z * other.y, 95 | y: self.z * other.x - self.x * other.z, 96 | z: self.x * other.y - self.y * other.x 97 | } 98 | } 99 | 100 | pub fn angle(&self, other: &Vec3) -> f32 { 101 | ((self * other) / (self.len() * other.len())).acos() 102 | } 103 | 104 | pub fn perpendicular_to(&self, other: &Vec3) -> bool { 105 | self.dot(other) < 0.01 106 | } 107 | 108 | pub fn projected_on(&self, other: &Vec3) -> Self { 109 | other * (self.dot(other) / other.dot(other)) 110 | } 111 | 112 | pub fn rotated_around(&self, other: &Vec3, theta: f32) -> Vec3 { 113 | let cos_theta = theta.cos(); 114 | &(&(self * cos_theta) + &(&other.cross(self) * theta.sin())) + &(other * (other.dot(self) * (1.0 - cos_theta))) 115 | // Vrot = self * cos(theta) + (other x self) * sin(theta) + k * (k * v) * (1 - cos(theta)) 116 | } 117 | } 118 | 119 | 120 | // standard traits 121 | impl std::ops::Add for &Vec3 { 122 | type Output = Vec3; 123 | 124 | fn add(self, other: Self) -> Self::Output { 125 | Self::Output { 126 | x: self.x + other.x, 127 | y: self.y + other.y, 128 | z: self.z + other.z 129 | } 130 | } 131 | } 132 | impl std::ops::Add for &Vec3 { 133 | type Output = Vec3; 134 | 135 | fn add(self, diff: f32) -> Self::Output { 136 | Self::Output { 137 | x: self.x + diff, 138 | y: self.y + diff, 139 | z: self.z + diff 140 | } 141 | } 142 | } 143 | impl std::ops::Sub for &Vec3 { 144 | type Output = Vec3; 145 | 146 | fn sub(self, other: Self) -> Self::Output { 147 | Self::Output { 148 | x: self.x - other.x, 149 | y: self.y - other.y, 150 | z: self.z - other.z 151 | } 152 | } 153 | } 154 | impl std::ops::Sub for &Vec3 { 155 | type Output = Vec3; 156 | 157 | fn sub(self, diff: f32) -> Self::Output { 158 | Self::Output { 159 | x: self.x - diff, 160 | y: self.y - diff, 161 | z: self.z - diff 162 | } 163 | } 164 | } 165 | 166 | impl std::ops::Mul for &Vec3 { 167 | type Output = f32; 168 | 169 | // (dot-product) 170 | fn mul(self, other: Self) -> Self::Output { 171 | self.dot(other) 172 | } 173 | } 174 | impl std::ops::Mul for &Vec3 { 175 | type Output = Vec3; 176 | 177 | fn mul(self, scale: f32) -> Self::Output { 178 | Self::Output { 179 | x: self.x * scale, 180 | y: self.y * scale, 181 | z: self.z * scale 182 | } 183 | } 184 | } 185 | 186 | 187 | #[cfg(test)] 188 | mod tests { 189 | use crate::vec3::Vec3; 190 | 191 | // tests 192 | #[test] 193 | fn test_ops() { 194 | let vec1 = Vec3 { x: 1.0, y: 3.0, z: -5.0 }; 195 | let vec2 = Vec3 { x: 4.0, y: -2.0, z: -1.0 }; 196 | 197 | assert_eq!(&vec1 + &vec2, Vec3{ x: 5.0, y: 1.0, z: -6.0 }); 198 | assert_eq!(&vec1 * &vec2, 3.0); 199 | } 200 | } -------------------------------------------------------------------------------- /texture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brundonsmith/raytracer/63e1cd2d7636c5e7c95f65817aa7325748a23405/texture.jpg -------------------------------------------------------------------------------- /tree.mtl: -------------------------------------------------------------------------------- 1 | newmtl mat10 2 | Kd 0.30 0.69 0.31 3 | 4 | newmtl mat20 5 | Kd 0.47 0.33 0.28 6 | 7 | newmtl mat21 8 | Kd 1.00 1.00 1.00 9 | 10 | newmtl mat25 11 | Ka 1.00 0.65 0.67 12 | Kd 0.92 0.95 0.94 13 | Ks 1 1 1 14 | illum 9 15 | Ns 300 16 | d 0.4 17 | Ni 1.5 18 | 19 | newmtl mat12 20 | Kd 1.00 0.92 0.23 21 | 22 | newmtl mat8 23 | Kd 0.96 0.26 0.21 24 | 25 | newmtl mat9 26 | Kd 0.55 0.76 0.29 27 | 28 | newmtl mat3 29 | Kd 0.50 0.87 0.92 30 | 31 | newmtl mat13 32 | Kd 1.00 0.60 0.00 33 | 34 | newmtl mat5 35 | Kd 0.01 0.61 0.90 36 | 37 | newmtl mat19 38 | Kd 0.87 0.60 0.27 39 | 40 | newmtl mat17 41 | Kd 0.27 0.35 0.39 42 | 43 | --------------------------------------------------------------------------------