├── .gitignore ├── COPYING ├── Cargo.lock ├── Cargo.toml ├── README.md ├── clippy.toml ├── examples ├── common │ ├── mod.rs │ ├── ray_tracing_in_one_weekend.rs │ ├── ray_tracing_next_week.rs │ └── scene.rs ├── earth-map.png ├── rtnw_10.rs ├── rtnw_2_5.rs ├── rtnw_4_3.rs ├── rtnw_4_4.rs ├── rtnw_5_1.rs ├── rtnw_5_2.rs ├── rtnw_5_3.rs ├── rtnw_5_4.rs ├── rtnw_5_5.rs ├── rtnw_5_6.rs ├── rtnw_5_7.rs ├── rtnw_6_2.rs ├── rtnw_7_4.rs ├── rtnw_7_4_2.rs ├── rtnw_7_6.rs ├── rtnw_8_0.rs ├── rtnw_8_1.rs ├── rtnw_8_2.rs ├── rtnw_9_2.rs ├── rtow_10_3.rs ├── rtow_10_5.rs ├── rtow_11_1.rs ├── rtow_11_2.rs ├── rtow_11_2_2.rs ├── rtow_12_2.rs ├── rtow_13_1.rs ├── rtow_2_2.rs ├── rtow_4_2.rs ├── rtow_5_2.rs ├── rtow_6_1.rs ├── rtow_6_7.rs ├── rtow_7_2.rs ├── rtow_8_2.rs ├── rtow_8_3.rs ├── rtow_8_5.rs ├── rtow_8_6.rs ├── rtow_9_5.rs ├── rtow_9_6.rs ├── rtrl_2_1.rs ├── rtrl_2_2.rs ├── rtrl_2_3.rs ├── rtrl_3_1.rs └── rtrl_3_3.rs ├── rustfmt.toml └── src ├── camera.rs ├── hittable ├── collection │ ├── bvh.rs │ ├── list.rs │ ├── mod.rs │ └── world.rs ├── geometry │ ├── carton.rs │ ├── mod.rs │ ├── rect.rs │ └── sphere.rs ├── hit.rs ├── medium │ ├── constant.rs │ └── mod.rs ├── mod.rs └── transform │ ├── mod.rs │ ├── rotation.rs │ └── translation.rs ├── internal ├── mod.rs └── rayon_seq_iter.rs ├── lib.rs ├── material ├── dielectric.rs ├── isotropic.rs ├── lambertian.rs ├── light.rs ├── metal.rs └── mod.rs ├── painter.rs ├── prelude ├── aabb.rs ├── color.rs ├── mod.rs ├── random.rs ├── ray.rs └── vec3.rs └── texture ├── checker.rs ├── image.rs ├── mod.rs └── noise.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.idea 3 | /.vscode 4 | /*.ppm 5 | **/.DS_Store 6 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | \"This License\" refers to version 3 of the GNU General Public License. 76 | 77 | \"Copyright\" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | \"The Program\" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as \"you\". \"Licensees\" and 82 | \"recipients\" may be individuals or organizations. 83 | 84 | To \"modify\" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a \"modified version\" of the 87 | earlier work or a work \"based on\" the earlier work. 88 | 89 | A \"covered work\" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To \"propagate\" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To \"convey\" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays \"Appropriate Legal Notices\ 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The \"source code\" for a work means the preferred form of the work 115 | for making modifications to it. \"Object code\" means any non-source 116 | form of a work. 117 | 118 | A \"Standard Interface\" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The \"System Libraries\" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | \"Major Component\", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The \"Corresponding Source\" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | \"keep intact all notices\". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | \"aggregate\" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A \"User Product\" is either (1) a \"consumer product\", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, \"normally used\" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | \"Installation Information\" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | \"Additional permissions\" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered \"further 389 | restrictions\" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An \"entity transaction\" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A \"contributor\" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's \"contributor version\". 476 | 477 | A contributor's \"essential patent claims\" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, \"control\" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a \"patent license\" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To \"grant\" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. \"Knowingly relying\" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is \"discriminatory\" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License \"or any later version\" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the \"copyright\" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an \"about box\". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a \"copyright disclaimer\" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | 676 | -------------------------------------------------------------------------------- /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.2.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 8 | 9 | [[package]] 10 | name = "aho-corasick" 11 | version = "0.7.15" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" 14 | dependencies = [ 15 | "memchr", 16 | ] 17 | 18 | [[package]] 19 | name = "atty" 20 | version = "0.2.14" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 23 | dependencies = [ 24 | "hermit-abi", 25 | "libc", 26 | "winapi", 27 | ] 28 | 29 | [[package]] 30 | name = "autocfg" 31 | version = "1.0.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 34 | 35 | [[package]] 36 | name = "bitflags" 37 | version = "1.2.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 40 | 41 | [[package]] 42 | name = "bytemuck" 43 | version = "1.5.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58" 46 | 47 | [[package]] 48 | name = "byteorder" 49 | version = "1.4.2" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" 52 | 53 | [[package]] 54 | name = "cfg-if" 55 | version = "1.0.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 58 | 59 | [[package]] 60 | name = "color_quant" 61 | version = "1.1.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 64 | 65 | [[package]] 66 | name = "crc32fast" 67 | version = "1.2.1" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 70 | dependencies = [ 71 | "cfg-if", 72 | ] 73 | 74 | [[package]] 75 | name = "crossbeam-channel" 76 | version = "0.5.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" 79 | dependencies = [ 80 | "cfg-if", 81 | "crossbeam-utils", 82 | ] 83 | 84 | [[package]] 85 | name = "crossbeam-deque" 86 | version = "0.8.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" 89 | dependencies = [ 90 | "cfg-if", 91 | "crossbeam-epoch", 92 | "crossbeam-utils", 93 | ] 94 | 95 | [[package]] 96 | name = "crossbeam-epoch" 97 | version = "0.9.3" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" 100 | dependencies = [ 101 | "cfg-if", 102 | "crossbeam-utils", 103 | "lazy_static", 104 | "memoffset", 105 | "scopeguard", 106 | ] 107 | 108 | [[package]] 109 | name = "crossbeam-utils" 110 | version = "0.8.3" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" 113 | dependencies = [ 114 | "autocfg", 115 | "cfg-if", 116 | "lazy_static", 117 | ] 118 | 119 | [[package]] 120 | name = "deflate" 121 | version = "0.8.6" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" 124 | dependencies = [ 125 | "adler32", 126 | "byteorder", 127 | ] 128 | 129 | [[package]] 130 | name = "either" 131 | version = "1.6.1" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 134 | 135 | [[package]] 136 | name = "env_logger" 137 | version = "0.8.3" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" 140 | dependencies = [ 141 | "atty", 142 | "humantime", 143 | "log", 144 | "regex", 145 | "termcolor", 146 | ] 147 | 148 | [[package]] 149 | name = "getrandom" 150 | version = "0.2.2" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 153 | dependencies = [ 154 | "cfg-if", 155 | "libc", 156 | "wasi", 157 | ] 158 | 159 | [[package]] 160 | name = "hermit-abi" 161 | version = "0.1.18" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 164 | dependencies = [ 165 | "libc", 166 | ] 167 | 168 | [[package]] 169 | name = "humantime" 170 | version = "2.1.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 173 | 174 | [[package]] 175 | name = "image" 176 | version = "0.23.14" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" 179 | dependencies = [ 180 | "bytemuck", 181 | "byteorder", 182 | "color_quant", 183 | "jpeg-decoder", 184 | "num-iter", 185 | "num-rational", 186 | "num-traits", 187 | "png", 188 | ] 189 | 190 | [[package]] 191 | name = "jpeg-decoder" 192 | version = "0.1.22" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" 195 | dependencies = [ 196 | "rayon", 197 | ] 198 | 199 | [[package]] 200 | name = "lazy_static" 201 | version = "1.4.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 204 | 205 | [[package]] 206 | name = "libc" 207 | version = "0.2.88" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a" 210 | 211 | [[package]] 212 | name = "log" 213 | version = "0.4.14" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 216 | dependencies = [ 217 | "cfg-if", 218 | ] 219 | 220 | [[package]] 221 | name = "memchr" 222 | version = "2.3.4" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 225 | 226 | [[package]] 227 | name = "memoffset" 228 | version = "0.6.1" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" 231 | dependencies = [ 232 | "autocfg", 233 | ] 234 | 235 | [[package]] 236 | name = "miniz_oxide" 237 | version = "0.3.7" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" 240 | dependencies = [ 241 | "adler32", 242 | ] 243 | 244 | [[package]] 245 | name = "num-integer" 246 | version = "0.1.44" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 249 | dependencies = [ 250 | "autocfg", 251 | "num-traits", 252 | ] 253 | 254 | [[package]] 255 | name = "num-iter" 256 | version = "0.1.42" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" 259 | dependencies = [ 260 | "autocfg", 261 | "num-integer", 262 | "num-traits", 263 | ] 264 | 265 | [[package]] 266 | name = "num-rational" 267 | version = "0.3.2" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" 270 | dependencies = [ 271 | "autocfg", 272 | "num-integer", 273 | "num-traits", 274 | ] 275 | 276 | [[package]] 277 | name = "num-traits" 278 | version = "0.2.14" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 281 | dependencies = [ 282 | "autocfg", 283 | ] 284 | 285 | [[package]] 286 | name = "num_cpus" 287 | version = "1.13.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 290 | dependencies = [ 291 | "hermit-abi", 292 | "libc", 293 | ] 294 | 295 | [[package]] 296 | name = "once_cell" 297 | version = "1.7.2" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" 300 | 301 | [[package]] 302 | name = "png" 303 | version = "0.16.8" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" 306 | dependencies = [ 307 | "bitflags", 308 | "crc32fast", 309 | "deflate", 310 | "miniz_oxide", 311 | ] 312 | 313 | [[package]] 314 | name = "ppv-lite86" 315 | version = "0.2.10" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 318 | 319 | [[package]] 320 | name = "rand" 321 | version = "0.8.3" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" 324 | dependencies = [ 325 | "libc", 326 | "rand_chacha", 327 | "rand_core", 328 | "rand_hc", 329 | ] 330 | 331 | [[package]] 332 | name = "rand_chacha" 333 | version = "0.3.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" 336 | dependencies = [ 337 | "ppv-lite86", 338 | "rand_core", 339 | ] 340 | 341 | [[package]] 342 | name = "rand_core" 343 | version = "0.6.2" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 346 | dependencies = [ 347 | "getrandom", 348 | ] 349 | 350 | [[package]] 351 | name = "rand_hc" 352 | version = "0.3.0" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" 355 | dependencies = [ 356 | "rand_core", 357 | ] 358 | 359 | [[package]] 360 | name = "rayon" 361 | version = "1.5.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" 364 | dependencies = [ 365 | "autocfg", 366 | "crossbeam-deque", 367 | "either", 368 | "rayon-core", 369 | ] 370 | 371 | [[package]] 372 | name = "rayon-core" 373 | version = "1.9.0" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" 376 | dependencies = [ 377 | "crossbeam-channel", 378 | "crossbeam-deque", 379 | "crossbeam-utils", 380 | "lazy_static", 381 | "num_cpus", 382 | ] 383 | 384 | [[package]] 385 | name = "regex" 386 | version = "1.4.3" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" 389 | dependencies = [ 390 | "aho-corasick", 391 | "memchr", 392 | "regex-syntax", 393 | "thread_local", 394 | ] 395 | 396 | [[package]] 397 | name = "regex-syntax" 398 | version = "0.6.22" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" 401 | 402 | [[package]] 403 | name = "remda" 404 | version = "0.1.0" 405 | dependencies = [ 406 | "env_logger", 407 | "image", 408 | "log", 409 | "num_cpus", 410 | "once_cell", 411 | "rand", 412 | "rayon", 413 | ] 414 | 415 | [[package]] 416 | name = "scopeguard" 417 | version = "1.1.0" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 420 | 421 | [[package]] 422 | name = "termcolor" 423 | version = "1.1.2" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 426 | dependencies = [ 427 | "winapi-util", 428 | ] 429 | 430 | [[package]] 431 | name = "thread_local" 432 | version = "1.1.3" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" 435 | dependencies = [ 436 | "once_cell", 437 | ] 438 | 439 | [[package]] 440 | name = "wasi" 441 | version = "0.10.2+wasi-snapshot-preview1" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 444 | 445 | [[package]] 446 | name = "winapi" 447 | version = "0.3.9" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 450 | dependencies = [ 451 | "winapi-i686-pc-windows-gnu", 452 | "winapi-x86_64-pc-windows-gnu", 453 | ] 454 | 455 | [[package]] 456 | name = "winapi-i686-pc-windows-gnu" 457 | version = "0.4.0" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 460 | 461 | [[package]] 462 | name = "winapi-util" 463 | version = "0.1.5" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 466 | dependencies = [ 467 | "winapi", 468 | ] 469 | 470 | [[package]] 471 | name = "winapi-x86_64-pc-windows-gnu" 472 | version = "0.4.0" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 475 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "remda" 3 | version = "0.1.0" 4 | authors = ["7sDream "] 5 | edition = "2018" 6 | description = "Yet another simple and pure software ray tracing renderer" 7 | homepage = "https://github.com/7sDream/remda" 8 | repository = "https://github.com/7sDream/remda" 9 | license-file = "COPYING" 10 | publish = false 11 | 12 | [lib] 13 | name = "remda" 14 | path = "src/lib.rs" 15 | 16 | [dependencies] 17 | env_logger = "0.8" 18 | log = "0.4" 19 | rand = "0.8" 20 | rayon = "1.5" 21 | num_cpus = "1.13" 22 | once_cell = "1.7" 23 | 24 | [dependencies.image] 25 | version = "0.23" 26 | default-features = false 27 | features = ["jpeg_rayon", "png", "bmp"] 28 | 29 | [profile.release] 30 | debug = true 31 | lto = "fat" 32 | codegen-units = 1 33 | panic = "abort" 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Remda 2 | 3 | Learn [*Ray Tracing in One Weekend* series][book-series] using Rust. 4 | 5 | Yet another naive and pure software ray tracing renderer. 6 | 7 | ## Render Result Examples 8 | 9 | ![rendered image of RTOW's balls scene][rtow-balls-scene-render-result] 10 | *example rtow_13_1, 512 sample, 8 depth, 1920x1080, 8m30s* 11 | 12 | ![rendered image of RTNW's balls motion blur scene][rtnw-balls-scene-motion-blur-render-result] 13 | *example rtnw_4_3, 1024 sample, 8 depth, 1920x1080, 22min* 14 | 15 | ![rendered image of RTNW's ball with noise texture scene][rtnw-ball-noise-texture-scene-render-result] 16 | *example rtnw_7_4_2, 4096 sample, 8 depth, 1500x900, 15min* 17 | 18 | ![rendered image of RTNW's Cornell scene][rtnw-cornell-rotated-scene-render-result] 19 | *example rtnw_8_2, 10240 sample, 10 depth, 1000x1000, 1h20m* 20 | 21 | ![rendered image of RTNW's Cornel smoke scene][rtnw-cornell-smoke-scene-render-result] 22 | *example rtnw_9_2, 10240 sample, 10 depth, 1000x1000, 1h30m* 23 | 24 | ![rendered image of RTNW's final scene][rtnw-final-scene-render-result] 25 | *example rtnw_10, 10240 sample, 10 depth, 1000x1000, 2h30m* 26 | 27 | ## Current Progress 28 | 29 | - [x] [*Ray Tracing in One Weekend*][book-1] 30 | - [x] Basic types, Vec, Color, Ray, etc 31 | - [x] Background/Sky 32 | - [x] Sphere 33 | - [x] Lambertian Material 34 | - [x] Metal Material with Different Fuzz 35 | - [x] Glass Material with Different Refractive 36 | - [x] Pinhole Camera 37 | - [x] Camera Defocus/Depth Field 38 | - [x] (Extra) Parallelism (by using [rayon][rayon-crates-io]) 39 | - [x] [*Ray Tracing: The Next Week*][book-2] 40 | - [x] Motion Blur 41 | - [x] BVH(Bounding Volume Hierarchies) 42 | - [x] Solid Textures 43 | - [x] Perlin Noise 44 | - [x] Image Textures 45 | - [x] Rectangles 46 | - [x] Lights 47 | - [x] Cornell Box 48 | - [x] Box 49 | - [x] Instance Translation and Rotation 50 | - [x] Volumes/Participating Media 51 | - [ ] [*Ray Tracing: The Rest of Your Life*][book-3] not started yet 52 | 53 | ## Run 54 | 55 | Remda is a library crate, but you can run built-in examples(from the book series) to try it. 56 | 57 | Use `cargo run --example` to get examples list, then choose one to run. 58 | 59 | For example, to get final scene in section 13.1 of *Ray Tracing in One Weekend*, run 60 | 61 | ```bash 62 | cargo run --example rtow_13_1 --release 63 | ``` 64 | 65 | Wait about 1s(according to your machine's CPU performance), you will get a `rtow_13_1.ppm` in current dir, that's your result. 66 | 67 | If you want a bigger and clear image, adjust `height()`, `depth` and `samples()` parameter in example source file and re-run. 68 | 69 | You can also try other examples if you want. 70 | 71 | PS: Pure software ray tracing takes a long time to render, be patient. 72 | 73 | ## LICENSE 74 | 75 | GPLv3 76 | 77 | Except: 78 | 79 | - `example/earth-map.png`, download from [NASA][earth-map-source], fall in public domain. 80 | 81 | [book-series]: https://raytracing.github.io/ 82 | [book-1]: https://raytracing.github.io/books/RayTracingInOneWeekend.html 83 | [book-2]: https://raytracing.github.io/books/RayTracingTheNextWeek.html 84 | [book-3]: https://raytracing.github.io/books/RayTracingTheRestOfYourLife.html 85 | [rtow-balls-scene-render-result]: https://rikka.7sdre.am/files/a952c7ca-af57-46a6-959f-237702333ab6.png 86 | [rtnw-balls-scene-motion-blur-render-result]: https://rikka.7sdre.am/files/a0f8d5bd-9bc5-4361-b1dc-4adabd9c9949.png 87 | [rtnw-ball-noise-texture-scene-render-result]: https://rikka.7sdre.am/files/e5b459c6-aa8e-47aa-ab20-33d430ba6b2f.png 88 | [rtnw-cornell-rotated-scene-render-result]: https://rikka.7sdre.am/files/1721b196-b746-4e6d-a4d0-f9c7c2e75c41.png 89 | [rtnw-cornell-smoke-scene-render-result]: https://rikka.7sdre.am/files/545972fd-d10d-4345-9e8c-3ba16fb50524.png 90 | [rtnw-final-scene-render-result]: https://rikka.7sdre.am/files/3e1e1849-54bf-4a7b-9e09-b2cc25b5cf6f.png 91 | [rayon-crates-io]: https://crates.io/crates/rayon 92 | [earth-map-source]: http://visibleearth.nasa.gov/view.php?id=57752 93 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | cognitive-complexity-threshold = 25 2 | literal-representation-threshold = 0 3 | enum-variant-name-threshold = 0 4 | enum-variant-size-threshold = 32 5 | single-char-binding-names-threshold = 6 6 | too-many-arguments-threshold = 5 7 | too-many-lines-threshold = 50 8 | trivial-copy-size-limit = 8 9 | verbose-bit-mask-threshold = 1 10 | -------------------------------------------------------------------------------- /examples/common/mod.rs: -------------------------------------------------------------------------------- 1 | mod scene; 2 | 3 | pub mod ray_tracing_in_one_weekend; 4 | pub mod ray_tracing_next_week; 5 | 6 | pub fn init_log(level: &'static str) { 7 | let env = env_logger::Env::default().default_filter_or(level); 8 | env_logger::init_from_env(env); 9 | } 10 | -------------------------------------------------------------------------------- /examples/common/ray_tracing_in_one_weekend.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::scene, 3 | remda::{camera::Camera, hittable::collection::HittableList}, 4 | }; 5 | 6 | #[must_use] 7 | fn final_world(seed: Option) -> HittableList { 8 | scene::balls_scene(seed, false, false) 9 | } 10 | 11 | #[must_use] 12 | fn final_camera() -> Camera { 13 | scene::balls_scene_camera(false) 14 | } 15 | 16 | #[must_use] 17 | pub fn final_scene(seed: Option) -> (Camera, HittableList) { 18 | (final_camera(), final_world(seed)) 19 | } 20 | -------------------------------------------------------------------------------- /examples/common/ray_tracing_next_week.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::scene, 3 | remda::{camera::Camera, hittable::collection::HittableList}, 4 | }; 5 | 6 | pub use scene::all_feature_scene; 7 | 8 | #[must_use] 9 | fn motion_blur_world(seed: Option, checker: bool) -> HittableList { 10 | scene::balls_scene(seed, true, checker) 11 | } 12 | 13 | #[must_use] 14 | fn motion_blur_camera() -> Camera { 15 | scene::balls_scene_camera(true) 16 | } 17 | 18 | #[must_use] 19 | pub fn motion_blur(seed: Option, checker: bool) -> (Camera, HittableList) { 20 | (motion_blur_camera(), motion_blur_world(seed, checker)) 21 | } 22 | 23 | #[must_use] 24 | pub fn empty_cornell_box() -> (Camera, HittableList) { 25 | scene::cornell_box_scene(false, false, false) 26 | } 27 | 28 | #[must_use] 29 | pub fn cornell_box_no_rotation() -> (Camera, HittableList) { 30 | scene::cornell_box_scene(true, false, false) 31 | } 32 | 33 | #[must_use] 34 | pub fn cornell_box() -> (Camera, HittableList) { 35 | scene::cornell_box_scene(true, true, false) 36 | } 37 | 38 | #[must_use] 39 | pub fn cornell_box_smoke() -> (Camera, HittableList) { 40 | scene::cornell_box_scene(true, true, true) 41 | } 42 | -------------------------------------------------------------------------------- /examples/common/scene.rs: -------------------------------------------------------------------------------- 1 | use { 2 | remda::{ 3 | camera::{Camera, CameraBuilder}, 4 | hittable::{ 5 | collection::{HittableList, BVH}, 6 | medium::ConstantMedium, 7 | transform::{AARotation, ByYAxis, Translation}, 8 | AARect, AARectMetrics, Carton, Sphere, 9 | }, 10 | material::{Dielectric, DiffuseLight, Glass, Lambertian, Metal}, 11 | prelude::*, 12 | texture::{Checker, Image, Perlin, SmoothType}, 13 | }, 14 | std::sync::Arc, 15 | }; 16 | 17 | fn add_small_balls(world: &mut HittableList, rng: &mut SeedRandom, need_speed: bool) { 18 | let small_ball_radius = 0.2; 19 | let mut avoid = Point3::new(0.0, 0.2, 0.0); 20 | for a in -11..11 { 21 | for b in -11..11 { 22 | let center = Point3::new( 23 | 0.9_f64.mul_add(rng.normal(), f64::from(a)), 24 | 0.2, 25 | 0.9_f64.mul_add(rng.normal(), f64::from(b)), 26 | ); 27 | 28 | avoid.x = center.x; 29 | 30 | if !((0.0..0.9).contains(¢er.x.abs()) || (3.1..4.9).contains(¢er.x.abs())) 31 | || (¢er - &avoid).length() >= 0.9 32 | { 33 | let mat = rng.normal(); 34 | if mat < 0.8 { 35 | let color = Color::new(rng.normal(), rng.normal(), rng.normal()); 36 | let material = Lambertian::new(color); 37 | let mut sphere = Sphere::new(center, small_ball_radius, material); 38 | if need_speed { 39 | sphere = sphere.with_speed(Vec3::new(0.0, Random::range(0.0..0.5), 0.0)); 40 | } 41 | world.add(sphere); 42 | } else if mat < 0.95 { 43 | let color = Color::new( 44 | rng.range(0.5..1.0), 45 | rng.range(0.5..1.0), 46 | rng.range(0.5..1.0), 47 | ); 48 | let fuzz = rng.range(0.0..0.5); 49 | let material = Metal::new(color).fuzz(fuzz); 50 | world.add(Sphere::new(center, small_ball_radius, material)); 51 | } else { 52 | world.add(Sphere::new( 53 | center, 54 | small_ball_radius, 55 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5).reflect_curve(Glass {}), 56 | )); 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | fn add_big_balls(world: &mut HittableList) { 64 | world.add(Sphere::new( 65 | Point3::new(0.0, 1.0, 0.0), 66 | 1.0, 67 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5).reflect_curve(Glass {}), 68 | )); 69 | 70 | world.add(Sphere::new( 71 | Point3::new(-4.0, 1.0, 0.0), 72 | 1.0, 73 | Lambertian::new(Color::new(0.4, 0.2, 0.1)), 74 | )); 75 | 76 | world.add(Sphere::new( 77 | Point3::new(4.0, 1.0, 0.0), 78 | 1.0, 79 | Metal::new(Color::new(0.7, 0.6, 0.5)), 80 | )); 81 | } 82 | 83 | #[must_use] 84 | pub fn balls_scene(seed: Option, need_speed: bool, checker: bool) -> HittableList { 85 | let mut list = HittableList::default(); 86 | 87 | if checker { 88 | list.add(Sphere::new( 89 | Point3::new(0.0, -1000.0, 0.0), 90 | 1000.0, 91 | Lambertian::new(Checker::new( 92 | Color::new(0.2, 0.3, 0.1), 93 | Color::new(0.9, 0.9, 0.9), 94 | )), 95 | )); 96 | } else { 97 | list.add(Sphere::new( 98 | Point3::new(0.0, -1000.0, 0.0), 99 | 1000.0, 100 | Lambertian::new(Color::new(0.5, 0.5, 0.5)), 101 | )); 102 | }; 103 | 104 | // Ground 105 | 106 | let mut rng = if let Some(seed) = seed { 107 | SeedRandom::new(seed) 108 | } else { 109 | SeedRandom::random() 110 | }; 111 | 112 | add_small_balls(&mut list, &mut rng, need_speed); 113 | add_big_balls(&mut list); 114 | 115 | list 116 | } 117 | 118 | #[must_use] 119 | pub fn balls_scene_camera(need_shutter_speed: bool) -> Camera { 120 | let mut builder = CameraBuilder::default() 121 | .look_from(Point3::new(13.0, 2.0, 3.0)) 122 | .look_at(Point3::new(0.0, 0.0, 0.0)) 123 | .fov(20.0) 124 | .aperture(0.1) 125 | .focus(10.0); 126 | 127 | if need_shutter_speed { 128 | builder = builder.shutter_speed(1.0); 129 | } 130 | 131 | builder.build() 132 | } 133 | 134 | #[must_use] 135 | pub fn cornell_box_scene( 136 | carton: bool, carton_rotation: bool, smoke: bool, 137 | ) -> (Camera, HittableList) { 138 | let red = Lambertian::new(Color::new(0.65, 0.05, 0.05)); 139 | let green = Lambertian::new(Color::new(0.12, 0.45, 0.15)); 140 | let white = Lambertian::new(Color::new(0.73, 0.73, 0.73)); 141 | let light = 142 | DiffuseLight::new(Color::new(1.0, 1.0, 1.0)).multiplier(if smoke { 7.0 } else { 15.0 }); 143 | 144 | let mut objects = HittableList::default(); 145 | 146 | objects 147 | .add(AARect::new_yz( 148 | AARectMetrics::new(555.0, (0.0, 555.0), (0.0, 555.0)), 149 | green, 150 | )) 151 | .add(AARect::new_yz( 152 | AARectMetrics::new(0.0, (0.0, 555.0), (0.0, 555.0)), 153 | red, 154 | )) 155 | .add(AARect::new_xz( 156 | AARectMetrics::new(0.0, (0.0, 555.0), (0.0, 555.0)), 157 | white.clone(), 158 | )) 159 | .add(AARect::new_xz( 160 | AARectMetrics::new(555.0, (0.0, 555.0), (0.0, 555.0)), 161 | white.clone(), 162 | )) 163 | .add(AARect::new_xy( 164 | AARectMetrics::new(555.0, (0.0, 555.0), (0.0, 555.0)), 165 | white.clone(), 166 | )); 167 | 168 | if smoke { 169 | objects.add(AARect::new_xz( 170 | AARectMetrics::new(554.0, (113.0, 443.0), (127.0, 432.0)), 171 | light, 172 | )); 173 | } else { 174 | objects.add(AARect::new_xz( 175 | AARectMetrics::new(554.0, (213.0, 343.0), (227.0, 332.0)), 176 | light, 177 | )); 178 | } 179 | 180 | if carton { 181 | if carton_rotation { 182 | let box1 = Translation::new( 183 | AARotation::::new( 184 | Carton::new( 185 | Point3::new(0.0, 0.0, 0.0), 186 | Point3::new(165.0, 165.0, 165.0), 187 | white.clone(), 188 | ), 189 | -18.0, 190 | ), 191 | Vec3::new(130.0, 0.0, 65.0), 192 | ); 193 | let box2 = Translation::new( 194 | AARotation::::new( 195 | Carton::new( 196 | Point3::new(0.0, 0.0, 0.0), 197 | Point3::new(165.0, 330.0, 165.0), 198 | white, 199 | ), 200 | 15.0, 201 | ), 202 | Vec3::new(265.0, 0.0, 295.0), 203 | ); 204 | if smoke { 205 | let box1 = ConstantMedium::new(box1, Color::new(1.0, 1.0, 1.0), 0.01); 206 | let box2 = ConstantMedium::new(box2, Color::new(0.0, 0.0, 0.0), 0.01); 207 | objects.add(box1).add(box2); 208 | } else { 209 | objects.add(box1).add(box2); 210 | } 211 | } else { 212 | let box1 = Carton::new( 213 | Point3::new(130.0, 0.0, 65.0), 214 | Point3::new(295.0, 165.0, 230.0), 215 | white.clone(), 216 | ); 217 | let box2 = Carton::new( 218 | Point3::new(265.0, 0.0, 295.0), 219 | Point3::new(430.0, 330.0, 460.0), 220 | white, 221 | ); 222 | if smoke { 223 | let box1 = ConstantMedium::new(box1, Color::new(1.0, 1.0, 1.0), 0.01); 224 | let box2 = ConstantMedium::new(box2, Color::new(0.0, 0.0, 0.0), 0.01); 225 | objects.add(box1).add(box2); 226 | } else { 227 | objects.add(box1).add(box2); 228 | } 229 | } 230 | } 231 | 232 | let camera = CameraBuilder::default() 233 | .aspect_ratio(1.0) 234 | .fov(40.0) 235 | .look_from(Point3::new(278.0, 278.0, -800.0)) 236 | .look_at(Point3::new(278.0, 278.0, 0.0)) 237 | .build(); 238 | 239 | (camera, objects) 240 | } 241 | 242 | pub fn all_feature_scene(seed: Option) -> (Camera, HittableList) { 243 | let time_limit = 0.0..1.0; 244 | let boxes_per_side: usize = 20; 245 | let mut rng = seed.map(SeedRandom::new).unwrap_or_default(); 246 | 247 | let mut boxes1 = HittableList::default(); 248 | let ground = Arc::new(Lambertian::new(Color::new(0.48, 0.83, 0.53))); 249 | for i in 0..boxes_per_side { 250 | for j in 0..boxes_per_side { 251 | let w = 100.0; 252 | let x0 = -1000.0 + i as f64 * w; 253 | let z0 = -1000.0 + j as f64 * w; 254 | let y0 = 0.0; 255 | let x1 = x0 + w; 256 | let y1 = rng.range(1.0..100.0); 257 | let z1 = z0 + w; 258 | boxes1.add(Carton::new( 259 | Point3::new(x0, y0, z0), 260 | Point3::new(x1, y1, z1), 261 | Arc::clone(&ground), 262 | )); 263 | } 264 | } 265 | 266 | let mut objects = HittableList::default(); 267 | objects.add(BVH::new(boxes1, time_limit.clone())); 268 | 269 | let light = DiffuseLight::new(Color::new(1.0, 1.0, 1.0)).multiplier(7.0); 270 | objects.add(AARect::new_xz( 271 | AARectMetrics::new(554.0, (123.0, 423.0), (147.0, 412.0)), 272 | light, 273 | )); 274 | 275 | let moving_sphere = Sphere::new( 276 | Point3::new(400.0, 400.0, 200.0), 277 | 50.0, 278 | Lambertian::new(Color::new(0.7, 0.3, 0.1)), 279 | ) 280 | .with_speed(Vec3::new(30.0, 0.0, 0.0)); 281 | objects.add(moving_sphere); 282 | 283 | let glass_sphere = Sphere::new( 284 | Point3::new(260.0, 150.0, 45.0), 285 | 50.0, 286 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5).reflect_curve(Glass {}), 287 | ); 288 | objects.add(glass_sphere); 289 | 290 | let metal_sphere = Sphere::new( 291 | Point3::new(0.0, 150.0, 145.0), 292 | 50.0, 293 | Metal::new(Color::new(0.8, 0.8, 0.9)).fuzz(1.0), 294 | ); 295 | objects.add(metal_sphere); 296 | 297 | let boundary = Sphere::new( 298 | Point3::new(360.0, 170.0, 145.0), 299 | 70.0, 300 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5).reflect_curve(Glass {}), 301 | ); 302 | objects.add(boundary); 303 | objects.add(ConstantMedium::new( 304 | Sphere::new( 305 | Point3::new(360.0, 170.0, 145.0), 306 | 70.0, 307 | Lambertian::new(Color::new(1.0, 1.0, 1.0)), 308 | ), 309 | Color::new(0.2, 0.4, 0.9), 310 | 0.2, 311 | )); 312 | 313 | objects.add(ConstantMedium::new( 314 | Sphere::new( 315 | Point3::new(0.0, 0.0, 0.0), 316 | 5000.0, 317 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5).reflect_curve(Glass {}), 318 | ), 319 | Color::new(1.0, 1.0, 1.0), 320 | 0.0001, 321 | )); 322 | 323 | objects.add(Sphere::new( 324 | Point3::new(400.0, 200.0, 400.0), 325 | 100.0, 326 | Lambertian::new(Image::new("examples/earth-map.png").unwrap()), 327 | )); 328 | 329 | objects.add(Sphere::new( 330 | Point3::new(220.0, 280.0, 300.0), 331 | 80.0, 332 | Lambertian::new( 333 | Perlin::new(256, true) 334 | .scale(0.1) 335 | .smooth(SmoothType::HermitianCubic), 336 | ), 337 | )); 338 | 339 | let white = Arc::new(Lambertian::new(Color::new(0.73, 0.73, 0.73))); 340 | let mut boxes2 = HittableList::default(); 341 | for _ in 0..1000_usize { 342 | boxes2.add(Sphere::new( 343 | Point3::new( 344 | rng.range(0.0..165.0), 345 | rng.range(0.0..165.0), 346 | rng.range(0.0..165.0), 347 | ), 348 | 10.0, 349 | Arc::clone(&white), 350 | )); 351 | } 352 | 353 | objects.add(Translation::new( 354 | AARotation::::new(BVH::new(boxes2, time_limit), 15.0), 355 | Vec3::new(-100.0, 270.0, 395.0), 356 | )); 357 | 358 | let camera = CameraBuilder::default() 359 | .look_from(Point3::new(478.0, 278.0, -600.0)) 360 | .look_at(Point3::new(278.0, 278.0, 0.0)) 361 | .aspect_ratio(1.0) 362 | .fov(40.0) 363 | .shutter_speed(1.0) 364 | .build(); 365 | 366 | (camera, objects) 367 | } 368 | -------------------------------------------------------------------------------- /examples/earth-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7sDream/remda/a80e94528ddd70443b7faadf0853e9e8d59c71c6/examples/earth-map.png -------------------------------------------------------------------------------- /examples/rtnw_10.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | mod common; 3 | 4 | use remda::prelude::*; 5 | 6 | fn main() { 7 | common::init_log("info"); 8 | 9 | let (camera, world) = common::ray_tracing_next_week::all_feature_scene(Some(777)); 10 | 11 | camera 12 | .take_photo(world) 13 | .background(|_| Color::default()) 14 | .height(300) 15 | .depth(50) 16 | .samples(1000) 17 | .shot(Some("rtnw_10.ppm")) 18 | .unwrap(); 19 | } 20 | -------------------------------------------------------------------------------- /examples/rtnw_2_5.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | mod common; 3 | 4 | fn main() { 5 | common::init_log("info"); 6 | 7 | // Change `77` to another number to generate different scene 8 | // Or use `None` to use random seed 9 | let (camera, world) = common::ray_tracing_next_week::motion_blur(Some(77), false); 10 | 11 | camera 12 | .take_photo(world) 13 | .height(100) 14 | .samples(100) 15 | .shot(Some("rtnw_2_5.ppm")) 16 | .unwrap(); 17 | } 18 | -------------------------------------------------------------------------------- /examples/rtnw_4_3.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | mod common; 3 | 4 | fn main() { 5 | common::init_log("info"); 6 | 7 | // Change `77` to another number to generate different scene 8 | // Or use `None` to use random seed 9 | let (camera, world) = common::ray_tracing_next_week::motion_blur(Some(77), true); 10 | 11 | camera 12 | .take_photo(world) 13 | .height(500) 14 | .samples(128) 15 | .shot(Some("rtnw_4_3.ppm")) 16 | .unwrap(); 17 | } 18 | -------------------------------------------------------------------------------- /examples/rtnw_4_4.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::Lambertian, 5 | prelude::*, 6 | texture::Checker, 7 | }; 8 | 9 | fn main() { 10 | let mut world = HittableList::default(); 11 | let checker = Checker::new(Color::new(0.2, 0.3, 0.1), Color::new(0.9, 0.9, 0.9)); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -10.0, 0.0), 16 | 10.0, 17 | Lambertian::new(checker.clone()), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 10.0, 0.0), 21 | 10.0, 22 | Lambertian::new(checker), 23 | )); 24 | 25 | let camera = CameraBuilder::default() 26 | .aspect_ratio(5.0 / 3.0) 27 | .fov(20.0) 28 | .look_from(Point3::new(13.0, 2.0, 3.0)) 29 | .look_at(Point3::new(0.0, 0.0, 0.0)) 30 | .focus(10.0) 31 | .build(); 32 | 33 | camera 34 | .take_photo(world) 35 | .height(300) 36 | .samples(128) 37 | .shot(Some("rtnw_4_4.ppm")) 38 | .unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /examples/rtnw_5_1.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::Lambertian, 5 | prelude::*, 6 | texture::{Perlin, SmoothType}, 7 | }; 8 | 9 | fn main() { 10 | let mut world = HittableList::default(); 11 | let perlin = Perlin::new(256, false).smooth(SmoothType::None); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -1000.0, 0.0), 16 | 1000.0, 17 | Lambertian::new(perlin.clone()), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 2.0, 0.0), 21 | 2.0, 22 | Lambertian::new(perlin), 23 | )); 24 | 25 | let camera = CameraBuilder::default() 26 | .aspect_ratio(5.0 / 3.0) 27 | .fov(20.0) 28 | .look_from(Point3::new(13.0, 2.0, 3.0)) 29 | .look_at(Point3::new(0.0, 0.0, 0.0)) 30 | .focus(10.0) 31 | .build(); 32 | 33 | camera 34 | .take_photo(world) 35 | .height(300) 36 | .samples(128) 37 | .shot(Some("rtnw_5_1.ppm")) 38 | .unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /examples/rtnw_5_2.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::Lambertian, 5 | prelude::*, 6 | texture::{Perlin, SmoothType}, 7 | }; 8 | 9 | fn main() { 10 | let mut world = HittableList::default(); 11 | let perlin = Perlin::new(256, false).smooth(SmoothType::LinearInterpolate); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -1000.0, 0.0), 16 | 1000.0, 17 | Lambertian::new(perlin.clone()), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 2.0, 0.0), 21 | 2.0, 22 | Lambertian::new(perlin), 23 | )); 24 | 25 | let camera = CameraBuilder::default() 26 | .aspect_ratio(5.0 / 3.0) 27 | .fov(20.0) 28 | .look_from(Point3::new(13.0, 2.0, 3.0)) 29 | .look_at(Point3::new(0.0, 0.0, 0.0)) 30 | .focus(10.0) 31 | .build(); 32 | 33 | camera 34 | .take_photo(world) 35 | .height(300) 36 | .samples(128) 37 | .shot(Some("rtnw_5_2.ppm")) 38 | .unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /examples/rtnw_5_3.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::Lambertian, 5 | prelude::*, 6 | texture::Perlin, 7 | }; 8 | 9 | fn main() { 10 | let mut world = HittableList::default(); 11 | let perlin = Perlin::new(256, false); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -1000.0, 0.0), 16 | 1000.0, 17 | Lambertian::new(perlin.clone()), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 2.0, 0.0), 21 | 2.0, 22 | Lambertian::new(perlin), 23 | )); 24 | 25 | let camera = CameraBuilder::default() 26 | .aspect_ratio(5.0 / 3.0) 27 | .fov(20.0) 28 | .look_from(Point3::new(13.0, 2.0, 3.0)) 29 | .look_at(Point3::new(0.0, 0.0, 0.0)) 30 | .focus(10.0) 31 | .build(); 32 | 33 | camera 34 | .take_photo(world) 35 | .height(300) 36 | .samples(128) 37 | .shot(Some("rtnw_5_3.ppm")) 38 | .unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /examples/rtnw_5_4.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::Lambertian, 5 | prelude::*, 6 | texture::Perlin, 7 | }; 8 | 9 | fn main() { 10 | let mut world = HittableList::default(); 11 | let perlin = Perlin::new(256, false).scale(4.0); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -1000.0, 0.0), 16 | 1000.0, 17 | Lambertian::new(perlin.clone()), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 2.0, 0.0), 21 | 2.0, 22 | Lambertian::new(perlin), 23 | )); 24 | 25 | let camera = CameraBuilder::default() 26 | .aspect_ratio(5.0 / 3.0) 27 | .fov(20.0) 28 | .look_from(Point3::new(13.0, 2.0, 3.0)) 29 | .look_at(Point3::new(0.0, 0.0, 0.0)) 30 | .focus(10.0) 31 | .build(); 32 | 33 | camera 34 | .take_photo(world) 35 | .height(300) 36 | .samples(128) 37 | .shot(Some("rtnw_5_4.ppm")) 38 | .unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /examples/rtnw_5_5.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::Lambertian, 5 | prelude::*, 6 | texture::Perlin, 7 | }; 8 | 9 | fn main() { 10 | let mut world = HittableList::default(); 11 | let perlin = Perlin::new(256, true).scale(4.0); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -1000.0, 0.0), 16 | 1000.0, 17 | Lambertian::new(perlin.clone()), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 2.0, 0.0), 21 | 2.0, 22 | Lambertian::new(perlin), 23 | )); 24 | 25 | let camera = CameraBuilder::default() 26 | .aspect_ratio(5.0 / 3.0) 27 | .fov(20.0) 28 | .look_from(Point3::new(13.0, 2.0, 3.0)) 29 | .look_at(Point3::new(0.0, 0.0, 0.0)) 30 | .focus(10.0) 31 | .build(); 32 | 33 | camera 34 | .take_photo(world) 35 | .height(300) 36 | .samples(128) 37 | .shot(Some("rtnw_5_5.ppm")) 38 | .unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /examples/rtnw_5_6.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::Lambertian, 5 | prelude::*, 6 | texture::Perlin, 7 | }; 8 | 9 | fn main() { 10 | let mut world = HittableList::default(); 11 | let perlin = Perlin::new(256, true).turbulence(7); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -1000.0, 0.0), 16 | 1000.0, 17 | Lambertian::new(perlin.clone()), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 2.0, 0.0), 21 | 2.0, 22 | Lambertian::new(perlin), 23 | )); 24 | 25 | let camera = CameraBuilder::default() 26 | .aspect_ratio(5.0 / 3.0) 27 | .fov(20.0) 28 | .look_from(Point3::new(13.0, 2.0, 3.0)) 29 | .look_at(Point3::new(0.0, 0.0, 0.0)) 30 | .focus(10.0) 31 | .build(); 32 | 33 | camera 34 | .take_photo(world) 35 | .height(300) 36 | .samples(128) 37 | .shot(Some("rtnw_5_6.ppm")) 38 | .unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /examples/rtnw_5_7.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::Lambertian, 5 | prelude::*, 6 | texture::Perlin, 7 | }; 8 | 9 | fn main() { 10 | let mut world = HittableList::default(); 11 | let perlin = Perlin::new(256, true).scale(4.0).marble(7); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -1000.0, 0.0), 16 | 1000.0, 17 | Lambertian::new(perlin.clone()), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 2.0, 0.0), 21 | 2.0, 22 | Lambertian::new(perlin), 23 | )); 24 | 25 | let camera = CameraBuilder::default() 26 | .aspect_ratio(5.0 / 3.0) 27 | .fov(20.0) 28 | .look_from(Point3::new(13.0, 2.0, 3.0)) 29 | .look_at(Point3::new(0.0, 0.0, 0.0)) 30 | .focus(10.0) 31 | .build(); 32 | 33 | camera 34 | .take_photo(world) 35 | .height(300) 36 | .samples(128) 37 | .shot(Some("rtnw_5_7.ppm")) 38 | .unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /examples/rtnw_6_2.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::Lambertian, 5 | prelude::*, 6 | texture::Image, 7 | }; 8 | 9 | fn main() { 10 | let mut world = HittableList::default(); 11 | // Image comes from http://visibleearth.nasa.gov/view.php?id=57752 12 | let earth_texture = Image::new("examples/earth-map.png").unwrap(); 13 | 14 | world 15 | .add(Sphere::new( 16 | Point3::new(0.0, 0.0, 0.0), 17 | 3.0, 18 | Lambertian::new(earth_texture), 19 | )) 20 | .add(Sphere::new( 21 | Point3::new(0.0, -1003.0, 0.0), 22 | 1000.0, 23 | Lambertian::new(Color::new(0.5, 0.5, 0.5)), 24 | )); 25 | 26 | let camera = CameraBuilder::default() 27 | .aspect_ratio(1.0) 28 | .fov(90.0) 29 | .look_from(Point3::new(0.0, 4.0, -6.0)) 30 | .look_at(Point3::new(0.0, 0.0, 0.0)) 31 | .build(); 32 | 33 | camera 34 | .take_photo(world) 35 | .height(800) 36 | .samples(128) 37 | .shot(Some("rtnw_6_2.ppm")) 38 | .unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /examples/rtnw_7_4.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, AARect, AARectMetrics, Sphere}, 4 | material::{DiffuseLight, Lambertian}, 5 | prelude::*, 6 | texture::Perlin, 7 | }; 8 | 9 | fn main() { 10 | let mut world = HittableList::default(); 11 | let perlin = Perlin::new(256, true).scale(4.0).marble(7); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -1000.0, 0.0), 16 | 1000.0, 17 | Lambertian::new(perlin.clone()), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 2.0, 0.0), 21 | 2.0, 22 | Lambertian::new(perlin), 23 | )) 24 | .add(AARect::new_xy( 25 | AARectMetrics::new(-2.0, (3.0, 5.0), (1.0, 3.0)), 26 | DiffuseLight::new(Color::new(1.0, 1.0, 1.0)).multiplier(4.0), 27 | )); 28 | 29 | let camera = CameraBuilder::default() 30 | .aspect_ratio(5.0 / 3.0) 31 | .fov(20.0) 32 | .look_from(Point3::new(25.0, 5.0, 7.5)) 33 | .look_at(Point3::new(0.0, 2.0, 0.0)) 34 | .focus(10.0) 35 | .build(); 36 | 37 | camera 38 | .take_photo(world) 39 | .background(|_| Color::default()) 40 | .height(300) 41 | .samples(1000) 42 | .shot(Some("rtnw_7_4.ppm")) 43 | .unwrap(); 44 | } 45 | -------------------------------------------------------------------------------- /examples/rtnw_7_4_2.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | mod common; 3 | 4 | use remda::{ 5 | camera::CameraBuilder, 6 | hittable::{collection::HittableList, AARect, AARectMetrics, Sphere}, 7 | material::{DiffuseLight, Lambertian}, 8 | prelude::*, 9 | texture::Perlin, 10 | }; 11 | 12 | fn main() { 13 | common::init_log("info"); 14 | 15 | let mut world = HittableList::default(); 16 | let perlin = Perlin::new(256, true).scale(4.0).marble(7); 17 | 18 | world 19 | .add(Sphere::new( 20 | Point3::new(0.0, -1000.0, 0.0), 21 | 1000.0, 22 | Lambertian::new(perlin.clone()), 23 | )) 24 | .add(Sphere::new( 25 | Point3::new(0.0, 2.0, 0.0), 26 | 2.0, 27 | Lambertian::new(perlin), 28 | )) 29 | .add(Sphere::new( 30 | Point3::new(0.0, 7.0, 0.0), 31 | 2.0, 32 | DiffuseLight::new(Color::new(1.0, 1.0, 1.0)).multiplier(4.0), 33 | )) 34 | .add(AARect::new_xy( 35 | AARectMetrics::new(-2.0, (3.0, 5.0), (1.0, 3.0)), 36 | DiffuseLight::new(Color::new(1.0, 1.0, 1.0)).multiplier(4.0), 37 | )); 38 | 39 | let camera = CameraBuilder::default() 40 | .aspect_ratio(5.0 / 3.0) 41 | .fov(20.0) 42 | .look_from(Point3::new(25.0, 5.0, 7.5)) 43 | .look_at(Point3::new(0.0, 2.0, 0.0)) 44 | .focus(10.0) 45 | .build(); 46 | 47 | camera 48 | .take_photo(world) 49 | .background(|_| Color::default()) 50 | .height(300) 51 | .samples(1000) 52 | .shot(Some("rtnw_7_4_2.ppm")) 53 | .unwrap(); 54 | } 55 | -------------------------------------------------------------------------------- /examples/rtnw_7_6.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | mod common; 3 | 4 | use remda::prelude::*; 5 | 6 | fn main() { 7 | let (camera, world) = common::ray_tracing_next_week::empty_cornell_box(); 8 | 9 | camera 10 | .take_photo(world) 11 | .background(|_| Color::default()) 12 | .height(300) 13 | .depth(50) 14 | .samples(1000) 15 | .shot(Some("rtnw_7_6.ppm")) 16 | .unwrap(); 17 | } 18 | -------------------------------------------------------------------------------- /examples/rtnw_8_0.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | mod common; 3 | 4 | use remda::prelude::*; 5 | 6 | fn main() { 7 | let (camera, world) = common::ray_tracing_next_week::cornell_box_no_rotation(); 8 | 9 | camera 10 | .take_photo(world) 11 | .background(|_| Color::default()) 12 | .height(300) 13 | .depth(50) 14 | .samples(1000) 15 | .shot(Some("rtnw_8_0.ppm")) 16 | .unwrap(); 17 | } 18 | -------------------------------------------------------------------------------- /examples/rtnw_8_1.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | mod common; 3 | 4 | use remda::{ 5 | camera::CameraBuilder, 6 | hittable::{collection::HittableList, transform::Translation, Carton}, 7 | material::Lambertian, 8 | prelude::*, 9 | }; 10 | 11 | fn main() { 12 | let camera = CameraBuilder::default() 13 | .look_from(Point3::new(-1.0, 1.5, 2.0)) 14 | .look_at(Point3::new(0.0, 0.0, 0.5)) 15 | .build(); 16 | 17 | let mut world = HittableList::default(); 18 | let carton_at_origin = Carton::new( 19 | Point3::new(-0.5, 0.0, -0.5), 20 | Point3::new(0.5, 1.0, 0.5), 21 | Lambertian::new(Color::new(0.2, 0.2, 0.2)), 22 | ); 23 | let carton_moved = Translation::new(carton_at_origin.clone(), Vec3::new(2.0, 0.0, 0.0)); 24 | world.add(carton_at_origin).add(carton_moved); 25 | 26 | camera 27 | .take_photo(world) 28 | .height(480) 29 | .samples(512) 30 | .shot(Some("rtnw_8_1.ppm")) 31 | .unwrap(); 32 | } 33 | -------------------------------------------------------------------------------- /examples/rtnw_8_2.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | mod common; 3 | 4 | use remda::prelude::*; 5 | 6 | fn main() { 7 | common::init_log("info"); 8 | 9 | let (camera, world) = common::ray_tracing_next_week::cornell_box(); 10 | 11 | camera 12 | .take_photo(world) 13 | .background(|_| Color::default()) 14 | .height(300) 15 | .depth(50) 16 | .samples(1000) 17 | .shot(Some("rtnw_8_2.ppm")) 18 | .unwrap(); 19 | } 20 | -------------------------------------------------------------------------------- /examples/rtnw_9_2.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | mod common; 3 | 4 | use remda::prelude::*; 5 | 6 | fn main() { 7 | common::init_log("info"); 8 | 9 | let (camera, world) = common::ray_tracing_next_week::cornell_box_smoke(); 10 | 11 | camera 12 | .take_photo(world) 13 | .background(|_| Color::default()) 14 | .height(300) 15 | .depth(50) 16 | .samples(1000) 17 | .shot(Some("rtnw_9_2.ppm")) 18 | .unwrap(); 19 | } 20 | -------------------------------------------------------------------------------- /examples/rtow_10_3.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::{Dielectric, Lambertian, Metal}, 5 | prelude::*, 6 | }; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | 11 | let mut world = HittableList::default(); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -100.5, -1.0), 16 | 100.0, 17 | Lambertian::new(Color::new(0.8, 0.8, 0.0)), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 0.0, -1.0), 21 | 0.5, 22 | Lambertian::new(Color::new(0.1, 0.2, 0.5)), 23 | )) 24 | .add(Sphere::new( 25 | Point3::new(1.0, 0.0, -1.0), 26 | 0.5, 27 | Metal::new(Color::new(0.8, 0.6, 0.2)), 28 | )) 29 | .add(Sphere::new( 30 | Point3::new(-1.0, 0.0, -1.0), 31 | 0.5, 32 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5), 33 | )); 34 | 35 | let camera = CameraBuilder::default().aspect_ratio(2.0).build(); 36 | 37 | camera 38 | .take_photo(world) 39 | .height(100) 40 | .samples(100) 41 | .shot(Some("rtow_10_3.ppm")) 42 | .unwrap(); 43 | } 44 | -------------------------------------------------------------------------------- /examples/rtow_10_5.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::{Dielectric, Glass, Lambertian, Metal}, 5 | prelude::*, 6 | }; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | 11 | let mut world = HittableList::default(); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -100.5, -1.0), 16 | 100.0, 17 | Lambertian::new(Color::new(0.8, 0.8, 0.0)), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 0.0, -1.0), 21 | 0.5, 22 | Lambertian::new(Color::new(0.1, 0.2, 0.5)), 23 | )) 24 | .add(Sphere::new( 25 | Point3::new(1.0, 0.0, -1.0), 26 | 0.5, 27 | Metal::new(Color::new(0.8, 0.6, 0.2)).fuzz(0.3), 28 | )) 29 | .add(Sphere::new( 30 | Point3::new(-1.0, 0.0, -1.0), 31 | 0.5, 32 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5).reflect_curve(Glass {}), 33 | )) 34 | .add(Sphere::new( 35 | Point3::new(-1.0, 0.0, -1.0), 36 | -0.45, 37 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5).reflect_curve(Glass {}), 38 | )); 39 | 40 | let camera = CameraBuilder::default().aspect_ratio(2.0).build(); 41 | 42 | camera 43 | .take_photo(world) 44 | .height(100) 45 | .samples(100) 46 | .shot(Some("rtow_10_5.ppm")) 47 | .unwrap(); 48 | } 49 | -------------------------------------------------------------------------------- /examples/rtow_11_1.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::Lambertian, 5 | prelude::*, 6 | }; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | 11 | let mut world = HittableList::default(); 12 | 13 | let r = (PI / 4.0).cos(); 14 | 15 | world 16 | .add(Sphere::new( 17 | Point3::new(-r, 0.0, -1.0), 18 | r, 19 | Lambertian::new(Color::new(0.0, 0.0, 1.0)), 20 | )) 21 | .add(Sphere::new( 22 | Point3::new(r, 0.0, -1.0), 23 | r, 24 | Lambertian::new(Color::new(1.0, 0.0, 0.0)), 25 | )); 26 | 27 | let camera = CameraBuilder::default().aspect_ratio(2.0).build(); 28 | 29 | camera 30 | .take_photo(world) 31 | .height(100) 32 | .samples(100) 33 | .shot(Some("rtow_11_1.ppm")) 34 | .unwrap(); 35 | } 36 | -------------------------------------------------------------------------------- /examples/rtow_11_2.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::{Dielectric, Glass, Lambertian, Metal}, 5 | prelude::*, 6 | }; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | 11 | let mut world = HittableList::default(); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -100.5, -1.0), 16 | 100.0, 17 | Lambertian::new(Color::new(0.8, 0.8, 0.0)), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 0.0, -1.0), 21 | 0.5, 22 | Lambertian::new(Color::new(0.1, 0.2, 0.5)), 23 | )) 24 | .add(Sphere::new( 25 | Point3::new(1.0, 0.0, -1.0), 26 | 0.5, 27 | Metal::new(Color::new(0.8, 0.6, 0.2)), 28 | )) 29 | .add(Sphere::new( 30 | Point3::new(-1.0, 0.0, -1.0), 31 | 0.5, 32 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5).reflect_curve(Glass {}), 33 | )) 34 | .add(Sphere::new( 35 | Point3::new(-1.0, 0.0, -1.0), 36 | -0.45, 37 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5).reflect_curve(Glass {}), 38 | )); 39 | 40 | let camera = CameraBuilder::default() 41 | .look_from(Point3::new(-2.0, 2.0, 1.0)) 42 | .aspect_ratio(2.0) 43 | .build(); 44 | 45 | camera 46 | .take_photo(world) 47 | .height(100) 48 | .samples(100) 49 | .shot(Some("rtow_11_2.ppm")) 50 | .unwrap(); 51 | } 52 | -------------------------------------------------------------------------------- /examples/rtow_11_2_2.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::{Dielectric, Glass, Lambertian, Metal}, 5 | prelude::*, 6 | }; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | 11 | let mut world = HittableList::default(); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -100.5, -1.0), 16 | 100.0, 17 | Lambertian::new(Color::new(0.8, 0.8, 0.0)), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 0.0, -1.0), 21 | 0.5, 22 | Lambertian::new(Color::new(0.1, 0.2, 0.5)), 23 | )) 24 | .add(Sphere::new( 25 | Point3::new(1.0, 0.0, -1.0), 26 | 0.5, 27 | Metal::new(Color::new(0.8, 0.6, 0.2)), 28 | )) 29 | .add(Sphere::new( 30 | Point3::new(-1.0, 0.0, -1.0), 31 | 0.5, 32 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5).reflect_curve(Glass {}), 33 | )) 34 | .add(Sphere::new( 35 | Point3::new(-1.0, 0.0, -1.0), 36 | -0.45, 37 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5).reflect_curve(Glass {}), 38 | )); 39 | 40 | let camera = CameraBuilder::default() 41 | .look_from(Point3::new(-2.0, 2.0, 1.0)) 42 | .fov(20.0) 43 | .aspect_ratio(2.0) 44 | .build(); 45 | 46 | camera 47 | .take_photo(world) 48 | .height(100) 49 | .samples(100) 50 | .shot(Some("rtow_11_2_2.ppm")) 51 | .unwrap(); 52 | } 53 | -------------------------------------------------------------------------------- /examples/rtow_12_2.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::{Dielectric, Glass, Lambertian, Metal}, 5 | prelude::*, 6 | }; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | 11 | let mut world = HittableList::default(); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -100.5, -1.0), 16 | 100.0, 17 | Lambertian::new(Color::new(0.8, 0.8, 0.0)), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 0.0, -1.0), 21 | 0.5, 22 | Lambertian::new(Color::new(0.1, 0.2, 0.5)), 23 | )) 24 | .add(Sphere::new( 25 | Point3::new(1.0, 0.0, -1.0), 26 | 0.5, 27 | Metal::new(Color::new(0.8, 0.6, 0.2)), 28 | )) 29 | .add(Sphere::new( 30 | Point3::new(-1.0, 0.0, -1.0), 31 | 0.5, 32 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5).reflect_curve(Glass {}), 33 | )) 34 | .add(Sphere::new( 35 | Point3::new(-1.0, 0.0, -1.0), 36 | -0.45, 37 | Dielectric::new(Color::new(1.0, 1.0, 1.0), 1.5).reflect_curve(Glass {}), 38 | )); 39 | 40 | let camera = CameraBuilder::default() 41 | .look_from(Point3::new(3.0, 3.0, 2.0)) 42 | .fov(20.0) 43 | .focus_to_look_at() 44 | .aperture(2.0) 45 | .aspect_ratio(2.0) 46 | .build(); 47 | 48 | camera 49 | .take_photo(world) 50 | .height(100) 51 | .samples(100) 52 | .shot(Some("rtow_12_2.ppm")) 53 | .unwrap(); 54 | } 55 | -------------------------------------------------------------------------------- /examples/rtow_13_1.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | mod common; 3 | 4 | fn main() { 5 | common::init_log("info"); 6 | 7 | // Change `7` to another number to generate different scene 8 | // Or use `None` to use random seed 9 | let (camera, world) = common::ray_tracing_in_one_weekend::final_scene(Some(7)); 10 | 11 | camera 12 | .take_photo(world) 13 | .height(108) 14 | .samples(128) 15 | .shot(Some("rtow_13_1.ppm")) 16 | .unwrap(); 17 | } 18 | -------------------------------------------------------------------------------- /examples/rtow_2_2.rs: -------------------------------------------------------------------------------- 1 | use remda::{painter::Painter, prelude::*}; 2 | 3 | fn main() { 4 | env_logger::init(); 5 | 6 | Painter::new(256, 256) 7 | .gamma(false) 8 | .samples(1) 9 | .draw(&Some("rtow_2_2.ppm"), |u, v| Vec3::new(u, v, 0.25)) 10 | .unwrap() 11 | } 12 | -------------------------------------------------------------------------------- /examples/rtow_4_2.rs: -------------------------------------------------------------------------------- 1 | use remda::{camera::CameraBuilder, hittable::collection::HittableList}; 2 | 3 | fn main() { 4 | env_logger::init(); 5 | 6 | let world = HittableList::default(); 7 | 8 | let camera = CameraBuilder::default().build(); 9 | 10 | camera 11 | .take_photo(world) 12 | .height(432) 13 | .gamma(false) 14 | .samples(1) 15 | .shot(Some("rtow_4_2.ppm")) 16 | .unwrap(); 17 | } 18 | -------------------------------------------------------------------------------- /examples/rtow_5_2.rs: -------------------------------------------------------------------------------- 1 | use remda::{camera::CameraBuilder, hittable::collection::HittableList, prelude::*}; 2 | 3 | fn hit_sphere(center: &Point3, radius: f64, ray: &Ray) -> bool { 4 | let oc = &ray.origin - center; 5 | let a = ray.direction.length_squared(); 6 | let b = 2.0 * oc.dot(&ray.direction); 7 | let c = oc.length_squared() - radius * radius; 8 | let discriminant = b * b - 4.0 * a * c; 9 | discriminant > 0.0 10 | } 11 | 12 | fn background(ray: &Ray) -> Color { 13 | if hit_sphere(&Point3::new(0.0, 0.0, -1.0), 0.5, ray) { 14 | return Color::new(1.0, 0.0, 0.0); 15 | } 16 | remda::hittable::collection::world_default_background(ray) 17 | } 18 | 19 | fn main() { 20 | env_logger::init(); 21 | 22 | let world = HittableList::default(); 23 | 24 | let camera = CameraBuilder::default().build(); 25 | 26 | camera 27 | .take_photo(world) 28 | .background(background) 29 | .height(432) 30 | .gamma(false) 31 | .samples(1) 32 | .shot(Some("rtow_5_2.ppm")) 33 | .unwrap(); 34 | } 35 | -------------------------------------------------------------------------------- /examples/rtow_6_1.rs: -------------------------------------------------------------------------------- 1 | use remda::{camera::CameraBuilder, hittable::collection::HittableList, prelude::*}; 2 | 3 | fn hit_sphere(center: &Point3, radius: f64, ray: &Ray) -> Option { 4 | let oc = &ray.origin - center; 5 | let a = ray.direction.length_squared(); 6 | let b = 2.0 * oc.dot(&ray.direction); 7 | let c = oc.length_squared() - radius * radius; 8 | let discriminant = b * b - 4.0 * a * c; 9 | if discriminant < 0.0 { 10 | None 11 | } else { 12 | Some((-b - discriminant.sqrt()) / (2.0 * a)) 13 | } 14 | } 15 | 16 | fn background(ray: &Ray) -> Color { 17 | if let Some(t) = hit_sphere(&Point3::new(0.0, 0.0, -1.0), 0.5, ray) { 18 | if t > 0.0 { 19 | let n = (ray.position_after(t) - Vec3::new(0.0, 0.0, -1.0)).unit(); 20 | return (0.5 * (n + Vec3::new(1.0, 1.0, 1.0))).into_color(1, false); 21 | } 22 | } 23 | let unit = ray.direction.unit(); 24 | let t = 0.5 * (unit.y + 1.0); 25 | Color::new(1.0, 1.0, 1.0).gradient(&Color::new(0.5, 0.7, 1.0), t) 26 | } 27 | 28 | fn main() { 29 | env_logger::init(); 30 | 31 | let world = HittableList::default(); 32 | 33 | let camera = CameraBuilder::default().build(); 34 | 35 | camera 36 | .take_photo(world) 37 | .background(background) 38 | .height(432) 39 | .gamma(false) 40 | .samples(1) 41 | .shot(Some("rtow_6_1.ppm")) 42 | .unwrap(); 43 | } 44 | -------------------------------------------------------------------------------- /examples/rtow_6_7.rs: -------------------------------------------------------------------------------- 1 | use remda::{camera::CameraBuilder, hittable::collection::HittableList, prelude::*}; 2 | 3 | fn hit_sphere(center: &Point3, radius: f64, ray: &Ray) -> Option<(f64, Vec3)> { 4 | let oc = &ray.origin - center; 5 | let a = ray.direction.length_squared(); 6 | let half_b = oc.dot(&ray.direction); 7 | let c = oc.length_squared() - radius * radius; 8 | #[allow(clippy::suspicious_operation_groupings)] 9 | let discriminant = half_b * half_b - a * c; 10 | if discriminant < 0.0 { 11 | None 12 | } else { 13 | let t1 = (-half_b - discriminant.sqrt()) / a; 14 | let t2 = (-half_b + discriminant.sqrt()) / a; 15 | if t1 > 0.0 { 16 | let p = ray.position_after(t1); 17 | let n = (p - center) / radius; 18 | Some((t1, n)) 19 | } else if t2 > 0.0 { 20 | let p = ray.position_after(t2); 21 | let n = (p - center) / radius; 22 | Some((t2, n)) 23 | } else { 24 | None 25 | } 26 | } 27 | } 28 | 29 | fn background(ray: &Ray) -> Color { 30 | let ts = vec![ 31 | hit_sphere(&Point3::new(0.0, 0.0, -1.0), 0.5, ray), 32 | hit_sphere(&Point3::new(0.0, -100.5, -1.0), 100.0, ray), 33 | ]; 34 | if let Some((_, n)) = ts 35 | .into_iter() 36 | .flatten() 37 | .min_by(|a, b| a.0.partial_cmp(&b.0).unwrap()) 38 | { 39 | return (0.5 * (n + Vec3::new(1.0, 1.0, 1.0))).into_color(1, false); 40 | } 41 | let unit = ray.direction.unit(); 42 | let t = 0.5 * (unit.y + 1.0); 43 | Color::new(1.0, 1.0, 1.0).gradient(&Color::new(0.5, 0.7, 1.0), t) 44 | } 45 | 46 | fn main() { 47 | env_logger::init(); 48 | 49 | let world = HittableList::default(); 50 | 51 | let camera = CameraBuilder::default().aspect_ratio(2.0).build(); 52 | 53 | camera 54 | .take_photo(world) 55 | .background(background) 56 | .height(100) 57 | .gamma(false) 58 | .samples(1) 59 | .shot(Some("rtow_6_7.ppm")) 60 | .unwrap(); 61 | } 62 | -------------------------------------------------------------------------------- /examples/rtow_7_2.rs: -------------------------------------------------------------------------------- 1 | use remda::{camera::CameraBuilder, hittable::collection::HittableList, prelude::*}; 2 | 3 | fn hit_sphere(center: &Point3, radius: f64, ray: &Ray) -> Option<(f64, Vec3)> { 4 | let oc = &ray.origin - center; 5 | let a = ray.direction.length_squared(); 6 | let half_b = oc.dot(&ray.direction); 7 | let c = oc.length_squared() - radius * radius; 8 | #[allow(clippy::suspicious_operation_groupings)] 9 | let discriminant = half_b * half_b - a * c; 10 | if discriminant < 0.0 { 11 | None 12 | } else { 13 | let t1 = (-half_b - discriminant.sqrt()) / a; 14 | let t2 = (-half_b + discriminant.sqrt()) / a; 15 | if t1 > 0.0 { 16 | let p = ray.position_after(t1); 17 | let n = (p - center) / radius; 18 | Some((t1, n)) 19 | } else if t2 > 0.0 { 20 | let p = ray.position_after(t2); 21 | let n = (p - center) / radius; 22 | Some((t2, n)) 23 | } else { 24 | None 25 | } 26 | } 27 | } 28 | 29 | fn background(ray: &Ray) -> Color { 30 | let ts = vec![ 31 | hit_sphere(&Point3::new(0.0, 0.0, -1.0), 0.5, ray), 32 | hit_sphere(&Point3::new(0.0, -100.5, -1.0), 100.0, ray), 33 | ]; 34 | if let Some((_, n)) = ts 35 | .into_iter() 36 | .flatten() 37 | .min_by(|a, b| a.0.partial_cmp(&b.0).unwrap()) 38 | { 39 | return (0.5 * (n + Vec3::new(1.0, 1.0, 1.0))).into_color(1, false); 40 | } 41 | let unit = ray.direction.unit(); 42 | let t = 0.5 * (unit.y + 1.0); 43 | Color::new(1.0, 1.0, 1.0).gradient(&Color::new(0.5, 0.7, 1.0), t) 44 | } 45 | 46 | fn main() { 47 | env_logger::init(); 48 | 49 | let world = HittableList::default(); 50 | 51 | let camera = CameraBuilder::default() 52 | .look_from(Point3::new(-0.5, 0.0, -0.7)) 53 | .look_at(Point3::new(-0.5, 0.0, -1.0)) 54 | .aspect_ratio(39.0 / 36.0) 55 | .build(); 56 | 57 | camera 58 | .take_photo(world) 59 | .background(background) 60 | .height(36) 61 | .gamma(false) 62 | .samples(100) 63 | .shot(Some("rtow_7_2.ppm")) 64 | .unwrap(); 65 | } 66 | -------------------------------------------------------------------------------- /examples/rtow_8_2.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::{Lambertian, LambertianMathType}, 5 | prelude::*, 6 | }; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | 11 | let mut world = HittableList::default(); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -100.5, -1.0), 16 | 100.0, 17 | Lambertian::new(Color::new(0.5, 0.5, 0.5)).math_type(LambertianMathType::Approximates), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 0.0, -1.0), 21 | 0.5, 22 | Lambertian::new(Color::new(0.5, 0.5, 0.5)).math_type(LambertianMathType::Approximates), 23 | )); 24 | 25 | let camera = CameraBuilder::default().aspect_ratio(2.0).build(); 26 | 27 | camera 28 | .take_photo(world) 29 | .height(100) 30 | .gamma(false) 31 | .samples(100) 32 | .shot(Some("rtow_8_2.ppm")) 33 | .unwrap(); 34 | } 35 | -------------------------------------------------------------------------------- /examples/rtow_8_3.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::{Lambertian, LambertianMathType}, 5 | prelude::*, 6 | }; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | 11 | let mut world = HittableList::default(); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -100.5, -1.0), 16 | 100.0, 17 | Lambertian::new(Color::new(0.5, 0.5, 0.5)).math_type(LambertianMathType::Approximates), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 0.0, -1.0), 21 | 0.5, 22 | Lambertian::new(Color::new(0.5, 0.5, 0.5)).math_type(LambertianMathType::Approximates), 23 | )); 24 | 25 | let camera = CameraBuilder::default().aspect_ratio(2.0).build(); 26 | 27 | camera 28 | .take_photo(world) 29 | .height(100) 30 | .samples(100) 31 | .shot(Some("rtow_8_3.ppm")) 32 | .unwrap(); 33 | } 34 | -------------------------------------------------------------------------------- /examples/rtow_8_5.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::Lambertian, 5 | prelude::*, 6 | }; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | 11 | let mut world = HittableList::default(); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -100.5, -1.0), 16 | 100.0, 17 | Lambertian::new(Color::new(0.5, 0.5, 0.5)), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 0.0, -1.0), 21 | 0.5, 22 | Lambertian::new(Color::new(0.5, 0.5, 0.5)), 23 | )); 24 | 25 | let camera = CameraBuilder::default().aspect_ratio(2.0).build(); 26 | 27 | camera 28 | .take_photo(world) 29 | .height(100) 30 | .samples(100) 31 | .shot(Some("rtow_8_5.ppm")) 32 | .unwrap(); 33 | } 34 | -------------------------------------------------------------------------------- /examples/rtow_8_6.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::{Lambertian, LambertianMathType}, 5 | prelude::*, 6 | }; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | 11 | let mut world = HittableList::default(); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -100.5, -1.0), 16 | 100.0, 17 | Lambertian::new(Color::new(0.5, 0.5, 0.5)).math_type(LambertianMathType::Hemisphere), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 0.0, -1.0), 21 | 0.5, 22 | Lambertian::new(Color::new(0.5, 0.5, 0.5)).math_type(LambertianMathType::Hemisphere), 23 | )); 24 | 25 | let camera = CameraBuilder::default().aspect_ratio(2.0).build(); 26 | 27 | camera 28 | .take_photo(world) 29 | .height(100) 30 | .samples(100) 31 | .shot(Some("rtow_8_6.ppm")) 32 | .unwrap(); 33 | } 34 | -------------------------------------------------------------------------------- /examples/rtow_9_5.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::{Lambertian, Metal}, 5 | prelude::*, 6 | }; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | 11 | let mut world = HittableList::default(); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -100.5, -1.0), 16 | 100.0, 17 | Lambertian::new(Color::new(0.8, 0.8, 0.0)), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 0.0, -1.0), 21 | 0.5, 22 | Lambertian::new(Color::new(0.7, 0.3, 0.3)), 23 | )) 24 | .add(Sphere::new( 25 | Point3::new(1.0, 0.0, -1.0), 26 | 0.5, 27 | Metal::new(Color::new(0.8, 0.6, 0.2)), 28 | )) 29 | .add(Sphere::new( 30 | Point3::new(-1.0, 0.0, -1.0), 31 | 0.5, 32 | Metal::new(Color::new(0.8, 0.8, 0.8)), 33 | )); 34 | 35 | let camera = CameraBuilder::default().aspect_ratio(2.0).build(); 36 | 37 | camera 38 | .take_photo(world) 39 | .height(100) 40 | .samples(100) 41 | .shot(Some("rtow_9_5.ppm")) 42 | .unwrap(); 43 | } 44 | -------------------------------------------------------------------------------- /examples/rtow_9_6.rs: -------------------------------------------------------------------------------- 1 | use remda::{ 2 | camera::CameraBuilder, 3 | hittable::{collection::HittableList, Sphere}, 4 | material::{Lambertian, Metal}, 5 | prelude::*, 6 | }; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | 11 | let mut world = HittableList::default(); 12 | 13 | world 14 | .add(Sphere::new( 15 | Point3::new(0.0, -100.5, -1.0), 16 | 100.0, 17 | Lambertian::new(Color::new(0.8, 0.8, 0.0)), 18 | )) 19 | .add(Sphere::new( 20 | Point3::new(0.0, 0.0, -1.0), 21 | 0.5, 22 | Lambertian::new(Color::new(0.7, 0.3, 0.3)), 23 | )) 24 | .add(Sphere::new( 25 | Point3::new(1.0, 0.0, -1.0), 26 | 0.5, 27 | Metal::new(Color::new(0.8, 0.6, 0.2)).fuzz(1.0), 28 | )) 29 | .add(Sphere::new( 30 | Point3::new(-1.0, 0.0, -1.0), 31 | 0.5, 32 | Metal::new(Color::new(0.8, 0.8, 0.8)).fuzz(0.3), 33 | )); 34 | 35 | let camera = CameraBuilder::default().aspect_ratio(2.0).build(); 36 | 37 | camera 38 | .take_photo(world) 39 | .height(100) 40 | .samples(100) 41 | .shot(Some("rtow_9_6.ppm")) 42 | .unwrap(); 43 | } 44 | -------------------------------------------------------------------------------- /examples/rtrl_2_1.rs: -------------------------------------------------------------------------------- 1 | use remda::prelude::*; 2 | 3 | const N: usize = 1000; 4 | 5 | fn main() { 6 | let mut inside_circle: u64 = 0; 7 | for _ in 0..N { 8 | let x: f64 = Random::range(-1.0..1.0); 9 | let y: f64 = Random::range(-1.0..1.0); 10 | if x.powi(2) + y.powi(2) <= 1.0 { 11 | inside_circle += 1; 12 | } 13 | } 14 | println!( 15 | "Estimate of Pi = {:.12}", 16 | 4.0 * inside_circle as f64 / N as f64 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /examples/rtrl_2_2.rs: -------------------------------------------------------------------------------- 1 | use remda::prelude::*; 2 | 3 | fn main() { 4 | let mut runs: u64 = 0; 5 | let mut inside_circle: u64 = 0; 6 | loop { 7 | runs += 1; 8 | let x: f64 = Random::range(-1.0..1.0); 9 | let y: f64 = Random::range(-1.0..1.0); 10 | if x.powi(2) + y.powi(2) <= 1.0 { 11 | inside_circle += 1; 12 | } 13 | if runs % 100000 == 0 { 14 | println!( 15 | "Estimate of Pi = {:.12}", 16 | 4.0 * inside_circle as f64 / runs as f64 17 | ); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/rtrl_2_3.rs: -------------------------------------------------------------------------------- 1 | use remda::prelude::*; 2 | 3 | const GRID_LINE_PRE_SIDE: u64 = 10000; 4 | 5 | fn main() { 6 | let mut inside_circle: u64 = 0; 7 | let mut inside_circle_stratified: u64 = 0; 8 | 9 | for i in 0..GRID_LINE_PRE_SIDE { 10 | for j in 0..GRID_LINE_PRE_SIDE { 11 | let x: f64 = Random::range(-1.0..1.0); 12 | let y: f64 = Random::range(-1.0..1.0); 13 | if x.powi(2) + y.powi(2) <= 1.0 { 14 | inside_circle += 1; 15 | } 16 | 17 | let x = -1.0 + 2.0 * (j as f64 + Random::normal()) / GRID_LINE_PRE_SIDE as f64; 18 | let y = -1.0 + 2.0 * (i as f64 + Random::normal()) / GRID_LINE_PRE_SIDE as f64; 19 | if x.powi(2) + y.powi(2) <= 1.0 { 20 | inside_circle_stratified += 1; 21 | } 22 | } 23 | } 24 | 25 | println!( 26 | "Regular Estimate of Pi = {:.12}", 27 | 4.0 * inside_circle as f64 / (GRID_LINE_PRE_SIDE as f64).powi(2) 28 | ); 29 | println!( 30 | "Stratified Estimate of Pi = {:.12}", 31 | 4.0 * inside_circle_stratified as f64 / (GRID_LINE_PRE_SIDE as f64).powi(2) 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /examples/rtrl_3_1.rs: -------------------------------------------------------------------------------- 1 | use remda::prelude::*; 2 | 3 | const N: u64 = 1000000; 4 | 5 | // Calculate \int_{0}^{2}x^2 6 | // Which is 8/3 7 | 8 | fn main() { 9 | let mut sum: f64 = 0.0; 10 | 11 | for _ in 0..N { 12 | sum += Random::range(0.0..2.0_f64).powi(2); 13 | } 14 | 15 | println!("I = {:.12}", 2.0 * sum / N as f64,); 16 | } 17 | -------------------------------------------------------------------------------- /examples/rtrl_3_3.rs: -------------------------------------------------------------------------------- 1 | use remda::prelude::*; 2 | 3 | const N: u64 = 1000000; 4 | 5 | // Calculate \int_{0}^{2}x^2 6 | // Which is 8/3 7 | 8 | fn pdf(x: f64) -> f64 { 9 | 0.5 * x 10 | } 11 | 12 | // pdf = 0.5x 13 | // P(x) = \int_{0}^{x} 0.5r dr = 1/4 * x^2 14 | // We use x = \sqrt{4 * y} to generate x 15 | // where y is uniform distribution in range [0, 1] 16 | fn generate_x() -> f64 { 17 | Random::range(0.0..4.0f64).sqrt() 18 | } 19 | 20 | fn main() { 21 | let mut sum: f64 = 0.0; 22 | 23 | for _ in 0..N { 24 | let x = generate_x(); 25 | sum += x * x / pdf(x); 26 | } 27 | 28 | println!("I = {:.12}", sum / N as f64,); 29 | } 30 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | fn_args_layout = "Compressed" 3 | newline_style = "Unix" 4 | use_field_init_shorthand = true 5 | use_small_heuristics = "Default" 6 | use_try_shorthand = true 7 | -------------------------------------------------------------------------------- /src/camera.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | hittable::{ 4 | collection::{HittableList, World}, 5 | Hittable, 6 | }, 7 | painter::Painter, 8 | prelude::*, 9 | }, 10 | std::path::Path, 11 | }; 12 | 13 | #[derive(Debug)] 14 | pub struct Camera { 15 | origin: Point3, 16 | lb: Point3, 17 | horizontal_full: Vec3, 18 | vertical_full: Vec3, 19 | horizontal_unit: Vec3, 20 | vertical_unit: Vec3, 21 | aspect_ratio: f64, 22 | aperture: f64, 23 | shutter_speed: f64, 24 | } 25 | 26 | impl Camera { 27 | #[allow(clippy::too_many_arguments, clippy::needless_pass_by_value)] // internal 28 | pub(self) fn new( 29 | look_from: &Point3, look_at: &Point3, vup: &Vec3, fov: f64, aspect_ratio: f64, 30 | aperture: f64, focus_distance: f64, shutter_speed: f64, 31 | ) -> Self { 32 | let fov = fov.to_radians(); 33 | let h = (fov / 2.0).tan(); 34 | let vh = 2.0 * h; 35 | let vw = vh * aspect_ratio; 36 | 37 | let w = (look_at - look_from).unit(); 38 | let horizontal_unit = w.cross(vup).unit(); 39 | let vertical_unit = horizontal_unit.cross(&w).unit(); 40 | 41 | let horizontal_full = focus_distance * vw * &horizontal_unit; 42 | let vertical_full = focus_distance * vh * &vertical_unit; 43 | let lb = look_from - &horizontal_full / 2.0 - &vertical_full / 2.0 + focus_distance * w; 44 | Self { 45 | origin: look_from.clone(), 46 | lb, 47 | horizontal_full, 48 | vertical_full, 49 | horizontal_unit, 50 | vertical_unit, 51 | aspect_ratio, 52 | aperture, 53 | shutter_speed, 54 | } 55 | } 56 | 57 | #[must_use] 58 | pub fn ray(&self, u: f64, v: f64) -> Ray { 59 | let rd = self.aperture / 2.0 * Vec3::random_unit_disk(); 60 | let offset = &self.horizontal_unit * rd.x + &self.vertical_unit * rd.y; 61 | let origin = &self.origin + offset; 62 | let direction = &self.lb + u * &self.horizontal_full + v * &self.vertical_full - &origin; 63 | 64 | Ray::new(origin, direction, self.shutter_speed * Random::normal()) 65 | } 66 | 67 | #[must_use] 68 | pub fn take_photo(&self, world: HittableList) -> TakePhotoSettings<'_> { 69 | let world = World::new(world, 0.0..self.shutter_speed); 70 | TakePhotoSettings::new(self, world) 71 | } 72 | } 73 | 74 | #[derive(Debug)] 75 | pub struct TakePhotoSettings<'c> { 76 | camera: &'c Camera, 77 | world: World, 78 | depth: usize, 79 | picture_height: usize, 80 | gamma: bool, 81 | samples: usize, 82 | threads: usize, 83 | parallel: bool, 84 | } 85 | 86 | impl<'c> TakePhotoSettings<'c> { 87 | #[must_use] 88 | pub const fn new(camera: &'c Camera, world: World) -> Self { 89 | Self { 90 | camera, 91 | world, 92 | depth: 8, 93 | picture_height: 108, 94 | gamma: true, 95 | samples: 50, 96 | threads: 0, 97 | parallel: true, 98 | } 99 | } 100 | 101 | pub fn background Color + Send + Sync + 'static>(mut self, bg: BG) -> Self { 102 | self.world.set_bg(bg); 103 | self 104 | } 105 | 106 | #[must_use] 107 | pub const fn depth(mut self, depth: usize) -> Self { 108 | self.depth = depth; 109 | self 110 | } 111 | 112 | #[must_use] 113 | pub const fn height(mut self, height: usize) -> Self { 114 | self.picture_height = height; 115 | self 116 | } 117 | 118 | #[must_use] 119 | pub const fn gamma(mut self, gamma: bool) -> Self { 120 | self.gamma = gamma; 121 | self 122 | } 123 | 124 | #[must_use] 125 | pub const fn samples(mut self, samples: usize) -> Self { 126 | self.samples = samples; 127 | self 128 | } 129 | 130 | #[must_use] 131 | pub const fn threads(mut self, threads: usize) -> Self { 132 | self.threads = threads; 133 | self 134 | } 135 | 136 | #[must_use] 137 | pub const fn parallel(mut self, parallel: bool) -> Self { 138 | self.parallel = parallel; 139 | self 140 | } 141 | 142 | fn ray_color(ray: &Ray, world: &World, depth: usize) -> Vec3 { 143 | if depth == 0 { 144 | return Vec3::default(); 145 | } 146 | 147 | if let Some(hit) = world.hit(ray, 0.001..f64::INFINITY) { 148 | let material = hit.material; 149 | let emitted = material 150 | .emitted(hit.u, hit.v, &hit.point) 151 | .unwrap_or_default(); 152 | if let Some(scattered) = material.scatter(ray, hit) { 153 | return emitted 154 | + scattered.color * Self::ray_color(&scattered.ray, world, depth - 1); 155 | } 156 | return emitted; 157 | } 158 | 159 | world.background(ray).into() 160 | } 161 | 162 | /// # Errors 163 | /// When open or save to file failed 164 | #[allow(clippy::needless_pass_by_value)] // Directly used public API, add & will make it harder to use 165 | pub fn shot>(&self, path: Option

) -> std::io::Result<()> { 166 | // because picture height/width is always positive and small enough in practice 167 | #[allow( 168 | clippy::cast_sign_loss, 169 | clippy::cast_precision_loss, 170 | clippy::cast_possible_truncation 171 | )] 172 | Painter::new( 173 | (self.picture_height as f64 * self.camera.aspect_ratio).round() as usize, 174 | self.picture_height, 175 | ) 176 | .gamma(self.gamma) 177 | .samples(self.samples) 178 | .threads(self.threads) 179 | .parallel(self.parallel) 180 | .draw(&path, |u, v| -> Vec3 { 181 | let ray = self.camera.ray(u, v); 182 | Self::ray_color(&ray, &self.world, self.depth) 183 | }) 184 | } 185 | } 186 | 187 | #[derive(Debug)] 188 | pub struct CameraBuilder { 189 | look_from: Point3, 190 | look_at: Point3, 191 | vup: Vec3, 192 | fov: f64, 193 | aspect_ratio: f64, 194 | aperture: f64, 195 | focus_distance: f64, 196 | shutter_speed: f64, 197 | } 198 | 199 | impl Default for CameraBuilder { 200 | fn default() -> Self { 201 | Self { 202 | look_from: Point3::default(), 203 | look_at: Point3::new(0.0, 0.0, -1.0), 204 | vup: Vec3::new(0.0, 1.0, 0.0), 205 | fov: 90.0, 206 | aspect_ratio: 16.0 / 9.0, 207 | aperture: 0.0, 208 | focus_distance: 1.0, 209 | shutter_speed: 0.0, 210 | } 211 | } 212 | } 213 | 214 | impl CameraBuilder { 215 | #[must_use] 216 | pub const fn look_from(mut self, look_from: Point3) -> Self { 217 | self.look_from = look_from; 218 | self 219 | } 220 | 221 | #[must_use] 222 | pub const fn look_at(mut self, look_at: Point3) -> Self { 223 | self.look_at = look_at; 224 | self 225 | } 226 | 227 | #[must_use] 228 | pub const fn vup(mut self, vup: Vec3) -> Self { 229 | self.vup = vup; 230 | self 231 | } 232 | 233 | #[must_use] 234 | pub fn fov(mut self, fov: f64) -> Self { 235 | debug_assert!(0.0 < fov && fov <= 180.0, "fov = {}", fov); 236 | self.fov = fov; 237 | self 238 | } 239 | 240 | #[must_use] 241 | pub fn aspect_ratio(mut self, aspect_ratio: f64) -> Self { 242 | debug_assert!(aspect_ratio > 0.0, "aspect_ratio = {}", aspect_ratio); 243 | self.aspect_ratio = aspect_ratio; 244 | self 245 | } 246 | 247 | #[must_use] 248 | pub fn aperture(mut self, aperture: f64) -> Self { 249 | debug_assert!(aperture >= 0.0, "aperture = {}", aperture); 250 | self.aperture = aperture; 251 | self 252 | } 253 | 254 | #[must_use] 255 | pub fn focus(mut self, distance: f64) -> Self { 256 | debug_assert!(distance >= 0.0, "distance = {}", distance); 257 | self.focus_distance = distance; 258 | self 259 | } 260 | 261 | #[must_use] 262 | pub fn focus_to_look_at(self) -> Self { 263 | let distance = (&self.look_at - &self.look_from).length(); 264 | self.focus(distance) 265 | } 266 | 267 | #[must_use] 268 | pub fn shutter_speed(mut self, duration: f64) -> Self { 269 | debug_assert!(duration >= 0.0, "duration = {}", duration); 270 | self.shutter_speed = duration; 271 | self 272 | } 273 | 274 | #[must_use] 275 | pub fn build(self) -> Camera { 276 | Camera::new( 277 | &self.look_from, 278 | &self.look_at, 279 | &self.vup, 280 | self.fov, 281 | self.aspect_ratio, 282 | self.aperture, 283 | self.focus_distance, 284 | self.shutter_speed, 285 | ) 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/hittable/collection/bvh.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | hittable::{collection::HittableList, HitRecord, Hittable}, 4 | prelude::*, 5 | }, 6 | std::{ 7 | cmp::Ordering, 8 | fmt::{Debug, Formatter}, 9 | ops::Range, 10 | }, 11 | }; 12 | 13 | #[derive(Default)] 14 | pub struct BVH { 15 | bbox: Option, 16 | left: Option>, 17 | right: Option>, 18 | } 19 | 20 | impl Debug for BVH { 21 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 22 | f.write_fmt(format_args!("BVH {{ bbox: {:?} }}", self.bbox)) 23 | } 24 | } 25 | 26 | fn cmp_geometry_by(axis: usize, a: &dyn Hittable, b: &dyn Hittable) -> Ordering { 27 | let box_a = a 28 | .bbox(0.0..0.0) 29 | .expect("No bounding box in bvh_node constructor"); 30 | let box_b = b 31 | .bbox(0.0..0.0) 32 | .expect("No bounding box in bvh_node constructor"); 33 | 34 | box_a.min()[axis] 35 | .partial_cmp(&box_b.min()[axis]) 36 | .expect("Bounding box contains NaN") 37 | } 38 | 39 | impl BVH { 40 | #[must_use] 41 | pub fn new(objects: HittableList, time_limit: Range) -> Self { 42 | let objects = objects.into_objects(); 43 | if objects.is_empty() { 44 | Self::default() 45 | } else { 46 | let mut objects: Vec<_> = objects.into_iter().map(Some).collect(); 47 | let count = objects.len(); 48 | Self::new_internal(&mut objects, 0..count, time_limit) 49 | } 50 | } 51 | 52 | fn new_internal( 53 | objects: &mut Vec>>, index: Range, time_limit: Range, 54 | ) -> Self { 55 | let count = index.end - index.start; 56 | 57 | if count == 1 { 58 | let left = objects[index.start].take().unwrap(); 59 | let bbox = left 60 | .bbox(time_limit) 61 | .expect("No bounding box in bvh_node constructor."); 62 | Self { 63 | bbox: Some(bbox), 64 | left: Some(left), 65 | right: None, 66 | } 67 | } else if count == 2 { 68 | let left = objects[index.start].take().unwrap(); 69 | let right = objects[index.start + 1].take().unwrap(); 70 | let left_bbox = left 71 | .bbox(time_limit.clone()) 72 | .expect("No bounding box in bvh_node constructor."); 73 | let right_bbox = right 74 | .bbox(time_limit) 75 | .expect("No bounding box in bvh_node constructor."); 76 | Self { 77 | bbox: Some(left_bbox | right_bbox), 78 | left: Some(left), 79 | right: Some(right), 80 | } 81 | } else { 82 | let axis = *Random::choose(&[0, 1, 2]); 83 | objects[index.clone()].sort_by(|a, b| { 84 | cmp_geometry_by( 85 | axis, 86 | a.as_ref().unwrap().as_ref(), 87 | b.as_ref().unwrap().as_ref(), 88 | ) 89 | }); 90 | let mid = index.start + count / 2; 91 | let left = Box::new(Self::new_internal( 92 | objects, 93 | index.start..mid, 94 | time_limit.clone(), 95 | )); 96 | let right = Box::new(Self::new_internal(objects, mid..index.end, time_limit)); 97 | Self { 98 | bbox: Some(left.bbox.as_ref().unwrap() | right.bbox.as_ref().unwrap()), 99 | left: Some(left), 100 | right: Some(right), 101 | } 102 | } 103 | } 104 | } 105 | 106 | /// Bounding Volume Hierarchies 107 | impl Hittable for BVH { 108 | fn hit(&self, ray: &Ray, unit_limit: Range) -> Option> { 109 | let bbox = self.bbox.as_ref()?; 110 | if !bbox.hit(ray, unit_limit.clone()) { 111 | return None; 112 | } 113 | 114 | let hit_left = self 115 | .left 116 | .as_ref() 117 | .and_then(|left| left.hit(ray, unit_limit.clone())); 118 | let hit_right = self.right.as_ref().and_then(|right| { 119 | let right_limit = unit_limit.start 120 | ..hit_left 121 | .as_ref() 122 | .map_or(unit_limit.end, |record| record.unit); 123 | right.hit(ray, right_limit) 124 | }); 125 | 126 | // Right has small t then left if it return `Some`, so right appear first 127 | hit_right.or(hit_left) 128 | } 129 | 130 | fn bbox(&self, _time_limit: Range) -> Option { 131 | self.bbox.clone() 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/hittable/collection/list.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | hittable::{Hittable, HitRecord}, 4 | prelude::*, 5 | }, 6 | std::{ 7 | fmt::{Debug, Formatter}, 8 | ops::Range, 9 | }, 10 | }; 11 | 12 | #[derive(Default)] 13 | pub struct HittableList { 14 | objects: Vec>, 15 | } 16 | 17 | impl Debug for HittableList { 18 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 19 | f.write_fmt(format_args!( 20 | "GeometryList {{ objects: {}}}", 21 | self.objects.len() 22 | )) 23 | } 24 | } 25 | 26 | impl HittableList { 27 | pub fn add(&mut self, object: G) -> &mut Self { 28 | let object: Box = Box::new(object); 29 | self.objects.push(object); 30 | self 31 | } 32 | 33 | pub fn add_ref(&mut self, object: Box) -> &mut Self { 34 | self.objects.push(object); 35 | self 36 | } 37 | 38 | pub fn clear(&mut self) { 39 | self.objects.clear(); 40 | } 41 | 42 | #[must_use] 43 | pub fn into_objects(self) -> Vec> { 44 | self.objects 45 | } 46 | } 47 | 48 | impl Hittable for HittableList { 49 | fn hit(&self, r: &Ray, unit_limit: Range) -> Option> { 50 | self.objects 51 | .iter() 52 | .filter_map(|object| object.hit(r, unit_limit.clone())) 53 | .min_by(|r1, r2| r1.unit.partial_cmp(&r2.unit).unwrap()) 54 | } 55 | 56 | fn bbox(&self, time_limit: Range) -> Option { 57 | if self.objects.is_empty() { 58 | return None; 59 | } 60 | 61 | let mut result: Option = None; 62 | 63 | for object in &self.objects { 64 | let bbox = object.bbox(time_limit.clone())?; 65 | result = result.map(|last| last | &bbox).or(Some(bbox)) 66 | } 67 | 68 | result 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/hittable/collection/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod bvh; 2 | pub(crate) mod list; 3 | pub(crate) mod world; 4 | 5 | pub use { 6 | bvh::BVH, 7 | list::HittableList, 8 | world::{default_background as world_default_background, World}, 9 | }; 10 | -------------------------------------------------------------------------------- /src/hittable/collection/world.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | hittable::{ 4 | collection::{HittableList, BVH}, 5 | HitRecord, Hittable, 6 | }, 7 | prelude::*, 8 | }, 9 | std::{ 10 | fmt::{Debug, Formatter}, 11 | ops::Range, 12 | }, 13 | }; 14 | 15 | #[must_use] 16 | pub fn default_background(ray: &Ray) -> Color { 17 | let unit = ray.direction.unit(); 18 | let t = 0.5 * (unit.y + 1.0); 19 | Color::new(1.0, 1.0, 1.0).gradient(&Color::new(0.5, 0.7, 1.0), t) 20 | } 21 | 22 | pub struct World { 23 | bvh: BVH, 24 | bg_func: Box Color + Send + Sync>, 25 | } 26 | 27 | impl Debug for World { 28 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 29 | f.write_str("World {}") 30 | } 31 | } 32 | 33 | impl World { 34 | #[must_use] 35 | pub fn new(list: HittableList, time_range: Range) -> Self { 36 | Self { 37 | bvh: BVH::new(list, time_range), 38 | bg_func: Box::new(default_background), 39 | } 40 | } 41 | 42 | pub fn set_bg(&mut self, f: F) 43 | where 44 | F: Fn(&Ray) -> Color + Send + Sync + 'static, 45 | { 46 | self.bg_func = Box::new(f); 47 | } 48 | 49 | #[must_use] 50 | pub fn background(&self, ray: &Ray) -> Color { 51 | let f = &self.bg_func; 52 | f(ray) 53 | } 54 | } 55 | 56 | impl Hittable for World { 57 | fn hit(&self, ray: &Ray, unit_limit: Range) -> Option> { 58 | self.bvh.hit(ray, unit_limit) 59 | } 60 | 61 | fn bbox(&self, time_limit: Range) -> Option { 62 | self.bvh.bbox(time_limit) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/hittable/geometry/carton.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | hittable::{collection::HittableList, AARect, AARectMetrics, Hittable, HitRecord}, 4 | material::Material, 5 | prelude::*, 6 | }, 7 | std::{ops::Range, sync::Arc}, 8 | }; 9 | 10 | #[derive(Debug)] 11 | pub struct Carton { 12 | point_min: Point3, 13 | point_max: Point3, 14 | material: Arc, 15 | faces: HittableList, 16 | } 17 | 18 | impl Clone for Carton { 19 | fn clone(&self) -> Self { 20 | Self::new_inner( 21 | self.point_min.clone(), 22 | self.point_max.clone(), 23 | Arc::clone(&self.material), 24 | ) 25 | } 26 | } 27 | 28 | impl Carton { 29 | #[allow(clippy::needless_pass_by_value)] // for api consistency 30 | pub fn new(p0: Point3, p1: Point3, material: M) -> Self { 31 | let point_min = Point3::new_min(&p0, &p1); 32 | let point_max = Point3::new_max(&p0, &p1); 33 | let shared_material = Arc::new(material); 34 | Self::new_inner(point_min, point_max, shared_material) 35 | } 36 | 37 | #[allow(clippy::too_many_lines)] 38 | fn new_inner(point_min: Point3, point_max: Point3, material: Arc) -> Self { 39 | let mut faces = HittableList::default(); 40 | faces 41 | .add(AARect::new_xy( 42 | // back 43 | AARectMetrics::new( 44 | point_min.z, 45 | (point_min.x, point_max.x), 46 | (point_min.y, point_max.y), 47 | ), 48 | Arc::clone(&material), 49 | )) 50 | .add(AARect::new_xy( 51 | // front 52 | AARectMetrics::new( 53 | point_max.z, 54 | (point_min.x, point_max.x), 55 | (point_min.y, point_max.y), 56 | ), 57 | Arc::clone(&material), 58 | )) 59 | .add(AARect::new_yz( 60 | // left 61 | AARectMetrics::new( 62 | point_min.x, 63 | (point_min.y, point_max.y), 64 | (point_min.z, point_max.z), 65 | ), 66 | Arc::clone(&material), 67 | )) 68 | .add(AARect::new_yz( 69 | // right 70 | AARectMetrics::new( 71 | point_max.x, 72 | (point_min.y, point_max.y), 73 | (point_min.z, point_max.z), 74 | ), 75 | Arc::clone(&material), 76 | )) 77 | .add(AARect::new_xz( 78 | // down 79 | AARectMetrics::new( 80 | point_min.y, 81 | (point_min.x, point_max.x), 82 | (point_min.z, point_max.z), 83 | ), 84 | Arc::clone(&material), 85 | )) 86 | .add(AARect::new_xz( 87 | // up 88 | AARectMetrics::new( 89 | point_max.y, 90 | (point_min.x, point_max.x), 91 | (point_min.z, point_max.z), 92 | ), 93 | Arc::clone(&material), 94 | )); 95 | 96 | Self { 97 | point_min, 98 | point_max, 99 | material, 100 | faces, 101 | } 102 | } 103 | } 104 | 105 | impl Hittable for Carton { 106 | fn hit(&self, ray: &Ray, unit_limit: Range) -> Option> { 107 | self.faces.hit(ray, unit_limit) 108 | } 109 | 110 | fn bbox(&self, time_limit: Range) -> Option { 111 | self.faces.bbox(time_limit) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/hittable/geometry/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod carton; 2 | pub(crate) mod rect; 3 | pub(crate) mod sphere; 4 | 5 | pub use { 6 | carton::Carton, 7 | rect::{AARect, AARectMetrics}, 8 | sphere::Sphere, 9 | }; 10 | -------------------------------------------------------------------------------- /src/hittable/geometry/rect.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | hittable::{Hittable, HitRecord}, 4 | material::Material, 5 | prelude::*, 6 | }, 7 | std::ops::Range, 8 | }; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct AARectMetrics { 12 | k: f64, 13 | a0: f64, 14 | a1: f64, 15 | b0: f64, 16 | b1: f64, 17 | a_len: f64, 18 | b_len: f64, 19 | } 20 | 21 | impl AARectMetrics { 22 | #[must_use] 23 | pub fn new(k: f64, (a0, a1): (f64, f64), (b0, b1): (f64, f64)) -> Self { 24 | assert!(a0 < a1); 25 | assert!(b0 < b1); 26 | Self { 27 | k, 28 | a0, 29 | a1, 30 | b0, 31 | b1, 32 | a_len: a1 - a0, 33 | b_len: b1 - b0, 34 | } 35 | } 36 | } 37 | 38 | #[derive(Debug, Clone)] 39 | pub struct AARect { 40 | // 0: a axis, 1: b axis, 2: fixed axis 41 | axis: (usize, usize, usize), 42 | metrics: AARectMetrics, 43 | material: M, 44 | } 45 | 46 | impl AARect { 47 | pub const fn new_xy(metrics: AARectMetrics, material: M) -> Self { 48 | Self { 49 | metrics, 50 | material, 51 | axis: (0, 1, 2), 52 | } 53 | } 54 | 55 | pub const fn new_xz(metrics: AARectMetrics, material: M) -> Self { 56 | Self { 57 | metrics, 58 | material, 59 | axis: (0, 2, 1), 60 | } 61 | } 62 | 63 | pub const fn new_yz(metrics: AARectMetrics, material: M) -> Self { 64 | Self { 65 | metrics, 66 | material, 67 | axis: (1, 2, 0), 68 | } 69 | } 70 | } 71 | 72 | impl Hittable for AARect { 73 | fn normal(&self, _point: &Point3) -> Vec3 { 74 | let mut n = Vec3::default(); 75 | n[self.axis.2] = 1.0; 76 | n 77 | } 78 | 79 | fn material(&self) -> &dyn Material { 80 | &self.material 81 | } 82 | 83 | fn uv(&self, point: &Point3) -> (f64, f64) { 84 | ( 85 | (point[self.axis.0] - self.metrics.a0) / self.metrics.a_len, 86 | (point[self.axis.1] - self.metrics.b0) / self.metrics.b_len, 87 | ) 88 | } 89 | 90 | fn hit(&self, ray: &Ray, unit_limit: Range) -> Option> { 91 | let unit = (self.metrics.k - ray.origin[self.axis.2]) / ray.direction[self.axis.2]; 92 | if !unit_limit.contains(&unit) { 93 | return None; 94 | } 95 | 96 | let a = unit.mul_add(ray.direction[self.axis.0], ray.origin[self.axis.0]); 97 | 98 | if a < self.metrics.a0 || a > self.metrics.a1 { 99 | return None; 100 | } 101 | 102 | let b = unit.mul_add(ray.direction[self.axis.1], ray.origin[self.axis.1]); 103 | 104 | if b < self.metrics.b0 || b > self.metrics.b1 { 105 | return None; 106 | } 107 | 108 | Some(HitRecord::new(ray, self, unit)) 109 | } 110 | 111 | fn bbox(&self, _time_limit: Range) -> Option { 112 | let mut p0 = Point3::default(); 113 | p0[self.axis.0] = self.metrics.a0; 114 | p0[self.axis.1] = self.metrics.b0; 115 | p0[self.axis.2] = self.metrics.k - 0.0001; 116 | 117 | let mut p1 = Point3::default(); 118 | p1[self.axis.0] = self.metrics.a1; 119 | p1[self.axis.1] = self.metrics.b1; 120 | p1[self.axis.2] = self.metrics.k + 0.0001; 121 | 122 | Some(AABB::new(p0, p1)) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/hittable/geometry/sphere.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | hittable::{HitRecord, Hittable}, 4 | material::Material, 5 | prelude::*, 6 | }, 7 | std::{ 8 | fmt::{Debug, Formatter}, 9 | ops::Range, 10 | }, 11 | }; 12 | 13 | #[derive(Clone)] 14 | pub struct Sphere { 15 | center: Point3, 16 | radius: f64, 17 | speed: Vec3, 18 | material: M, 19 | radius_squared: f64, 20 | } 21 | 22 | impl Debug for Sphere { 23 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 24 | f.write_fmt(format_args!( 25 | "Sphere {{ center: {:?}, radius: {}, speed: {:?} }}", 26 | self.center, self.radius, self.speed, 27 | )) 28 | } 29 | } 30 | 31 | impl Sphere { 32 | pub fn new(center: Point3, radius: f64, material: M) -> Self { 33 | Self { 34 | center, 35 | radius, 36 | material, 37 | speed: Vec3::default(), 38 | radius_squared: radius * radius, 39 | } 40 | } 41 | 42 | pub const fn with_speed(mut self, speed: Vec3) -> Self { 43 | self.speed = speed; 44 | self 45 | } 46 | 47 | pub fn center_at(&self, t: f64) -> Point3 { 48 | &self.center + &self.speed * t 49 | } 50 | } 51 | 52 | impl Hittable for Sphere { 53 | fn normal(&self, point: &Point3) -> crate::prelude::Vec3 { 54 | (point - &self.center) / self.radius 55 | } 56 | 57 | fn material(&self) -> &dyn Material { 58 | &self.material 59 | } 60 | 61 | fn uv(&self, point: &Point3) -> (f64, f64) { 62 | let point = (point - &self.center).unit(); 63 | let phi = (-point.z).atan2(point.x); // [-pi, pi] 64 | let theta = point.y.asin(); // [-pi / 2 , pi / 2] 65 | let u = phi / 2.0 / PI + 0.5; 66 | let v = theta / PI + 0.5; 67 | (u, v) 68 | } 69 | 70 | // Ray(t) = O + tD 71 | // Sphere surface = (X - C)^2 = r^2 72 | // (O + tD - C)^2 = r^2 73 | // let O - C = L 74 | // (tD + L)^2 = r^2 75 | // D^2 t^2 + 2DLt + L^2- r^2 = 0 76 | // a = D^2, b = 2(DL), c = L^2 - r^2 77 | // Delta = b^2 - 4ac = 4(DL)^2 - 4 D^2 (L^2 - r2) 78 | // So, check (DL)^2 - D^2(L^2 - r^2) 79 | // root is 80 | fn hit(&self, ray: &Ray, unit_limit: Range) -> Option> { 81 | let current_center = self.center_at(ray.departure_time); 82 | let l = &ray.origin - current_center; 83 | let half_b = ray.direction.dot(&l); 84 | let a = ray.direction.length_squared(); 85 | let c = l.length_squared() - self.radius_squared; 86 | #[allow(clippy::suspicious_operation_groupings)] 87 | let delta = half_b * half_b - a * c; 88 | 89 | if delta < 0.0 { 90 | return None; 91 | } 92 | 93 | let sqrt = delta.sqrt(); 94 | 95 | let t1 = (-half_b - sqrt) / a; 96 | if unit_limit.contains(&t1) { 97 | return Some(HitRecord::new(ray, self, t1)); 98 | } 99 | 100 | let t2 = (-half_b + sqrt) / a; 101 | if unit_limit.contains(&t2) { 102 | return Some(HitRecord::new(ray, self, t2)); 103 | } 104 | 105 | None 106 | } 107 | 108 | fn bbox(&self, time_limit: Range) -> Option { 109 | Some( 110 | if self.speed.x == 0.0 && self.speed.y == 0.0 && self.speed.z == 0.0 { 111 | AABB::new( 112 | &self.center - Vec3::new(self.radius, self.radius, self.radius), 113 | &self.center + Vec3::new(self.radius, self.radius, self.radius), 114 | ) 115 | } else { 116 | let start = AABB::new( 117 | self.center_at(time_limit.start) 118 | - Vec3::new(self.radius, self.radius, self.radius), 119 | self.center_at(time_limit.start) 120 | + Vec3::new(self.radius, self.radius, self.radius), 121 | ); 122 | 123 | let end = AABB::new( 124 | self.center_at(time_limit.end) 125 | - Vec3::new(self.radius, self.radius, self.radius), 126 | self.center_at(time_limit.end) 127 | + Vec3::new(self.radius, self.radius, self.radius), 128 | ); 129 | 130 | start | end 131 | }, 132 | ) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/hittable/hit.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{material::Material, prelude::*}, 3 | std::{ 4 | fmt::{Debug, Formatter}, 5 | ops::Range, 6 | }, 7 | }; 8 | 9 | pub struct HitRecord<'m> { 10 | pub point: Point3, 11 | pub normal: Vec3, 12 | pub material: &'m dyn Material, 13 | pub unit: f64, 14 | pub u: f64, 15 | pub v: f64, 16 | pub outside: bool, 17 | } 18 | 19 | impl Debug for HitRecord<'_> { 20 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 21 | f.write_fmt(format_args!( 22 | "HitRecord {{ unit: {}, hit: {:?}, normal: {:?}, outside: {} }}", 23 | self.unit, self.point, self.normal, self.outside 24 | )) 25 | } 26 | } 27 | 28 | impl<'m> HitRecord<'m> { 29 | pub fn new(r: &Ray, object: &'m G, unit: f64) -> Self { 30 | let point = r.position_after(unit); 31 | let mut normal = object.normal(&point); 32 | let outside = r.direction.dot(&normal) < 0.0; 33 | if !outside { 34 | normal.reverse(); 35 | } 36 | let material = object.material(); 37 | let (u, v) = object.uv(&point); 38 | Self { 39 | point, 40 | normal, 41 | material, 42 | unit, 43 | u, 44 | v, 45 | outside, 46 | } 47 | } 48 | } 49 | 50 | #[allow(unused_variables)] 51 | pub trait Hittable: Send + Sync { 52 | fn normal(&self, point: &Point3) -> Vec3 { 53 | unimplemented!( 54 | "{}'s normal function should not be called directly", 55 | std::any::type_name::() 56 | ) 57 | } 58 | fn material(&self) -> &dyn Material { 59 | unimplemented!( 60 | "{}'s material function should not be called directly", 61 | std::any::type_name::() 62 | ) 63 | } 64 | fn uv(&self, point: &Point3) -> (f64, f64) { 65 | unimplemented!( 66 | "{}'s uv function should not be called directly", 67 | std::any::type_name::() 68 | ) 69 | } 70 | fn hit(&self, ray: &Ray, unit_limit: Range) -> Option>; 71 | fn bbox(&self, time_limit: Range) -> Option; 72 | } 73 | -------------------------------------------------------------------------------- /src/hittable/medium/constant.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | hittable::{HitRecord, Hittable}, 3 | material::Isotropic, 4 | prelude::*, 5 | }; 6 | 7 | #[derive(Debug)] 8 | pub struct ConstantMedium { 9 | boundary: T, 10 | material: Isotropic, 11 | density: f64, 12 | neg_inv_density: f64, 13 | } 14 | 15 | impl ConstantMedium { 16 | #[must_use] 17 | pub fn new(boundary: T, color: Color, density: f64) -> Self { 18 | Self { 19 | boundary, 20 | material: Isotropic::new(color), 21 | density, 22 | neg_inv_density: -1.0 / density, 23 | } 24 | } 25 | } 26 | 27 | impl Hittable for ConstantMedium { 28 | fn hit( 29 | &self, ray: &Ray, unit_limit: std::ops::Range, 30 | ) -> Option> { 31 | let mut rec1 = self.boundary.hit(ray, f64::NEG_INFINITY..f64::INFINITY)?; 32 | let mut rec2 = self.boundary.hit(ray, rec1.unit + 0.0001..f64::INFINITY)?; 33 | if rec1.unit < unit_limit.start { 34 | rec1.unit = unit_limit.start; 35 | } 36 | if rec2.unit > unit_limit.end { 37 | rec2.unit = unit_limit.end; 38 | } 39 | if rec1.unit >= rec2.unit { 40 | return None; 41 | } 42 | if rec1.unit < 0.0 { 43 | rec1.unit = 0.0; 44 | } 45 | 46 | let length_per_unit = ray.direction.length(); 47 | let distance_inside = (rec2.unit - rec1.unit) * length_per_unit; 48 | let hit_distance = self.neg_inv_density * Random::normal().ln(); 49 | 50 | if hit_distance > distance_inside { 51 | return None; 52 | } 53 | 54 | let hit_point_unit = rec1.unit + hit_distance / length_per_unit; 55 | 56 | Some(HitRecord { 57 | point: ray.position_after(hit_point_unit), 58 | normal: Vec3::new(1.0, 0.0, 0.0), // useless, 59 | material: &self.material, 60 | unit: hit_point_unit, 61 | u: 0.0, // useless 62 | v: 0.0, // useless 63 | outside: false, // useless 64 | }) 65 | } 66 | 67 | fn bbox(&self, time_limit: std::ops::Range) -> Option { 68 | self.boundary.bbox(time_limit) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/hittable/medium/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod constant; 2 | 3 | pub use constant::ConstantMedium; 4 | -------------------------------------------------------------------------------- /src/hittable/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod hit; 2 | 3 | pub mod collection; 4 | pub mod geometry; 5 | pub mod medium; 6 | pub mod transform; 7 | 8 | pub use { 9 | geometry::{AARect, AARectMetrics, Carton, Sphere}, 10 | hit::{HitRecord, Hittable}, 11 | }; 12 | -------------------------------------------------------------------------------- /src/hittable/transform/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod rotation; 2 | pub(crate) mod translation; 3 | 4 | pub use { 5 | rotation::{AARotation, ByXAxis, ByYAxis, ByZAxis}, 6 | translation::Translation, 7 | }; 8 | -------------------------------------------------------------------------------- /src/hittable/transform/rotation.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | hittable::{HitRecord, Hittable}, 4 | prelude::*, 5 | }, 6 | once_cell::sync::OnceCell, 7 | std::marker::PhantomData, 8 | }; 9 | 10 | pub trait RotationByAxis: Send + Sync { 11 | fn rotate(point: &Point3, radian: f64) -> Point3; 12 | } 13 | 14 | #[derive(Debug)] 15 | pub struct ByXAxis; 16 | 17 | impl RotationByAxis for ByXAxis { 18 | fn rotate(point: &Point3, radian: f64) -> Point3 { 19 | Point3::new( 20 | point.x, 21 | radian.cos() * point.y - radian.sin() * point.z, 22 | radian.sin().mul_add(point.y, radian.cos() * point.z), 23 | ) 24 | } 25 | } 26 | 27 | #[derive(Debug)] 28 | pub struct ByYAxis; 29 | 30 | impl RotationByAxis for ByYAxis { 31 | fn rotate(point: &Point3, radian: f64) -> Point3 { 32 | Point3::new( 33 | radian.cos().mul_add(point.x, radian.sin() * point.z), 34 | point.y, 35 | (-radian.sin()).mul_add(point.x, radian.cos() * point.z), 36 | ) 37 | } 38 | } 39 | 40 | #[derive(Debug)] 41 | pub struct ByZAxis; 42 | 43 | impl RotationByAxis for ByZAxis { 44 | fn rotate(point: &Point3, radian: f64) -> Point3 { 45 | Point3::new( 46 | radian.cos() * point.x - radian.sin() * point.y, 47 | radian.sin().mul_add(point.x, radian.cos() * point.y), 48 | point.z, 49 | ) 50 | } 51 | } 52 | 53 | #[derive(Debug)] 54 | pub struct AARotation { 55 | object: T, 56 | radian: f64, 57 | bbox_cache: OnceCell>, 58 | axes: PhantomData, 59 | } 60 | 61 | impl AARotation { 62 | pub fn new(object: T, angle: f64) -> Self { 63 | Self { 64 | object, 65 | radian: angle.to_radians(), 66 | bbox_cache: OnceCell::new(), 67 | axes: PhantomData, 68 | } 69 | } 70 | } 71 | 72 | impl Hittable for AARotation { 73 | fn hit(&self, ray: &Ray, unit_limit: std::ops::Range) -> Option> { 74 | let rotated_origin = Axis::rotate(&ray.origin, -self.radian); 75 | let rotated_direction = Axis::rotate(&ray.direction, -self.radian); 76 | let rotated_ray = Ray::new(rotated_origin, rotated_direction, ray.departure_time); 77 | self.object.hit(&rotated_ray, unit_limit).map(|mut record| { 78 | record.point = Axis::rotate(&record.point, self.radian); 79 | record.normal = Axis::rotate(&record.normal, self.radian); 80 | record 81 | }) 82 | } 83 | 84 | #[allow(clippy::cast_precision_loss)] // 0, 1 is small enough 85 | fn bbox(&self, time_limit: std::ops::Range) -> Option { 86 | self.bbox_cache 87 | .get_or_init(|| { 88 | self.object.bbox(time_limit).map(|bbox| { 89 | let mut point_min = Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY); 90 | let mut point_max = 91 | Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY); 92 | 93 | for i in 0..2_usize { 94 | for j in 0..2_usize { 95 | for k in 0..2_usize { 96 | let x = 97 | (i as f64).mul_add(bbox.max().x, (1 - i) as f64 * bbox.min().x); 98 | let y = 99 | (j as f64).mul_add(bbox.max().y, (1 - j) as f64 * bbox.min().y); 100 | let z = 101 | (k as f64).mul_add(bbox.max().z, (1 - k) as f64 * bbox.min().z); 102 | 103 | let rotated_point = 104 | Axis::rotate(&Point3::new(x, y, z), self.radian); 105 | 106 | for c in 0..3 { 107 | point_min[c] = point_min[c].min(rotated_point[c]); 108 | point_max[c] = point_max[c].max(rotated_point[c]); 109 | } 110 | } 111 | } 112 | } 113 | 114 | AABB::new(point_min, point_max) 115 | }) 116 | }) 117 | .clone() 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/hittable/transform/translation.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | hittable::{HitRecord, Hittable}, 3 | prelude::*, 4 | }; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct Translation { 8 | object: T, 9 | movement: Vec3, 10 | } 11 | 12 | impl Translation { 13 | pub const fn new(object: T, movement: Vec3) -> Self { 14 | Self { object, movement } 15 | } 16 | } 17 | 18 | impl Hittable for Translation { 19 | fn hit(&self, ray: &Ray, unit_limit: std::ops::Range) -> Option> { 20 | let moved_ray = Ray::new( 21 | &ray.origin - &self.movement, 22 | ray.direction.clone(), 23 | ray.departure_time, 24 | ); 25 | self.object.hit(&moved_ray, unit_limit).map(|mut record| { 26 | record.point += &self.movement; 27 | record 28 | }) 29 | } 30 | 31 | fn bbox(&self, time_limit: std::ops::Range) -> Option { 32 | self.object 33 | .bbox(time_limit) 34 | .map(|bbox| AABB::new(bbox.min() + &self.movement, bbox.max() + &self.movement)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/internal/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod rayon_seq_iter; 2 | -------------------------------------------------------------------------------- /src/internal/rayon_seq_iter.rs: -------------------------------------------------------------------------------- 1 | use { 2 | rayon::iter::{IndexedParallelIterator, ParallelIterator}, 3 | std::{cmp::Ordering, collections::BinaryHeap, sync::mpsc::channel}, 4 | }; 5 | 6 | #[derive(Debug)] 7 | struct ReverseFirst(usize, T); 8 | 9 | impl PartialEq for ReverseFirst { 10 | fn eq(&self, rhs: &Self) -> bool { 11 | rhs.0.eq(&self.0) 12 | } 13 | } 14 | 15 | impl Eq for ReverseFirst {} 16 | 17 | impl PartialOrd for ReverseFirst { 18 | fn partial_cmp(&self, rhs: &Self) -> Option { 19 | rhs.0.partial_cmp(&self.0) 20 | } 21 | } 22 | 23 | impl Ord for ReverseFirst { 24 | fn cmp(&self, o: &Self) -> Ordering { 25 | o.0.cmp(&self.0) 26 | } 27 | } 28 | 29 | // TODO: Write document for this complex but useful function 30 | pub trait SeqForEach: IndexedParallelIterator { 31 | fn seq_for_each_with(self, setup: SetUp, mut f: F) -> Result<(), E> 32 | where 33 | SetUp: FnOnce() -> Result + Send, 34 | F: FnMut(&mut SetUpRet, Self::Item) -> Result<(), E> + Send, 35 | { 36 | let (tx, rx) = channel(); 37 | let mut ret = Ok(()); 38 | 39 | rayon::scope(|s| { 40 | s.spawn(|_| { 41 | self.enumerate().for_each_with(tx, |sender, idx_and_item| { 42 | let _ = sender.send(idx_and_item); 43 | }) 44 | }); 45 | s.spawn(|_| { 46 | let mut context = match setup() { 47 | Ok(context) => context, 48 | Err(e) => { 49 | ret = Err(e); 50 | return; 51 | } 52 | }; 53 | let mut expected = 0; 54 | let mut heap = BinaryHeap::new(); 55 | for (idx, item) in rx { 56 | heap.push(ReverseFirst(idx, item)); 57 | while let Some(&ReverseFirst(idx, _)) = heap.peek() { 58 | if idx != expected { 59 | break; 60 | } 61 | let ReverseFirst(_idx, item) = heap.pop().unwrap(); 62 | if let e @ Err(_) = f(&mut context, item) { 63 | ret = e; 64 | return; 65 | } 66 | expected += 1; 67 | } 68 | } 69 | }); 70 | }); 71 | 72 | ret 73 | } 74 | } 75 | 76 | impl SeqForEach for P {} 77 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::all, clippy::pedantic, clippy::nursery)] 2 | #![deny(missing_debug_implementations, rust_2018_idioms)] 3 | #![deny(warnings)] 4 | #![allow(clippy::module_name_repetitions, clippy::cast_possible_truncation)] 5 | 6 | pub mod camera; 7 | pub mod hittable; 8 | pub mod material; 9 | pub mod painter; 10 | pub mod prelude; 11 | pub mod texture; 12 | 13 | mod internal; 14 | -------------------------------------------------------------------------------- /src/material/dielectric.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | hittable::HitRecord, 4 | material::{reflect, Material, ScatterRecord}, 5 | prelude::*, 6 | }, 7 | std::fmt::Debug, 8 | }; 9 | 10 | pub trait ReflectProbabilityCurve: Debug + Send + Sync { 11 | fn reflect_prob(&self, cos_theta: f64, refractive: f64) -> f64; 12 | } 13 | 14 | #[derive(Debug)] 15 | pub struct Glass {} 16 | 17 | impl ReflectProbabilityCurve for Glass { 18 | fn reflect_prob(&self, cos_theta: f64, refractive: f64) -> f64 { 19 | let r0 = (1.0 - refractive) / (1.0 + refractive); 20 | let r0 = r0 * r0; 21 | (1.0 - r0).mul_add((1.0 - cos_theta).powi(5), r0) 22 | } 23 | } 24 | 25 | #[derive(Debug)] 26 | pub struct Dielectric { 27 | color: Color, 28 | enter_refractive: f64, 29 | outer_refractive: f64, 30 | reflect_curve: Option>, 31 | } 32 | 33 | impl Dielectric { 34 | #[must_use] 35 | pub fn new(color: Color, refractive: f64) -> Self { 36 | let enter_refractive = 1.0 / refractive; 37 | let outer_refractive = refractive; 38 | Self { 39 | color, 40 | enter_refractive, 41 | outer_refractive, 42 | reflect_curve: None, 43 | } 44 | } 45 | 46 | pub fn reflect_curve(mut self, reflect_curve: R) -> Self { 47 | self.reflect_curve = Some(Box::new(reflect_curve)); 48 | self 49 | } 50 | 51 | fn refract(&self, ray: &Ray, hit: &HitRecord<'_>) -> Option { 52 | let dir = ray.direction.unit(); 53 | let cos_theta = (-&dir).dot(&hit.normal); 54 | let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); 55 | let refractive = if hit.outside { 56 | self.enter_refractive 57 | } else { 58 | self.outer_refractive 59 | }; 60 | if refractive * sin_theta > 1.0 { 61 | return None; 62 | } 63 | let reflect_prob = self 64 | .reflect_curve 65 | .as_ref() 66 | .map_or(0.0, |r| r.reflect_prob(cos_theta, refractive)); 67 | if Random::normal() < reflect_prob { 68 | return None; 69 | } 70 | let r_parallel = refractive * (&dir + cos_theta * &hit.normal); 71 | let r_perpendicular = -(1.0 - r_parallel.length_squared()).sqrt() * &hit.normal; 72 | let r = r_parallel + r_perpendicular; 73 | Some(Ray::new(hit.point.clone(), r, ray.departure_time)) 74 | } 75 | } 76 | 77 | impl Material for Dielectric { 78 | fn scatter(&self, ray: &Ray, hit: HitRecord<'_>) -> Option { 79 | let refract = self 80 | .refract(ray, &hit) 81 | .unwrap_or_else(|| reflect(ray, &hit)); 82 | Some(ScatterRecord { 83 | color: self.color.clone(), 84 | ray: refract, 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/material/isotropic.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | material::{Material, ScatterRecord}, 3 | prelude::*, 4 | }; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct Isotropic { 8 | color: Color, 9 | } 10 | 11 | impl Isotropic { 12 | #[must_use] 13 | pub const fn new(color: Color) -> Self { 14 | Self { color } 15 | } 16 | } 17 | 18 | impl Material for Isotropic { 19 | fn scatter( 20 | &self, ray: &Ray, hit: crate::hittable::HitRecord<'_>, 21 | ) -> Option { 22 | let scattered_ray = Ray::new(hit.point, Vec3::random_in_unit_sphere(), ray.departure_time); 23 | Some(ScatterRecord { 24 | ray: scattered_ray, 25 | color: self.color.clone(), 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/material/lambertian.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | hittable::HitRecord, 3 | material::{Material, ScatterRecord}, 4 | prelude::*, 5 | texture::Texture, 6 | }; 7 | 8 | #[derive(Debug, Clone)] 9 | pub enum LambertianMathType { 10 | Approximates, 11 | True, 12 | Hemisphere, 13 | } 14 | 15 | impl LambertianMathType { 16 | #[must_use] 17 | pub fn scatter_ray(&self, ray: &Ray, hit: HitRecord<'_>) -> Ray { 18 | match self { 19 | Self::Approximates => Ray::new( 20 | hit.point, 21 | hit.normal + Vec3::random_in_unit_sphere(), 22 | ray.departure_time, 23 | ), 24 | Self::True => Ray::new( 25 | hit.point, 26 | hit.normal + Vec3::random_unit(), 27 | ray.departure_time, 28 | ), 29 | Self::Hemisphere => Ray::new( 30 | hit.point, 31 | Vec3::random_unit_dir(&hit.normal), 32 | ray.departure_time, 33 | ), 34 | } 35 | } 36 | } 37 | 38 | #[derive(Debug, Clone)] 39 | pub struct Lambertian { 40 | texture: T, 41 | math_type: LambertianMathType, 42 | } 43 | 44 | impl Lambertian { 45 | #[must_use] 46 | pub fn new(texture: T) -> Self { 47 | Self { 48 | texture, 49 | math_type: LambertianMathType::True, 50 | } 51 | } 52 | 53 | #[must_use] 54 | pub fn math_type(mut self, value: LambertianMathType) -> Self { 55 | self.math_type = value; 56 | self 57 | } 58 | } 59 | 60 | impl Material for Lambertian { 61 | fn scatter(&self, ray: &Ray, hit: HitRecord<'_>) -> Option { 62 | let color = self.texture.color(hit.u, hit.v, &hit.point); 63 | let new_ray = self.math_type.scatter_ray(ray, hit); 64 | Some(ScatterRecord { 65 | color, 66 | ray: new_ray, 67 | }) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/material/light.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | hittable::HitRecord, 3 | material::{Material, ScatterRecord}, 4 | prelude::*, 5 | texture::Texture, 6 | }; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct DiffuseLight { 10 | texture: T, 11 | multiplier: f64, 12 | } 13 | 14 | impl DiffuseLight { 15 | pub const fn new(texture: T) -> Self { 16 | Self { 17 | texture, 18 | multiplier: 1.0, 19 | } 20 | } 21 | 22 | pub const fn multiplier(mut self, multiplier: f64) -> Self { 23 | self.multiplier = multiplier; 24 | self 25 | } 26 | } 27 | 28 | impl Material for DiffuseLight { 29 | fn scatter(&self, _ray: &Ray, _hit: HitRecord<'_>) -> Option { 30 | None 31 | } 32 | 33 | fn emitted(&self, u: f64, v: f64, point: &Point3) -> Option { 34 | Some(>::into(self.texture.color(u, v, point)) * self.multiplier) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/material/metal.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | hittable::HitRecord, 3 | material::{Material, ScatterRecord}, 4 | prelude::*, 5 | texture::Texture, 6 | }; 7 | 8 | #[derive(Debug)] 9 | pub struct Metal { 10 | texture: T, 11 | fuzz: f64, 12 | } 13 | 14 | impl Metal { 15 | #[must_use] 16 | pub fn new(texture: T) -> Self { 17 | Self { texture, fuzz: 0.0 } 18 | } 19 | 20 | #[must_use] 21 | pub fn fuzz(mut self, fuzz: f64) -> Self { 22 | self.fuzz = clamp(fuzz.abs(), 0.0..=1.0); 23 | self 24 | } 25 | 26 | fn reflect(&self, ray: &Ray, hit: &HitRecord<'_>) -> Ray { 27 | let dir = ray.direction.unit(); 28 | let mut reflected_dir = &dir - 2.0 * dir.dot(&hit.normal) * &hit.normal; 29 | reflected_dir += self.fuzz * Vec3::random_in_unit_sphere(); 30 | Ray::new(hit.point.clone(), reflected_dir, ray.departure_time) 31 | } 32 | } 33 | 34 | impl Material for Metal { 35 | fn scatter(&self, ray: &Ray, hit: HitRecord<'_>) -> Option { 36 | let color = self.texture.color(hit.u, hit.v, &hit.point); 37 | let reflected = self.reflect(ray, &hit); 38 | if reflected.direction.dot(&hit.normal) > 0.0 { 39 | Some(ScatterRecord { 40 | color, 41 | ray: reflected, 42 | }) 43 | } else { 44 | None 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/material/mod.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{hittable::HitRecord, prelude::*}, 3 | std::sync::Arc, 4 | }; 5 | 6 | pub(crate) mod dielectric; 7 | pub(crate) mod isotropic; 8 | pub(crate) mod lambertian; 9 | pub(crate) mod light; 10 | pub(crate) mod metal; 11 | 12 | pub use { 13 | dielectric::{Dielectric, Glass}, 14 | isotropic::Isotropic, 15 | lambertian::{Lambertian, LambertianMathType}, 16 | light::DiffuseLight, 17 | metal::Metal, 18 | }; 19 | 20 | #[derive(Debug)] 21 | pub struct ScatterRecord { 22 | pub color: Color, 23 | pub ray: Ray, 24 | } 25 | 26 | pub trait Material: Send + Sync { 27 | fn scatter(&self, ray: &Ray, hit: HitRecord<'_>) -> Option; 28 | #[allow(unused_variables)] 29 | fn emitted(&self, u: f64, v: f64, point: &Point3) -> Option { 30 | None 31 | } 32 | } 33 | 34 | impl Material for Arc { 35 | fn scatter(&self, ray: &Ray, hit: HitRecord<'_>) -> Option { 36 | self.as_ref().scatter(ray, hit) 37 | } 38 | 39 | fn emitted(&self, u: f64, v: f64, point: &Point3) -> Option { 40 | self.as_ref().emitted(u, v, point) 41 | } 42 | } 43 | 44 | pub(crate) fn reflect(ray: &Ray, hit: &HitRecord<'_>) -> Ray { 45 | let dir = ray.direction.unit(); 46 | let reflected_dir = &dir - 2.0 * dir.dot(&hit.normal) * &hit.normal; 47 | Ray::new(hit.point.clone(), reflected_dir, ray.departure_time) 48 | } 49 | -------------------------------------------------------------------------------- /src/painter.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{internal::rayon_seq_iter::SeqForEach, prelude::*}, 3 | log::info, 4 | rayon::{prelude::*, ThreadPool, ThreadPoolBuilder}, 5 | std::{ 6 | fs::File, 7 | io::{BufWriter, Write}, 8 | iter::FromIterator, 9 | ops::{Index, IndexMut}, 10 | path::Path, 11 | sync::atomic::{AtomicBool, AtomicUsize, Ordering}, 12 | }, 13 | }; 14 | 15 | #[derive(Debug)] 16 | pub struct PPMImage { 17 | width: usize, 18 | height: usize, 19 | colors: Vec, 20 | } 21 | 22 | impl PPMImage { 23 | #[must_use] 24 | pub fn new(width: usize, height: usize) -> Self { 25 | let colors = vec![Color::default(); width * height]; 26 | Self { 27 | width, 28 | height, 29 | colors, 30 | } 31 | } 32 | 33 | /// # Errors 34 | /// When open or write to file failed 35 | pub fn save>(&self, path: P) -> std::io::Result<()> { 36 | let mut file = File::create(path)?; 37 | write!( 38 | &mut file, 39 | "P3\n{width} {height}\n255\n", 40 | width = self.width, 41 | height = self.height 42 | )?; 43 | 44 | for row in 0..self.height { 45 | for column in 0..self.width { 46 | let index = row * self.width + column; 47 | let color = &self.colors[index].i(); 48 | writeln!( 49 | &mut file, 50 | "{r} {g} {b}", 51 | r = color.r, 52 | g = color.g, 53 | b = color.b 54 | )?; 55 | } 56 | } 57 | 58 | Ok(()) 59 | } 60 | 61 | /// # Errors 62 | /// 63 | /// When image pixel count is not divisible by new width 64 | pub fn reshape(&mut self, width: usize) -> Result<(), &'static str> { 65 | if self.colors.len() % width == 0 { 66 | self.width = width; 67 | self.height = self.colors.len() / width; 68 | Ok(()) 69 | } else { 70 | Err("Shape invalid") 71 | } 72 | } 73 | } 74 | 75 | impl FromIterator for PPMImage { 76 | fn from_iter>(iter: T) -> Self { 77 | Vec::from_iter(iter).into() 78 | } 79 | } 80 | 81 | impl From for PPMImage 82 | where 83 | T: Into>, 84 | { 85 | fn from(container: T) -> Self { 86 | let colors = container.into(); 87 | Self { 88 | height: 1, 89 | width: colors.len(), 90 | colors, 91 | } 92 | } 93 | } 94 | 95 | impl Index<(usize, usize)> for PPMImage { 96 | type Output = Color; 97 | fn index(&self, (row, col): (usize, usize)) -> &Self::Output { 98 | self.index(row * self.width + col) 99 | } 100 | } 101 | 102 | impl Index for PPMImage { 103 | type Output = Color; 104 | fn index(&self, index: usize) -> &Self::Output { 105 | self.colors.index(index) 106 | } 107 | } 108 | 109 | impl IndexMut<(usize, usize)> for PPMImage { 110 | fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut Self::Output { 111 | self.index_mut(row * self.width + col) 112 | } 113 | } 114 | 115 | impl IndexMut for PPMImage { 116 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 117 | self.colors.index_mut(index) 118 | } 119 | } 120 | 121 | #[derive(Debug)] 122 | pub struct Painter { 123 | pub width: usize, 124 | pub height: usize, 125 | samples: usize, 126 | gamma: bool, 127 | threads: usize, 128 | parallel: bool, 129 | } 130 | 131 | struct PainterOutputContext<'c> { 132 | file: BufWriter>, 133 | cancel: &'c AtomicBool, 134 | } 135 | 136 | impl Painter { 137 | #[must_use] 138 | pub const fn new(width: usize, height: usize) -> Self { 139 | Self { 140 | width, 141 | height, 142 | gamma: true, 143 | samples: 50, 144 | threads: 0, 145 | parallel: true, 146 | } 147 | } 148 | 149 | #[must_use] 150 | pub const fn gamma(mut self, gamma: bool) -> Self { 151 | self.gamma = gamma; 152 | self 153 | } 154 | 155 | #[must_use] 156 | pub const fn samples(mut self, samples: usize) -> Self { 157 | self.samples = samples; 158 | self 159 | } 160 | 161 | #[must_use] 162 | pub const fn threads(mut self, threads: usize) -> Self { 163 | self.threads = threads; 164 | self 165 | } 166 | 167 | #[must_use] 168 | pub const fn parallel(mut self, parallel: bool) -> Self { 169 | self.parallel = parallel; 170 | self 171 | } 172 | 173 | #[allow(clippy::cast_precision_loss)] // because row and column is small enough in practice 174 | fn calculate_uv(&self, row: usize, column: usize) -> (f64, f64) { 175 | if self.samples == 1 { 176 | let u = (column as f64) / self.width as f64; 177 | let v = ((self.height - 1 - row) as f64) / self.height as f64; 178 | (u, v) 179 | } else { 180 | let u = (column as f64 + Random::normal()) / self.width as f64; 181 | let v = ((self.height - 1 - row) as f64 + Random::normal()) / self.height as f64; 182 | (u, v) 183 | } 184 | } 185 | 186 | fn create_output_file( 187 | &self, path: Option<&Path>, 188 | ) -> std::io::Result>> { 189 | let mut file: BufWriter> = if let Some(path) = path { 190 | BufWriter::new(Box::new(File::create(&path)?)) 191 | } else { 192 | BufWriter::new(Box::new(std::io::sink())) 193 | }; 194 | 195 | write!( 196 | &mut file, 197 | "P3\n{width} {height}\n255\n", 198 | width = self.width, 199 | height = self.height 200 | )?; 201 | 202 | Ok(file) 203 | } 204 | 205 | fn create_output_context<'c>( 206 | &self, path: Option<&Path>, cancel: &'c AtomicBool, 207 | ) -> std::io::Result> { 208 | let file = self.create_output_file(path)?; 209 | Ok(PainterOutputContext { file, cancel }) 210 | } 211 | 212 | // TODO: make it return RGBInt type 213 | fn render_pixel(&self, row: usize, column: usize, uv_color: &F) -> (u8, u8, u8) 214 | where 215 | F: Fn(f64, f64) -> Vec3 + Send + Sync, 216 | { 217 | let color: Vec3 = (0..self.samples) 218 | .map(|_| { 219 | let (u, v) = self.calculate_uv(row, column); 220 | uv_color(u, v) 221 | }) 222 | .sum(); 223 | let color = color.into_color(self.samples, self.gamma); 224 | let color = color.i(); 225 | (color.r, color.g, color.b) 226 | } 227 | 228 | fn parallel_render_row( 229 | &self, row: usize, uv_color: &F, cancel: &AtomicBool, 230 | ) -> Vec<(u8, u8, u8)> 231 | where 232 | F: Fn(f64, f64) -> Vec3 + Send + Sync, 233 | { 234 | (0..self.width) 235 | .map(|column| { 236 | if cancel.load(Ordering::Relaxed) { 237 | return (0, 0, 0); 238 | } 239 | self.render_pixel(row, column, &uv_color) 240 | }) 241 | .collect::>() 242 | } 243 | 244 | fn seq_render_row(&self, row: usize, uv_color: &F) -> Vec<(u8, u8, u8)> 245 | where 246 | F: Fn(f64, f64) -> Vec3 + Send + Sync, 247 | { 248 | (0..self.width) 249 | .map(|column| self.render_pixel(row, column, &uv_color)) 250 | .collect::>() 251 | } 252 | 253 | fn parallel_render_row_iter<'c, F>( 254 | &'c self, uv_color: F, cancel: &'c AtomicBool, 255 | ) -> impl IndexedParallelIterator> + 'c 256 | where 257 | F: Fn(f64, f64) -> Vec3 + Send + Sync + 'c, 258 | { 259 | (0..self.height) 260 | .into_par_iter() 261 | .map(move |row| self.parallel_render_row(row, &uv_color, cancel)) 262 | } 263 | 264 | fn seq_render_row_iter<'c, F>( 265 | &'c self, uv_color: F, 266 | ) -> impl Iterator> + 'c 267 | where 268 | F: Fn(f64, f64) -> Vec3 + Send + Sync + 'c, 269 | { 270 | (0..self.height).map(move |row| self.seq_render_row(row, &uv_color)) 271 | } 272 | 273 | fn real_row_pixels_to_file( 274 | context: &mut PainterOutputContext<'_>, pixels: Vec<(u8, u8, u8)>, 275 | ) -> std::io::Result<()> { 276 | for pixel in pixels { 277 | writeln!(context.file, "{} {} {}", pixel.0, pixel.1, pixel.2)?; 278 | } 279 | context.file.flush() 280 | } 281 | 282 | fn row_pixels_to_file( 283 | context: &mut PainterOutputContext<'_>, pixels: Vec<(u8, u8, u8)>, 284 | ) -> std::io::Result<()> { 285 | Self::real_row_pixels_to_file(context, pixels).map_err(|e| { 286 | context.cancel.store(true, Ordering::Relaxed); 287 | e 288 | }) 289 | } 290 | 291 | fn parallel_render_and_output(&self, uv_color: F, path: Option<&Path>) -> std::io::Result<()> 292 | where 293 | F: Fn(f64, f64) -> Vec3 + Send + Sync, 294 | { 295 | let cancel = AtomicBool::new(false); 296 | let finished_row = AtomicUsize::new(0); 297 | 298 | self.parallel_render_row_iter(uv_color, &cancel) 299 | .inspect(|_| { 300 | let count = finished_row.fetch_add(1, Ordering::Relaxed); 301 | info!("Scan line remaining: {}", self.height - count - 1); 302 | }) 303 | .seq_for_each_with( 304 | || self.create_output_context(path, &cancel), 305 | |context, pixels| Self::row_pixels_to_file(context, pixels), 306 | ) 307 | } 308 | 309 | fn setup_thread_pool(&self) -> std::io::Result { 310 | let threads = if self.threads == 0 { 311 | num_cpus::get() + 1 312 | } else { 313 | self.threads + 1 314 | }; 315 | ThreadPoolBuilder::default() 316 | .num_threads(threads) 317 | .build() 318 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) 319 | } 320 | 321 | /// # Errors 322 | /// 323 | /// When open or save to file failed 324 | pub fn draw(&self, path: &Option

, uv_color: F) -> std::io::Result<()> 325 | where 326 | P: AsRef, 327 | F: Fn(f64, f64) -> Vec3 + Send + Sync, 328 | { 329 | let path = match path { 330 | Some(ref path) => Some(path.as_ref()), 331 | None => None, 332 | }; 333 | 334 | if self.parallel { 335 | let pool = self.setup_thread_pool()?; 336 | 337 | info!("Worker thread count: {}", pool.current_num_threads()); 338 | 339 | pool.install(|| self.parallel_render_and_output(uv_color, path)) 340 | } else { 341 | let cancel = AtomicBool::new(false); // useless in parallel mode 342 | let mut context = self.create_output_context(path, &cancel)?; 343 | for (row, pixels) in self.seq_render_row_iter(uv_color).enumerate() { 344 | info!("Scan line remaining: {}", self.height - row); 345 | Self::row_pixels_to_file(&mut context, pixels)?; 346 | } 347 | Ok(()) 348 | } 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/prelude/aabb.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::prelude::*, 3 | std::ops::{BitOr, BitOrAssign, Range}, 4 | }; 5 | 6 | /// Axis aligned bounding box 7 | #[derive(Debug, Clone)] 8 | pub struct AABB { 9 | min: Point3, 10 | max: Point3, 11 | } 12 | 13 | impl AABB { 14 | #[must_use] 15 | pub const fn new(min: Point3, max: Point3) -> Self { 16 | Self { min, max } 17 | } 18 | 19 | #[must_use] 20 | pub const fn min(&self) -> &Point3 { 21 | &self.min 22 | } 23 | 24 | #[must_use] 25 | pub const fn max(&self) -> &Point3 { 26 | &self.max 27 | } 28 | 29 | #[must_use] 30 | pub fn hit(&self, ray: &Ray, unit_limit: Range) -> bool { 31 | let mut t_min = unit_limit.start; 32 | let mut t_max = unit_limit.end; 33 | for i in 0..3 { 34 | // TODO: when inv = Inf and min - origin = 0, the calculation will give a NaN 35 | let inv = 1.0 / ray.direction[i]; 36 | let mut t0 = (self.min[i] - ray.origin[i]) * inv; 37 | let mut t1 = (self.max[i] - ray.origin[i]) * inv; 38 | if inv < 0.0 { 39 | std::mem::swap(&mut t0, &mut t1); 40 | } 41 | t_min = t_min.max(t0); 42 | t_max = t_max.min(t1); 43 | if t_max <= t_min { 44 | return false; 45 | } 46 | } 47 | true 48 | } 49 | } 50 | 51 | impl BitOr for &AABB { 52 | type Output = AABB; 53 | 54 | fn bitor(self, rhs: Self) -> Self::Output { 55 | let min = Point3::new_min(&self.min, &rhs.min); 56 | let max = Point3::new_max(&self.max, &rhs.max); 57 | 58 | AABB::new(min, max) 59 | } 60 | } 61 | 62 | impl BitOr for AABB { 63 | type Output = Self; 64 | 65 | fn bitor(self, rhs: Self) -> Self::Output { 66 | &self | &rhs 67 | } 68 | } 69 | 70 | impl BitOr<&Self> for AABB { 71 | type Output = Self; 72 | 73 | fn bitor(self, rhs: &Self) -> Self::Output { 74 | &self | rhs 75 | } 76 | } 77 | 78 | impl BitOr for &AABB { 79 | type Output = AABB; 80 | 81 | fn bitor(self, rhs: AABB) -> Self::Output { 82 | self | &rhs 83 | } 84 | } 85 | 86 | impl BitOrAssign<&Self> for AABB { 87 | fn bitor_assign(&mut self, rhs: &Self) { 88 | self.min = Point3::new_min(&self.min, &rhs.min); 89 | self.max = Point3::new_max(&self.max, &rhs.max); 90 | } 91 | } 92 | 93 | impl BitOrAssign for AABB { 94 | fn bitor_assign(&mut self, rhs: Self) { 95 | *self |= &rhs 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/prelude/color.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | prelude::{clamp, vec3::Point3}, 4 | texture::Texture, 5 | }, 6 | std::{borrow::Cow, ops::Mul}, 7 | }; 8 | 9 | macro_rules! check0to1 { 10 | ($r: ident, $g: ident, $b: ident) => { 11 | debug_assert!((0.0_f64..=1.0_f64).contains(&$r), "r = {}", $r); 12 | debug_assert!((0.0_f64..=1.0_f64).contains(&$g), "g = {}", $g); 13 | debug_assert!((0.0_f64..=1.0_f64).contains(&$b), "b = {}", $b); 14 | }; 15 | } 16 | 17 | #[derive(Debug, Clone, Default)] 18 | pub struct RGBFloat { 19 | pub r: f64, 20 | pub g: f64, 21 | pub b: f64, 22 | } 23 | 24 | impl RGBFloat { 25 | pub fn new(r: f64, g: f64, b: f64) -> Self { 26 | check0to1!(r, g, b); 27 | Self { r, g, b } 28 | } 29 | } 30 | 31 | #[derive(Debug, Clone, Default)] 32 | pub struct RGBInt { 33 | pub r: u8, 34 | pub g: u8, 35 | pub b: u8, 36 | } 37 | 38 | impl RGBInt { 39 | pub const fn new(r: u8, g: u8, b: u8) -> Self { 40 | Self { r, g, b } 41 | } 42 | } 43 | 44 | impl From<&RGBFloat> for RGBInt { 45 | fn from(c: &RGBFloat) -> Self { 46 | #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] 47 | // because RGBFloat r g b should be in [0..1] 48 | Self::new( 49 | (c.r * 255.0) as u8, 50 | (c.g * 255.0) as u8, 51 | (c.b * 255.0) as u8, 52 | ) 53 | } 54 | } 55 | 56 | impl From<&RGBInt> for RGBFloat { 57 | fn from(c: &RGBInt) -> Self { 58 | let s = 1.0 / 255.0; 59 | Self::new(f64::from(c.r) * s, f64::from(c.g) * s, f64::from(c.b) * s) 60 | } 61 | } 62 | 63 | #[derive(Debug, Clone)] 64 | pub enum Color { 65 | Float(RGBFloat), 66 | Int(RGBInt), 67 | } 68 | 69 | impl Default for Color { 70 | fn default() -> Self { 71 | Self::Float(RGBFloat::default()) 72 | } 73 | } 74 | 75 | impl Color { 76 | #[must_use] 77 | pub const fn new_int(r: u8, g: u8, b: u8) -> Self { 78 | Self::Int(RGBInt::new(r, g, b)) 79 | } 80 | 81 | #[must_use] 82 | pub fn new(r: f64, g: f64, b: f64) -> Self { 83 | Self::Float(RGBFloat::new(r, g, b)) 84 | } 85 | 86 | #[must_use] 87 | pub fn i(&self) -> Cow<'_, RGBInt> { 88 | match self { 89 | Self::Float(c) => Cow::Owned(c.into()), 90 | Self::Int(c) => Cow::Borrowed(c), 91 | } 92 | } 93 | 94 | #[must_use] 95 | pub fn f(&self) -> Cow<'_, RGBFloat> { 96 | match self { 97 | Self::Float(c) => Cow::Borrowed(c), 98 | Self::Int(c) => Cow::Owned(c.into()), 99 | } 100 | } 101 | 102 | #[must_use] 103 | pub fn gradient(&self, rhs: &Self, slide: f64) -> Self { 104 | let a = (1.0 - slide) * self; 105 | let b = slide * rhs; 106 | let c1 = a.f(); 107 | let c2 = b.f(); 108 | Self::new(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b) 109 | } 110 | } 111 | 112 | impl Texture for Color { 113 | fn color(&self, _u: f64, _v: f64, _point: &Point3) -> Color { 114 | self.clone() 115 | } 116 | } 117 | 118 | impl Mul<&Color> for &Color { 119 | type Output = Color; 120 | fn mul(self, rhs: &Color) -> Self::Output { 121 | let c1 = self.f(); 122 | let c2 = rhs.f(); 123 | Color::new(c1.r * c2.r, c1.g * c2.g, c1.b * c2.b) 124 | } 125 | } 126 | 127 | impl Mul for &Color { 128 | type Output = Color; 129 | fn mul(self, rhs: Color) -> Self::Output { 130 | self * &rhs 131 | } 132 | } 133 | 134 | impl Mul<&Color> for Color { 135 | type Output = Self; 136 | fn mul(self, rhs: &Self) -> Self::Output { 137 | &self * rhs 138 | } 139 | } 140 | 141 | impl Mul for Color { 142 | type Output = Self; 143 | fn mul(self, rhs: Self) -> Self::Output { 144 | &self * &rhs 145 | } 146 | } 147 | 148 | impl Mul for &Color { 149 | type Output = Color; 150 | fn mul(self, rhs: f64) -> Self::Output { 151 | let c = self.f(); 152 | Color::new( 153 | clamp(c.r * rhs, 0.0..=1.0), 154 | clamp(c.g * rhs, 0.0..=1.0), 155 | clamp(c.b * rhs, 0.0..=1.0), 156 | ) 157 | } 158 | } 159 | 160 | impl Mul for Color { 161 | type Output = Self; 162 | fn mul(self, rhs: f64) -> Self::Output { 163 | &self * rhs 164 | } 165 | } 166 | 167 | impl Mul<&Color> for f64 { 168 | type Output = Color; 169 | fn mul(self, rhs: &Color) -> Self::Output { 170 | rhs * self 171 | } 172 | } 173 | impl Mul for f64 { 174 | type Output = Color; 175 | fn mul(self, rhs: Color) -> Self::Output { 176 | &rhs * self 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/prelude/mod.rs: -------------------------------------------------------------------------------- 1 | mod aabb; 2 | mod color; 3 | mod random; 4 | mod ray; 5 | mod vec3; 6 | 7 | use std::ops::{Bound, RangeBounds}; 8 | 9 | pub use { 10 | aabb::AABB, 11 | color::Color, 12 | random::{Random, SeedRandom}, 13 | ray::Ray, 14 | std::f64::consts::PI, 15 | vec3::{Point3, Vec3}, 16 | }; 17 | 18 | #[must_use] 19 | pub fn clamp>(val: f64, range: R) -> f64 { 20 | let start = match range.start_bound() { 21 | Bound::Included(&x) | Bound::Excluded(&x) => x, 22 | _ => std::f64::NEG_INFINITY, 23 | }; 24 | let end = match range.end_bound() { 25 | Bound::Included(&x) | Bound::Excluded(&x) => x, 26 | _ => std::f64::INFINITY, 27 | }; 28 | if start > val { 29 | start 30 | } else if val > end { 31 | end 32 | } else { 33 | val 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/prelude/random.rs: -------------------------------------------------------------------------------- 1 | use { 2 | rand::{ 3 | distributions::uniform::SampleUniform, rngs::StdRng, seq::SliceRandom, thread_rng, Rng, 4 | RngCore, SeedableRng, 5 | }, 6 | std::ops::Range, 7 | }; 8 | 9 | #[must_use] 10 | fn normal(mut rng: R) -> f64 { 11 | rng.gen_range(0.0..=1.0) 12 | } 13 | 14 | #[must_use] 15 | fn range(mut rng: R, r: Range) -> T { 16 | rng.gen_range(r) 17 | } 18 | 19 | fn choose>(mut rng: R, values: &S) -> &T { 20 | let slice = values.as_ref(); 21 | assert!(!slice.is_empty()); 22 | let index = rng.gen_range(0..slice.len()); 23 | &slice[index] 24 | } 25 | 26 | fn shuffle>(mut rng: R, values: &mut S) { 27 | let slice = values.as_mut(); 28 | slice.shuffle(&mut rng); 29 | } 30 | 31 | #[derive(Debug)] 32 | pub struct Random(); 33 | 34 | impl Random { 35 | // Return random number in range [0, 1] 36 | #[must_use] 37 | pub fn normal() -> f64 { 38 | normal(thread_rng()) 39 | } 40 | 41 | #[must_use] 42 | pub fn range(r: Range) -> T { 43 | range(thread_rng(), r) 44 | } 45 | 46 | pub fn choose>(values: &S) -> &T { 47 | choose(thread_rng(), values) 48 | } 49 | 50 | pub fn shuffle>(values: &mut S) { 51 | shuffle(thread_rng(), values) 52 | } 53 | } 54 | 55 | #[derive(Debug)] 56 | pub struct SeedRandom(StdRng); 57 | 58 | impl Default for SeedRandom { 59 | fn default() -> Self { 60 | Self::random() 61 | } 62 | } 63 | 64 | impl SeedRandom { 65 | #[must_use] 66 | pub fn new(seed: u64) -> Self { 67 | Self(StdRng::seed_from_u64(seed)) 68 | } 69 | 70 | #[must_use] 71 | pub fn random() -> Self { 72 | Self::new(rand::thread_rng().next_u64()) 73 | } 74 | 75 | pub fn normal(&mut self) -> f64 { 76 | normal(&mut self.0) 77 | } 78 | 79 | pub fn range(&mut self, r: Range) -> T { 80 | range(&mut self.0, r) 81 | } 82 | 83 | pub fn choose<'i, 's, T, S: AsRef<[T]>>(&'i mut self, values: &'s S) -> &'s T { 84 | choose(&mut self.0, values) 85 | } 86 | 87 | pub fn shuffle>(&mut self, values: &mut S) { 88 | shuffle(&mut self.0, values) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/prelude/ray.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::vec3::{Point3, Vec3}; 2 | 3 | #[derive(Debug)] 4 | pub struct Ray { 5 | pub origin: Point3, 6 | pub direction: Vec3, 7 | pub departure_time: f64, 8 | } 9 | 10 | impl Ray { 11 | #[must_use] 12 | pub const fn new(origin: Point3, direction: Vec3, departure_time: f64) -> Self { 13 | Self { 14 | origin, 15 | direction, 16 | departure_time, 17 | } 18 | } 19 | 20 | #[must_use] 21 | pub fn position_after(&self, unit: f64) -> Point3 { 22 | &self.origin + &self.direction * unit 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/prelude/vec3.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::prelude::{clamp, Color, Random, PI}, 3 | std::{ 4 | fmt::Display, 5 | iter::Sum, 6 | ops::{ 7 | Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Range, Sub, 8 | SubAssign, 9 | }, 10 | }, 11 | }; 12 | 13 | #[derive(Default, Clone, Debug, PartialOrd, PartialEq)] 14 | pub struct Vec3 { 15 | pub x: f64, 16 | pub y: f64, 17 | pub z: f64, 18 | } 19 | 20 | pub type Point3 = Vec3; 21 | 22 | impl Vec3 { 23 | #[must_use] 24 | pub const fn new(x: f64, y: f64, z: f64) -> Self { 25 | Self { x, y, z } 26 | } 27 | 28 | #[must_use] 29 | pub fn new_min(a: &Self, b: &Self) -> Self { 30 | Self { 31 | x: a.x.min(b.x), 32 | y: a.y.min(b.y), 33 | z: a.z.min(b.z), 34 | } 35 | } 36 | 37 | #[must_use] 38 | pub fn new_max(a: &Self, b: &Self) -> Self { 39 | Self { 40 | x: a.x.max(b.x), 41 | y: a.y.max(b.y), 42 | z: a.z.max(b.z), 43 | } 44 | } 45 | 46 | #[must_use] 47 | pub fn random_in_unit_box() -> Self { 48 | Self::new( 49 | Random::range(-1.0..1.0), 50 | Random::range(-1.0..1.0), 51 | Random::range(-1.0..1.0), 52 | ) 53 | } 54 | 55 | #[must_use] 56 | pub fn random_range(r: Range) -> Self { 57 | Self::new( 58 | Random::range(r.clone()), 59 | Random::range(r.clone()), 60 | Random::range(r), 61 | ) 62 | } 63 | 64 | #[must_use] 65 | pub fn random_in_unit_sphere() -> Self { 66 | loop { 67 | let p = Self::random_in_unit_box(); 68 | if p.length_squared() < 1.0 { 69 | return p; 70 | } 71 | } 72 | } 73 | 74 | #[must_use] 75 | pub fn random_in_unit_hemisphere(dir: &Self) -> Self { 76 | let u = Self::random_in_unit_sphere(); 77 | if u.dot(dir) > 0.0 { 78 | u 79 | } else { 80 | -u 81 | } 82 | } 83 | 84 | #[must_use] 85 | pub fn random_unit() -> Self { 86 | let a: f64 = Random::range(0.0..(2.0 * PI)); 87 | let z: f64 = Random::range(-1.0..1.0); 88 | let r = (1.0 - z * z).sqrt(); 89 | Self::new(r * a.cos(), r * a.sin(), z) 90 | } 91 | 92 | #[must_use] 93 | pub fn random_unit_dir(dir: &Self) -> Self { 94 | let u = Self::random_unit(); 95 | if u.dot(dir) > 0.0 { 96 | u 97 | } else { 98 | -u 99 | } 100 | } 101 | 102 | #[must_use] 103 | pub fn random_unit_disk() -> Self { 104 | loop { 105 | let p = Self::new(Random::range(-1.0..1.0), Random::range(-1.0..1.0), 0.0); 106 | if p.length_squared() < 1.0 { 107 | return p; 108 | } 109 | } 110 | } 111 | 112 | #[must_use] 113 | pub fn length_squared(&self) -> f64 { 114 | self.z 115 | .mul_add(self.z, self.x.mul_add(self.x, self.y * self.y)) 116 | } 117 | 118 | #[must_use] 119 | pub fn length(&self) -> f64 { 120 | self.length_squared().sqrt() 121 | } 122 | 123 | pub fn reverse(&mut self) { 124 | self.x = -self.x; 125 | self.y = -self.y; 126 | self.z = -self.z; 127 | } 128 | 129 | #[must_use] 130 | pub fn dot(&self, rhs: &Self) -> f64 { 131 | self.z.mul_add(rhs.z, self.x.mul_add(rhs.x, self.y * rhs.y)) 132 | } 133 | 134 | #[must_use] 135 | pub fn cross(&self, rhs: &Self) -> Self { 136 | Self::new( 137 | self.y * rhs.z - self.z * rhs.y, 138 | self.z * rhs.x - self.x * rhs.z, 139 | self.x * rhs.y - self.y * rhs.x, 140 | ) 141 | } 142 | 143 | #[must_use] 144 | pub fn unit(&self) -> Self { 145 | self / self.length() 146 | } 147 | 148 | #[allow(clippy::cast_precision_loss)] // sample count is small enough in practice 149 | #[must_use] 150 | pub fn into_color(mut self, sample_count: usize, gamma: bool) -> Color { 151 | self /= sample_count as f64; 152 | if gamma { 153 | self.x = self.x.sqrt(); 154 | self.y = self.y.sqrt(); 155 | self.z = self.z.sqrt(); 156 | } 157 | Color::new( 158 | clamp(self.x, 0.0..=1.0), 159 | clamp(self.y, 0.0..=1.0), 160 | clamp(self.z, 0.0..=1.0), 161 | ) 162 | } 163 | } 164 | 165 | impl Display for Vec3 { 166 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 167 | f.write_fmt(format_args!("{} {} {}", self.x, self.y, self.z)) 168 | } 169 | } 170 | 171 | impl Index for Vec3 { 172 | type Output = f64; 173 | fn index(&self, index: usize) -> &Self::Output { 174 | match index { 175 | 0 => &self.x, 176 | 1 => &self.y, 177 | 2 => &self.z, 178 | _ => panic!("Vec3 can only index by 0-2, {} provided", index), 179 | } 180 | } 181 | } 182 | 183 | impl IndexMut for Vec3 { 184 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 185 | match index { 186 | 0 => &mut self.x, 187 | 1 => &mut self.y, 188 | 2 => &mut self.z, 189 | _ => panic!("Vec3 can only index by 0-2, {} provided", index), 190 | } 191 | } 192 | } 193 | 194 | impl Neg for &Vec3 { 195 | type Output = Vec3; 196 | fn neg(self) -> Self::Output { 197 | Vec3::new(-self.x, -self.y, -self.z) 198 | } 199 | } 200 | 201 | impl Neg for Vec3 { 202 | type Output = Self; 203 | fn neg(self) -> Self::Output { 204 | (&self).neg() 205 | } 206 | } 207 | 208 | impl Add for &Vec3 { 209 | type Output = Vec3; 210 | fn add(self, rhs: Self) -> Self::Output { 211 | Vec3::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) 212 | } 213 | } 214 | 215 | impl Add for &Vec3 { 216 | type Output = Vec3; 217 | fn add(self, rhs: Vec3) -> Self::Output { 218 | self + &rhs 219 | } 220 | } 221 | 222 | impl Add for Vec3 { 223 | type Output = Self; 224 | fn add(self, rhs: Self) -> Self::Output { 225 | &self + &rhs 226 | } 227 | } 228 | 229 | impl Add<&Self> for Vec3 { 230 | type Output = Self; 231 | fn add(self, rhs: &Self) -> Self::Output { 232 | &self + rhs 233 | } 234 | } 235 | 236 | impl Sub for &Vec3 { 237 | type Output = Vec3; 238 | fn sub(self, rhs: Self) -> Self::Output { 239 | Vec3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) 240 | } 241 | } 242 | 243 | impl Sub for &Vec3 { 244 | type Output = Vec3; 245 | fn sub(self, rhs: Vec3) -> Self::Output { 246 | self - &rhs 247 | } 248 | } 249 | 250 | impl Sub for Vec3 { 251 | type Output = Self; 252 | fn sub(self, rhs: Self) -> Self::Output { 253 | &self - &rhs 254 | } 255 | } 256 | 257 | impl Sub<&Self> for Vec3 { 258 | type Output = Self; 259 | fn sub(self, rhs: &Self) -> Self::Output { 260 | &self - rhs 261 | } 262 | } 263 | 264 | impl Mul for &Vec3 { 265 | type Output = Vec3; 266 | fn mul(self, rhs: Self) -> Self::Output { 267 | Vec3::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z) 268 | } 269 | } 270 | 271 | impl Mul for &Vec3 { 272 | type Output = Vec3; 273 | fn mul(self, rhs: Vec3) -> Self::Output { 274 | self * &rhs 275 | } 276 | } 277 | 278 | impl Mul for Vec3 { 279 | type Output = Self; 280 | fn mul(self, rhs: Self) -> Self::Output { 281 | &self * &rhs 282 | } 283 | } 284 | 285 | impl Mul<&Self> for Vec3 { 286 | type Output = Self; 287 | fn mul(self, rhs: &Self) -> Self::Output { 288 | &self * rhs 289 | } 290 | } 291 | 292 | impl Mul<&Color> for &Vec3 { 293 | type Output = Vec3; 294 | fn mul(self, rhs: &Color) -> Self::Output { 295 | let rhs = rhs.f(); 296 | Vec3::new( 297 | self.x * rhs.r as f64, 298 | self.y * rhs.g as f64, 299 | self.z * rhs.b as f64, 300 | ) 301 | } 302 | } 303 | 304 | impl Mul for &Vec3 { 305 | type Output = Vec3; 306 | fn mul(self, rhs: Color) -> Self::Output { 307 | self * &rhs 308 | } 309 | } 310 | 311 | impl Mul for Vec3 { 312 | type Output = Self; 313 | fn mul(self, rhs: Color) -> Self::Output { 314 | &self * &rhs 315 | } 316 | } 317 | 318 | impl Mul<&Color> for Vec3 { 319 | type Output = Self; 320 | fn mul(self, rhs: &Color) -> Self::Output { 321 | &self * rhs 322 | } 323 | } 324 | 325 | impl Mul<&Vec3> for &Color { 326 | type Output = Vec3; 327 | fn mul(self, rhs: &Vec3) -> Self::Output { 328 | rhs * self 329 | } 330 | } 331 | 332 | impl Mul<&Vec3> for Color { 333 | type Output = Vec3; 334 | fn mul(self, rhs: &Vec3) -> Self::Output { 335 | &self * rhs 336 | } 337 | } 338 | 339 | impl Mul for Color { 340 | type Output = Vec3; 341 | fn mul(self, rhs: Vec3) -> Self::Output { 342 | &self * &rhs 343 | } 344 | } 345 | 346 | impl Mul for &Color { 347 | type Output = Vec3; 348 | fn mul(self, rhs: Vec3) -> Self::Output { 349 | self * &rhs 350 | } 351 | } 352 | 353 | impl Mul for &Vec3 { 354 | type Output = Vec3; 355 | fn mul(self, rhs: f64) -> Self::Output { 356 | Vec3::new(self.x * rhs, self.y * rhs, self.z * rhs) 357 | } 358 | } 359 | 360 | impl Mul for Vec3 { 361 | type Output = Self; 362 | fn mul(self, rhs: f64) -> Self::Output { 363 | &self * rhs 364 | } 365 | } 366 | 367 | impl Mul for f64 { 368 | type Output = Vec3; 369 | fn mul(self, rhs: Vec3) -> Self::Output { 370 | rhs * self 371 | } 372 | } 373 | 374 | impl Mul<&Vec3> for f64 { 375 | type Output = Vec3; 376 | fn mul(self, rhs: &Vec3) -> Self::Output { 377 | rhs * self 378 | } 379 | } 380 | 381 | impl Div for &Vec3 { 382 | type Output = Vec3; 383 | fn div(self, rhs: f64) -> Self::Output { 384 | self * (1.0 / rhs) 385 | } 386 | } 387 | 388 | impl Div for Vec3 { 389 | type Output = Self; 390 | fn div(self, rhs: f64) -> Self::Output { 391 | self * (1.0 / rhs) 392 | } 393 | } 394 | 395 | impl AddAssign<&Self> for Vec3 { 396 | fn add_assign(&mut self, rhs: &Self) { 397 | self.x += rhs.x; 398 | self.y += rhs.y; 399 | self.z += rhs.z; 400 | } 401 | } 402 | 403 | impl AddAssign for Vec3 { 404 | fn add_assign(&mut self, rhs: Self) { 405 | self.x += rhs.x; 406 | self.y += rhs.y; 407 | self.z += rhs.z; 408 | } 409 | } 410 | 411 | impl SubAssign<&Self> for Vec3 { 412 | fn sub_assign(&mut self, rhs: &Self) { 413 | self.x -= rhs.x; 414 | self.y -= rhs.y; 415 | self.z -= rhs.z; 416 | } 417 | } 418 | 419 | impl SubAssign for Vec3 { 420 | fn sub_assign(&mut self, rhs: Self) { 421 | self.x -= rhs.x; 422 | self.y -= rhs.y; 423 | self.z -= rhs.z; 424 | } 425 | } 426 | 427 | impl MulAssign for Vec3 { 428 | fn mul_assign(&mut self, rhs: Self) { 429 | self.x *= rhs.x; 430 | self.y *= rhs.y; 431 | self.z *= rhs.z; 432 | } 433 | } 434 | 435 | impl MulAssign<&Self> for Vec3 { 436 | fn mul_assign(&mut self, rhs: &Self) { 437 | self.x *= rhs.x; 438 | self.y *= rhs.y; 439 | self.z *= rhs.z; 440 | } 441 | } 442 | 443 | impl MulAssign for Vec3 { 444 | fn mul_assign(&mut self, rhs: f64) { 445 | self.x *= rhs; 446 | self.y *= rhs; 447 | self.z *= rhs; 448 | } 449 | } 450 | 451 | impl DivAssign for Vec3 { 452 | fn div_assign(&mut self, rhs: f64) { 453 | self.x /= rhs; 454 | self.y /= rhs; 455 | self.z /= rhs; 456 | } 457 | } 458 | 459 | impl Sum for Vec3 { 460 | fn sum>(iter: I) -> Self { 461 | iter.fold(Self::default(), |acc, val| acc + val) 462 | } 463 | } 464 | 465 | impl From for Vec3 { 466 | fn from(c: Color) -> Self { 467 | let c = c.f(); 468 | Self::new(c.r as f64, c.g as f64, c.b as f64) 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /src/texture/checker.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | prelude::{Color, Point3}, 3 | texture::Texture, 4 | }; 5 | 6 | #[allow(missing_debug_implementations)] 7 | #[derive(Clone)] 8 | pub struct Checker { 9 | odd: T1, 10 | even: T2, 11 | } 12 | 13 | impl Checker { 14 | #[must_use] 15 | pub const fn new(odd: T1, even: T2) -> Self { 16 | Self { odd, even } 17 | } 18 | } 19 | 20 | impl Texture for Checker { 21 | fn color(&self, u: f64, v: f64, point: &Point3) -> Color { 22 | let value = (10.0 * point.x).sin() * (10.0 * point.y).sin() * (10.0 * point.z).sin(); 23 | if value < 0.0 { 24 | self.odd.color(u, v, point) 25 | } else { 26 | self.even.color(u, v, point) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/texture/image.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{prelude::*, texture::Texture}, 3 | image::{DynamicImage, GenericImageView}, 4 | std::{ 5 | fmt::{Debug, Formatter}, 6 | path::Path, 7 | }, 8 | }; 9 | 10 | pub struct Image { 11 | img: DynamicImage, 12 | } 13 | 14 | impl Debug for Image { 15 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 16 | f.write_fmt(format_args!( 17 | "Image {{ {}x{} }} ", 18 | self.img.width(), 19 | self.img.height() 20 | )) 21 | } 22 | } 23 | 24 | impl Image { 25 | /// # Errors 26 | /// 27 | /// When load image failed 28 | pub fn new>(p: P) -> Result { 29 | let img = image::open(p).map_err(|e| e.to_string())?; 30 | Ok(Self { img }) 31 | } 32 | } 33 | 34 | impl Texture for Image { 35 | #[allow(clippy::cast_sign_loss)] // u v and width all non-negative 36 | fn color(&self, u: f64, v: f64, _point: &Point3) -> Color { 37 | let v = 1.0 - v; 38 | let mut px = (u * f64::from(self.img.width())) as u32; 39 | let mut py = (v * f64::from(self.img.height())) as u32; 40 | if px >= self.img.width() { 41 | px = self.img.width() - 1; 42 | } 43 | if py >= self.img.height() { 44 | py = self.img.height() - 1; 45 | } 46 | 47 | let color = self.img.get_pixel(px, py); 48 | Color::new_int(color.0[0], color.0[1], color.0[2]) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/texture/mod.rs: -------------------------------------------------------------------------------- 1 | use {crate::prelude::*, std::sync::Arc}; 2 | 3 | pub(crate) mod checker; 4 | pub(crate) mod image; 5 | pub(crate) mod noise; 6 | 7 | pub use { 8 | self::image::Image, 9 | checker::Checker, 10 | noise::{Perlin, SmoothType}, 11 | }; 12 | 13 | pub trait Texture: Send + Sync { 14 | fn color(&self, u: f64, v: f64, point: &Point3) -> Color; 15 | } 16 | 17 | impl Texture for Arc { 18 | fn color(&self, u: f64, v: f64, point: &Point3) -> Color { 19 | self.as_ref().color(u, v, point) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/texture/noise.rs: -------------------------------------------------------------------------------- 1 | use crate::{prelude::*, texture::Texture}; 2 | 3 | #[derive(Debug, Clone, Eq, PartialEq)] 4 | pub enum SmoothType { 5 | None, 6 | LinearInterpolate, 7 | HermitianCubic, 8 | } 9 | 10 | #[derive(Debug, Clone, Eq, PartialEq)] 11 | enum TextureType { 12 | Normal, 13 | Turbulence(u8), 14 | Marble(u8), 15 | } 16 | 17 | #[derive(Debug, Clone, PartialEq)] 18 | enum RandomValueType { 19 | Float(f64), 20 | Vector(Vec3), 21 | } 22 | 23 | #[derive(Debug, Clone)] 24 | pub struct Perlin { 25 | texture_type: TextureType, 26 | smooth_type: SmoothType, 27 | point_count: usize, 28 | scale: f64, 29 | random_values: Vec, 30 | perm_x: Vec, 31 | perm_y: Vec, 32 | perm_z: Vec, 33 | } 34 | 35 | impl Default for RandomValueType { 36 | fn default() -> Self { 37 | Self::Float(0.0) 38 | } 39 | } 40 | 41 | impl Perlin { 42 | #[must_use] 43 | pub fn new(point_count: usize, vector: bool) -> Self { 44 | let ran = (0..point_count) 45 | .map(|_| { 46 | if vector { 47 | RandomValueType::Vector(Vec3::random_unit()) 48 | } else { 49 | RandomValueType::Float(Random::normal()) 50 | } 51 | }) 52 | .collect(); 53 | let mut perm_x = (0..point_count).collect(); 54 | Random::shuffle(&mut perm_x); 55 | let mut perm_y = (0..point_count).collect(); 56 | Random::shuffle(&mut perm_y); 57 | let mut perm_z = (0..point_count).collect(); 58 | Random::shuffle(&mut perm_z); 59 | Self { 60 | texture_type: TextureType::Normal, 61 | smooth_type: SmoothType::HermitianCubic, 62 | point_count, 63 | scale: 1.0, 64 | random_values: ran, 65 | perm_x, 66 | perm_y, 67 | perm_z, 68 | } 69 | } 70 | 71 | #[must_use] 72 | pub const fn scale(mut self, scale: f64) -> Self { 73 | self.scale = scale; 74 | self 75 | } 76 | 77 | #[must_use] 78 | pub const fn smooth(mut self, smooth: SmoothType) -> Self { 79 | self.smooth_type = smooth; 80 | self 81 | } 82 | 83 | #[must_use] 84 | pub const fn turbulence(mut self, depth: u8) -> Self { 85 | self.texture_type = TextureType::Turbulence(depth); 86 | self 87 | } 88 | 89 | #[must_use] 90 | pub const fn marble(mut self, depth: u8) -> Self { 91 | self.texture_type = TextureType::Marble(depth); 92 | self 93 | } 94 | 95 | #[allow(clippy::cast_sign_loss)] // because we bit and with positive number before cast 96 | #[allow(clippy::cast_possible_wrap)] // because di dj dk and point_count is small enough 97 | #[allow(clippy::cast_precision_loss)] // scene is not so big 98 | #[allow(clippy::many_single_char_names)] 99 | fn noise(&self, point: &Point3) -> f64 { 100 | match self.smooth_type { 101 | SmoothType::None => { 102 | let i = (((4.0 * point.x) as isize) & (self.point_count - 1) as isize) as usize; 103 | let j = (((4.0 * point.y) as isize) & (self.point_count - 1) as isize) as usize; 104 | let k = (((4.0 * point.z) as isize) & (self.point_count - 1) as isize) as usize; 105 | 106 | match &self.random_values[self.perm_x[i] ^ self.perm_y[j] ^ self.perm_z[k]] { 107 | RandomValueType::Vector(v) => v.x, 108 | RandomValueType::Float(x) => *x, 109 | } 110 | } 111 | SmoothType::LinearInterpolate | SmoothType::HermitianCubic => { 112 | let i = point.x.floor() as isize; 113 | let j = point.y.floor() as isize; 114 | let k = point.z.floor() as isize; 115 | let u = point.x - i as f64; 116 | let v = point.y - j as f64; 117 | let w = point.z - k as f64; 118 | 119 | let mut grays: [[[RandomValueType; 2]; 2]; 2] = Default::default(); 120 | 121 | (0..2).for_each(|di| { 122 | (0..2).for_each(|dj| { 123 | (0..2).for_each(|dk| { 124 | let xi = ((i + di as isize) & (self.point_count - 1) as isize) as usize; 125 | let yi = ((j + dj as isize) & (self.point_count - 1) as isize) as usize; 126 | let zi = ((k + dk as isize) & (self.point_count - 1) as isize) as usize; 127 | let index = self.perm_x[xi] ^ self.perm_y[yi] ^ self.perm_z[zi]; 128 | grays[di][dj][dk] = self.random_values[index].clone(); 129 | }) 130 | }) 131 | }); 132 | 133 | interpolate(&grays, u, v, w, &self.smooth_type) 134 | } 135 | } 136 | } 137 | 138 | fn calculate_turbulence(&self, point: &Point3, depth: usize) -> f64 { 139 | let mut p = point.clone(); 140 | let mut weight = 1.0; 141 | 142 | (0..depth) 143 | .map(|_| { 144 | let res = weight * self.noise(&p); 145 | weight *= 0.5; 146 | p *= 2.0; 147 | res 148 | }) 149 | .sum::() 150 | .abs() 151 | } 152 | } 153 | 154 | #[allow(clippy::cast_precision_loss)] // i j k is small enough 155 | fn interpolate( 156 | c: &[[[RandomValueType; 2]; 2]; 2], u: f64, v: f64, w: f64, smooth: &SmoothType, 157 | ) -> f64 { 158 | let (mut uu, mut vv, mut ww) = (u, v, w); 159 | if smooth == &SmoothType::HermitianCubic { 160 | uu = u * u * (3.0 - 2.0 * u); 161 | vv = v * v * (3.0 - 2.0 * v); 162 | ww = w * w * (3.0 - 2.0 * w); 163 | } 164 | 165 | (0..2) 166 | .map(|i| { 167 | (0..2) 168 | .map(|j| { 169 | (0..2) 170 | .map(|k| { 171 | (i as f64).mul_add(uu, (1 - i) as f64 * (1.0 - uu)) 172 | * (j as f64).mul_add(vv, (1 - j) as f64 * (1.0 - vv)) 173 | * (k as f64).mul_add(ww, (1 - k) as f64 * (1.0 - ww)) 174 | * match &c[i][j][k] { 175 | RandomValueType::Vector(vec) => { 176 | let weight = 177 | Vec3::new(u - i as f64, v - j as f64, w - k as f64); 178 | vec.dot(&weight) 179 | } 180 | RandomValueType::Float(x) => *x, 181 | } 182 | }) 183 | .sum::() 184 | }) 185 | .sum::() 186 | }) 187 | .sum() 188 | } 189 | 190 | impl Texture for Perlin { 191 | fn color(&self, _u: f64, _v: f64, point: &Point3) -> Color { 192 | Color::new(1.0, 1.0, 1.0) 193 | * match &self.texture_type { 194 | TextureType::Normal => { 195 | let p = self.scale * point; 196 | let mut noise = self.noise(&p); 197 | 198 | if let RandomValueType::Vector(_) = &self.random_values[0] { 199 | noise = 0.5 * (noise + 1.0); 200 | } 201 | noise 202 | } 203 | TextureType::Turbulence(depth) => self.calculate_turbulence(point, *depth as usize), 204 | TextureType::Marble(depth) => { 205 | let noise = self.calculate_turbulence(point, *depth as usize); 206 | (self.scale.mul_add(point.z, 10.0 * noise).sin() + 1.0) * 0.5 207 | } 208 | } 209 | } 210 | } 211 | --------------------------------------------------------------------------------