├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── assets ├── bowser_back.png ├── bowser_face.png ├── bowser_side.png ├── bowser_top.png ├── earthmap.png └── twitter.png ├── clippy.toml ├── sample ├── inoneweekend.png ├── thenextweek.png └── therestofyourlife.png └── src ├── accel.rs ├── hittable.rs ├── main.rs ├── material.rs ├── scene.rs ├── util.rs └── vec3.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /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.1.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" 8 | 9 | [[package]] 10 | name = "array-init" 11 | version = "0.1.1" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "f30bbe2f5e3d117f55bd8c7a1f9191e4a5deba9f15f595bbea4f670c59c765db" 14 | 15 | [[package]] 16 | name = "autocfg" 17 | version = "1.0.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 20 | 21 | [[package]] 22 | name = "bitflags" 23 | version = "1.2.1" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 26 | 27 | [[package]] 28 | name = "byteorder" 29 | version = "1.3.4" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 32 | 33 | [[package]] 34 | name = "cfg-if" 35 | version = "0.1.10" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 38 | 39 | [[package]] 40 | name = "crc32fast" 41 | version = "1.2.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 44 | dependencies = [ 45 | "cfg-if", 46 | ] 47 | 48 | [[package]] 49 | name = "crossbeam-deque" 50 | version = "0.7.3" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" 53 | dependencies = [ 54 | "crossbeam-epoch", 55 | "crossbeam-utils", 56 | "maybe-uninit", 57 | ] 58 | 59 | [[package]] 60 | name = "crossbeam-epoch" 61 | version = "0.8.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" 64 | dependencies = [ 65 | "autocfg", 66 | "cfg-if", 67 | "crossbeam-utils", 68 | "lazy_static", 69 | "maybe-uninit", 70 | "memoffset", 71 | "scopeguard", 72 | ] 73 | 74 | [[package]] 75 | name = "crossbeam-queue" 76 | version = "0.2.2" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2" 79 | dependencies = [ 80 | "cfg-if", 81 | "crossbeam-utils", 82 | ] 83 | 84 | [[package]] 85 | name = "crossbeam-utils" 86 | version = "0.7.2" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 89 | dependencies = [ 90 | "autocfg", 91 | "cfg-if", 92 | "lazy_static", 93 | ] 94 | 95 | [[package]] 96 | name = "deflate" 97 | version = "0.8.4" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "e7e5d2a2273fed52a7f947ee55b092c4057025d7a3e04e5ecdbd25d6c3fb1bd7" 100 | dependencies = [ 101 | "adler32", 102 | "byteorder", 103 | ] 104 | 105 | [[package]] 106 | name = "derive-new" 107 | version = "0.5.8" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "71f31892cd5c62e414316f2963c5689242c43d8e7bbcaaeca97e5e28c95d91d9" 110 | dependencies = [ 111 | "proc-macro2", 112 | "quote", 113 | "syn", 114 | ] 115 | 116 | [[package]] 117 | name = "either" 118 | version = "1.5.3" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 121 | 122 | [[package]] 123 | name = "getrandom" 124 | version = "0.1.14" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 127 | dependencies = [ 128 | "cfg-if", 129 | "libc", 130 | "wasi", 131 | ] 132 | 133 | [[package]] 134 | name = "hermit-abi" 135 | version = "0.1.13" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" 138 | dependencies = [ 139 | "libc", 140 | ] 141 | 142 | [[package]] 143 | name = "lazy_static" 144 | version = "1.4.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 147 | 148 | [[package]] 149 | name = "libc" 150 | version = "0.2.71" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" 153 | 154 | [[package]] 155 | name = "maybe-uninit" 156 | version = "2.0.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 159 | 160 | [[package]] 161 | name = "memoffset" 162 | version = "0.5.4" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" 165 | dependencies = [ 166 | "autocfg", 167 | ] 168 | 169 | [[package]] 170 | name = "miniz_oxide" 171 | version = "0.3.7" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" 174 | dependencies = [ 175 | "adler32", 176 | ] 177 | 178 | [[package]] 179 | name = "num_cpus" 180 | version = "1.13.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 183 | dependencies = [ 184 | "hermit-abi", 185 | "libc", 186 | ] 187 | 188 | [[package]] 189 | name = "png" 190 | version = "0.16.6" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "c150bf7479fafe3dd8740dbe48cc33b2a3efb7b0fe3483aced8bbc39f6d0238d" 193 | dependencies = [ 194 | "bitflags", 195 | "crc32fast", 196 | "deflate", 197 | "miniz_oxide", 198 | ] 199 | 200 | [[package]] 201 | name = "ppv-lite86" 202 | version = "0.2.8" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" 205 | 206 | [[package]] 207 | name = "proc-macro2" 208 | version = "1.0.17" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" 211 | dependencies = [ 212 | "unicode-xid", 213 | ] 214 | 215 | [[package]] 216 | name = "quote" 217 | version = "1.0.6" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" 220 | dependencies = [ 221 | "proc-macro2", 222 | ] 223 | 224 | [[package]] 225 | name = "rand" 226 | version = "0.7.3" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 229 | dependencies = [ 230 | "getrandom", 231 | "libc", 232 | "rand_chacha", 233 | "rand_core", 234 | "rand_hc", 235 | ] 236 | 237 | [[package]] 238 | name = "rand_chacha" 239 | version = "0.2.2" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 242 | dependencies = [ 243 | "ppv-lite86", 244 | "rand_core", 245 | ] 246 | 247 | [[package]] 248 | name = "rand_core" 249 | version = "0.5.1" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 252 | dependencies = [ 253 | "getrandom", 254 | ] 255 | 256 | [[package]] 257 | name = "rand_hc" 258 | version = "0.2.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 261 | dependencies = [ 262 | "rand_core", 263 | ] 264 | 265 | [[package]] 266 | name = "rayon" 267 | version = "1.3.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" 270 | dependencies = [ 271 | "crossbeam-deque", 272 | "either", 273 | "rayon-core", 274 | ] 275 | 276 | [[package]] 277 | name = "rayon-core" 278 | version = "1.7.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" 281 | dependencies = [ 282 | "crossbeam-deque", 283 | "crossbeam-queue", 284 | "crossbeam-utils", 285 | "lazy_static", 286 | "num_cpus", 287 | ] 288 | 289 | [[package]] 290 | name = "scopeguard" 291 | version = "1.1.0" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 294 | 295 | [[package]] 296 | name = "syn" 297 | version = "1.0.27" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "ef781e621ee763a2a40721a8861ec519cb76966aee03bb5d00adb6a31dc1c1de" 300 | dependencies = [ 301 | "proc-macro2", 302 | "quote", 303 | "unicode-xid", 304 | ] 305 | 306 | [[package]] 307 | name = "unicode-xid" 308 | version = "0.2.0" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 311 | 312 | [[package]] 313 | name = "vecchio" 314 | version = "0.1.0" 315 | dependencies = [ 316 | "array-init", 317 | "derive-new", 318 | "png", 319 | "rand", 320 | "rayon", 321 | ] 322 | 323 | [[package]] 324 | name = "wasi" 325 | version = "0.9.0+wasi-snapshot-preview1" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 328 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vecchio" 3 | version = "0.1.0" 4 | authors = ["bowser"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [profile.release] 10 | debug = true 11 | 12 | [dependencies] 13 | derive-new = "0.5" 14 | rand = "0.7.3" 15 | rayon = "1.3.0" 16 | png = "0.16.6" 17 | array-init = "0.1.1" 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rust implementation of the book series [Ray Tracing in One Weekend](https://raytracing.github.io/). 2 | 3 | To see the code for a particular book, see the repository tags: 4 | 5 | * [Ray Tracing in One Weekend](https://github.com/browserdotsys/vecchio/tree/InOneWeekend) 6 | * [Ray Tracing: The Next Week](https://github.com/browserdotsys/vecchio/tree/TheNextWeek) 7 | * [Ray Tracing: The Rest of Your Life](https://github.com/browserdotsys/vecchio/tree/TheRestOfYourLife) 8 | 9 | The [samples](samples) directory contains renderings of the capstone image for each book: 10 | 11 | ![Ray Tracing in One Weekend](https://github.com/browserdotsys/vecchio/blob/master/sample/inoneweekend.png?raw=true) 12 | 13 | ![Ray Tracing: The Next Week](https://github.com/browserdotsys/vecchio/blob/master/sample/thenextweek.png?raw=true) 14 | 15 | ![Ray Tracing: The Rest of Your Life](https://github.com/browserdotsys/vecchio/blob/TheRestOfYourLife/sample/therestofyourlife.png?raw=true) 16 | -------------------------------------------------------------------------------- /assets/bowser_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserdotsys/vecchio/2c0a249dbdd1a89f1ff1a9fa6f2547fd3859cf55/assets/bowser_back.png -------------------------------------------------------------------------------- /assets/bowser_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserdotsys/vecchio/2c0a249dbdd1a89f1ff1a9fa6f2547fd3859cf55/assets/bowser_face.png -------------------------------------------------------------------------------- /assets/bowser_side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserdotsys/vecchio/2c0a249dbdd1a89f1ff1a9fa6f2547fd3859cf55/assets/bowser_side.png -------------------------------------------------------------------------------- /assets/bowser_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserdotsys/vecchio/2c0a249dbdd1a89f1ff1a9fa6f2547fd3859cf55/assets/bowser_top.png -------------------------------------------------------------------------------- /assets/earthmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserdotsys/vecchio/2c0a249dbdd1a89f1ff1a9fa6f2547fd3859cf55/assets/earthmap.png -------------------------------------------------------------------------------- /assets/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserdotsys/vecchio/2c0a249dbdd1a89f1ff1a9fa6f2547fd3859cf55/assets/twitter.png -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | single-char-binding-names-threshold = 8 2 | too-many-arguments-threshold = 15 3 | -------------------------------------------------------------------------------- /sample/inoneweekend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserdotsys/vecchio/2c0a249dbdd1a89f1ff1a9fa6f2547fd3859cf55/sample/inoneweekend.png -------------------------------------------------------------------------------- /sample/thenextweek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserdotsys/vecchio/2c0a249dbdd1a89f1ff1a9fa6f2547fd3859cf55/sample/thenextweek.png -------------------------------------------------------------------------------- /sample/therestofyourlife.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserdotsys/vecchio/2c0a249dbdd1a89f1ff1a9fa6f2547fd3859cf55/sample/therestofyourlife.png -------------------------------------------------------------------------------- /src/accel.rs: -------------------------------------------------------------------------------- 1 | use crate::hittable::{HitRec, Hittable, HittableSS}; 2 | use crate::rand::Rng; 3 | use crate::util::{fmax, fmin}; 4 | use crate::vec3::Vec3; 5 | use crate::Ray; 6 | use std::sync::Arc; 7 | 8 | // Axis-aligned bounding box 9 | #[derive(new, Copy, Clone)] 10 | pub struct AxisBB { 11 | pub min: Vec3, 12 | pub max: Vec3, 13 | } 14 | 15 | impl AxisBB { 16 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> bool { 17 | let mut tmin_local = tmin; 18 | let mut tmax_local = tmax; 19 | for a in 0..3 { 20 | let t0 = fmin( 21 | (self.min[a] - r.origin[a]) / r.direction[a], 22 | (self.max[a] - r.origin[a]) / r.direction[a], 23 | ); 24 | let t1 = fmax( 25 | (self.min[a] - r.origin[a]) / r.direction[a], 26 | (self.max[a] - r.origin[a]) / r.direction[a], 27 | ); 28 | tmin_local = fmax(t0, tmin_local); 29 | tmax_local = fmin(t1, tmax_local); 30 | if tmax_local <= tmin_local { 31 | return false; 32 | } 33 | } 34 | true 35 | } 36 | 37 | pub fn surrounding_box(box1: AxisBB, box2: AxisBB) -> AxisBB { 38 | let small = Vec3::new( 39 | box1.min.x.min(box2.min.x), 40 | box1.min.y.min(box2.min.y), 41 | box1.min.z.min(box2.min.z), 42 | ); 43 | let big = Vec3::new( 44 | box1.max.x.max(box2.max.x), 45 | box1.max.y.max(box2.max.y), 46 | box1.max.z.max(box2.max.z), 47 | ); 48 | AxisBB::new(small, big) 49 | } 50 | } 51 | 52 | pub struct BVHNode { 53 | left: Arc, 54 | right: Arc, 55 | bb: AxisBB, 56 | } 57 | 58 | impl Hittable for BVHNode { 59 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option { 60 | if !self.bb.hit(r, tmin, tmax) { 61 | return None; 62 | } 63 | 64 | let rec_left = self.left.hit(r, tmin, tmax); 65 | let tmax_new = if let Some(r) = rec_left.as_ref() { 66 | r.t 67 | } else { 68 | tmax 69 | }; 70 | let rec_right = self.right.hit(r, tmin, tmax_new); 71 | match (rec_left.as_ref(), rec_right.as_ref()) { 72 | (Some(l), Some(r)) => { 73 | if l.t < r.t { 74 | rec_left 75 | } else { 76 | rec_right 77 | } 78 | } 79 | (Some(_), None) => rec_left, 80 | (None, Some(_)) => rec_right, 81 | (None, None) => None, 82 | } 83 | } 84 | 85 | fn bounding_box(&self, _t0: f32, _t1: f32) -> Option { 86 | Some(self.bb) 87 | } 88 | } 89 | 90 | impl BVHNode { 91 | fn box_for_hittables(h1: &Arc, h2: &Arc) -> AxisBB { 92 | AxisBB::surrounding_box( 93 | h1.bounding_box(0.0, 0.0).unwrap(), 94 | h2.bounding_box(0.0, 0.0).unwrap(), 95 | ) 96 | } 97 | 98 | pub fn new(objects: &mut [Arc]) -> BVHNode { 99 | let mut rng = rand::thread_rng(); 100 | let axis = rng.gen_range(0, 3); 101 | 102 | if objects.len() == 1 { 103 | BVHNode { 104 | left: objects[0].clone(), 105 | right: objects[0].clone(), 106 | bb: BVHNode::box_for_hittables(&objects[0], &objects[0]), 107 | } 108 | } else if objects.len() == 2 { 109 | let a_bb = objects[0].bounding_box(0.0, 0.0).unwrap(); 110 | let b_bb = objects[1].bounding_box(0.0, 0.0).unwrap(); 111 | let (mut i1, mut i2) = (0, 1); 112 | if a_bb.min[axis] < b_bb.min[axis] { 113 | i1 = 1; 114 | i2 = 0; 115 | } 116 | BVHNode { 117 | left: objects[i1].clone(), 118 | right: objects[i2].clone(), 119 | bb: BVHNode::box_for_hittables(&objects[i1], &objects[i2]), 120 | } 121 | } else { 122 | objects.sort_by(|a, b| { 123 | let a_bb = a.bounding_box(0.0, 0.0).unwrap(); 124 | let b_bb = b.bounding_box(0.0, 0.0).unwrap(); 125 | a_bb.min[axis].partial_cmp(&b_bb.min[axis]).unwrap() 126 | }); 127 | let (left_part, right_part) = objects.split_at_mut(objects.len() / 2); 128 | let left = Arc::new(BVHNode::new(left_part)); 129 | let right = Arc::new(BVHNode::new(right_part)); 130 | let bb = AxisBB::surrounding_box( 131 | left.bounding_box(0.0, 0.0).unwrap(), 132 | right.bounding_box(0.0, 0.0).unwrap(), 133 | ); 134 | BVHNode { left, right, bb } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/hittable.rs: -------------------------------------------------------------------------------- 1 | use crate::accel::AxisBB; 2 | use crate::material::Isotropic; 3 | use crate::material::{MaterialSS, TextureSS}; 4 | use rand::Rng; 5 | use rand::seq::SliceRandom; 6 | use crate::util::{fmax, fmin, ONB}; 7 | use crate::vec3::Vec3; 8 | use crate::Ray; 9 | use std::sync::Arc; 10 | 11 | #[derive(new)] 12 | pub struct HitRec { 13 | pub p: Vec3, 14 | pub normal: Vec3, 15 | pub t: f32, 16 | pub u: f32, 17 | pub v: f32, 18 | pub front: bool, 19 | pub material: Arc, 20 | } 21 | 22 | impl HitRec { 23 | fn set_face_normal(&mut self, r: &Ray, outward_normal: Vec3) { 24 | self.front = r.direction.dot(outward_normal) < 0.0; 25 | self.normal = if self.front { 26 | outward_normal 27 | } else { 28 | -outward_normal 29 | }; 30 | } 31 | } 32 | 33 | pub trait Hittable { 34 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option; 35 | fn bounding_box(&self, t0: f32, t1: f32) -> Option; 36 | fn pdf_value(&self, _o: Vec3, _v: Vec3) -> f32 { 37 | 0.0 38 | } 39 | fn random(&self, _o: Vec3) -> Vec3 { 40 | Vec3::new(1.0, 0.0, 0.0) 41 | } 42 | } 43 | 44 | pub type HittableSS = dyn Hittable + Send + Sync; 45 | 46 | #[derive(new)] 47 | pub struct Sphere { 48 | center: Vec3, 49 | radius: f32, 50 | material: Arc, 51 | } 52 | 53 | impl Sphere { 54 | pub fn spherical(p: Vec3) -> (f32, f32) { 55 | let pi = std::f32::consts::PI; 56 | let phi = p.z.atan2(p.x); 57 | let theta = p.y.asin(); 58 | let u = 1.0 - ((phi + pi) / (2.0 * pi)); 59 | let v = (theta + pi / 2.0) / pi; 60 | (u, v) 61 | } 62 | } 63 | 64 | impl Hittable for Sphere { 65 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option { 66 | let oc = r.origin - self.center; 67 | let a = r.direction.length2(); 68 | let half_b = oc.dot(r.direction); 69 | let c = oc.length2() - self.radius * self.radius; 70 | let discriminant = half_b * half_b - a * c; 71 | 72 | if discriminant > 0.0 { 73 | let root = discriminant.sqrt(); 74 | for temp in &[(-half_b - root) / a, (-half_b + root) / a] { 75 | if tmin < *temp && *temp < tmax { 76 | let mut ret = HitRec::new( 77 | r.at(*temp), 78 | (r.at(*temp) - self.center) / self.radius, 79 | *temp, 80 | 0.0, 81 | 0.0, 82 | false, 83 | self.material.clone(), 84 | ); 85 | ret.set_face_normal(r, (ret.p - self.center) / self.radius); 86 | let (u, v) = Sphere::spherical((ret.p - self.center) / self.radius); 87 | ret.u = u; 88 | ret.v = v; 89 | return Some(ret); 90 | } 91 | } 92 | } 93 | 94 | None 95 | } 96 | 97 | fn bounding_box(&self, _t0: f32, _t1: f32) -> Option { 98 | Some(AxisBB::new( 99 | self.center - Vec3::new_const(self.radius), 100 | self.center + Vec3::new_const(self.radius), 101 | )) 102 | } 103 | 104 | fn pdf_value(&self, o: Vec3, d: Vec3) -> f32 { 105 | if self.hit(&Ray::new(o,d), 0.001, f32::INFINITY).is_some() { 106 | let cos_theta_max = (1.0 - self.radius*self.radius/(self.center - o).length2()).sqrt(); 107 | let solid_angle = 2.0*std::f32::consts::PI*(1.0-cos_theta_max); 108 | 1.0 / solid_angle 109 | } 110 | else { 111 | 0.0 112 | } 113 | } 114 | 115 | fn random(&self, o: Vec3) -> Vec3 { 116 | let direction = self.center - o; 117 | let distance_squared = direction.length2(); 118 | let uvw = ONB::new_from_w(direction); 119 | uvw.local(random_to_sphere(self.radius, distance_squared)) 120 | } 121 | } 122 | 123 | fn random_to_sphere(radius: f32, distance_squared: f32) -> Vec3 { 124 | let mut rng = rand::thread_rng(); 125 | let r1 = rng.gen::(); 126 | let r2 = rng.gen::(); 127 | let z = 1.0 + r2*((1.0-radius*radius/distance_squared).sqrt() - 1.0); 128 | 129 | let phi = 2.0*std::f32::consts::PI*r1; 130 | let x = phi.cos()*(1.0-z*z); 131 | let y = phi.sin()*(1.0-z*z); 132 | 133 | Vec3::new(x,y,z) 134 | } 135 | 136 | #[derive(new)] 137 | pub struct MovingSphere { 138 | center0: Vec3, 139 | center1: Vec3, 140 | time0: f32, 141 | time1: f32, 142 | radius: f32, 143 | material: Arc, 144 | } 145 | 146 | impl MovingSphere { 147 | pub fn center(&self, time: f32) -> Vec3 { 148 | self.center0 149 | + (self.center1 - self.center0) * ((time - self.time0) / (self.time1 - self.time0)) 150 | } 151 | } 152 | 153 | impl Hittable for MovingSphere { 154 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option { 155 | let oc = r.origin - self.center(r.time); 156 | let a = r.direction.length2(); 157 | let half_b = oc.dot(r.direction); 158 | let c = oc.length2() - self.radius * self.radius; 159 | let discriminant = half_b * half_b - a * c; 160 | 161 | if discriminant > 0.0 { 162 | let root = discriminant.sqrt(); 163 | for temp in &[(-half_b - root) / a, (-half_b + root) / a] { 164 | if tmin < *temp && *temp < tmax { 165 | let mut ret = HitRec::new( 166 | r.at(*temp), 167 | (r.at(*temp) - self.center(r.time)) / self.radius, 168 | *temp, 169 | 0.0, 170 | 0.0, 171 | false, 172 | self.material.clone(), 173 | ); 174 | ret.set_face_normal(r, (ret.p - self.center(r.time)) / self.radius); 175 | let (u, v) = Sphere::spherical((ret.p - self.center(r.time)) / self.radius); 176 | ret.u = u; 177 | ret.v = v; 178 | return Some(ret); 179 | } 180 | } 181 | } 182 | 183 | None 184 | } 185 | 186 | fn bounding_box(&self, _t0: f32, _t1: f32) -> Option { 187 | let bb1 = AxisBB::new( 188 | self.center(self.time0) - Vec3::new_const(self.radius), 189 | self.center(self.time0) + Vec3::new_const(self.radius), 190 | ); 191 | let bb2 = AxisBB::new( 192 | self.center(self.time1) - Vec3::new_const(self.radius), 193 | self.center(self.time1) + Vec3::new_const(self.radius), 194 | ); 195 | Some(AxisBB::surrounding_box(bb1, bb2)) 196 | } 197 | } 198 | 199 | #[derive(new)] 200 | pub struct Rect { 201 | c0: f32, 202 | c1: f32, 203 | d0: f32, 204 | d1: f32, 205 | k: f32, 206 | axis0: usize, 207 | axis1: usize, 208 | axis2: usize, 209 | mat: Arc, 210 | } 211 | 212 | impl Rect { 213 | #[allow(non_snake_case)] 214 | pub fn XYRect(x0: f32, x1: f32, y0: f32, y1: f32, k: f32, mat: Arc) -> Rect { 215 | Rect::new(x0, x1, y0, y1, k, 0, 1, 2, mat) 216 | } 217 | 218 | #[allow(non_snake_case)] 219 | pub fn XZRect(x0: f32, x1: f32, z0: f32, z1: f32, k: f32, mat: Arc) -> Rect { 220 | Rect::new(x0, x1, z0, z1, k, 0, 2, 1, mat) 221 | } 222 | 223 | #[allow(non_snake_case)] 224 | pub fn YZRect(y0: f32, y1: f32, z0: f32, z1: f32, k: f32, mat: Arc) -> Rect { 225 | Rect::new(y0, y1, z0, z1, k, 1, 2, 0, mat) 226 | } 227 | } 228 | 229 | impl Hittable for Rect { 230 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option { 231 | let t = (self.k - r.origin[self.axis2]) / r.direction[self.axis2]; 232 | if t < tmin || t > tmax { 233 | return None; 234 | } 235 | let a = r.origin[self.axis0] + t * r.direction[self.axis0]; 236 | let b = r.origin[self.axis1] + t * r.direction[self.axis1]; 237 | if a < self.c0 || a > self.c1 || b < self.d0 || b > self.d1 { 238 | return None; 239 | } 240 | let mut outward_normal = Vec3::new_const(0.0); 241 | outward_normal[self.axis2] = 1.0; 242 | let u = (a - self.c0) / (self.c1 - self.c0); 243 | let v = (b - self.d0) / (self.d1 - self.d0); 244 | 245 | let mut ret = HitRec::new( 246 | r.at(t), 247 | Vec3::new_const(0.0), // overwritten later 248 | t, 249 | u, 250 | v, 251 | false, // overwritten later 252 | self.mat.clone(), 253 | ); 254 | ret.set_face_normal(r, outward_normal); 255 | Some(ret) 256 | } 257 | 258 | fn bounding_box(&self, _t0: f32, _t1: f32) -> Option { 259 | // Make the box a little thicc to prevent zero division 260 | let mut v1 = Vec3::new_const(0.0); 261 | let mut v2 = Vec3::new_const(0.0); 262 | v1[self.axis0] = self.c0; 263 | v1[self.axis1] = self.d0; 264 | v1[self.axis2] = self.k - 0.0001; 265 | v2[self.axis0] = self.c1; 266 | v2[self.axis1] = self.d1; 267 | v2[self.axis2] = self.k + 0.0001; 268 | Some(AxisBB::new(v1, v2)) 269 | } 270 | 271 | fn pdf_value(&self, origin: Vec3, v: Vec3) -> f32 { 272 | if let Some(rec) = self.hit(&Ray::new(origin, v), 0.001, f32::INFINITY) { 273 | let area = (self.c1 - self.c0) * (self.d1 - self.d0); 274 | let distance_squared = rec.t * rec.t * v.length2(); 275 | let cosine = v.dot(rec.normal).abs() / v.length(); 276 | 277 | distance_squared / (cosine * area) 278 | } 279 | else { 280 | 0.0 281 | } 282 | } 283 | 284 | fn random(&self, origin: Vec3) -> Vec3 { 285 | let mut rng = rand::thread_rng(); 286 | let mut random_point = Vec3::new_const(0.0); 287 | random_point[self.axis0] = rng.gen_range(self.c0, self.c1); 288 | random_point[self.axis1] = rng.gen_range(self.d0, self.d1); 289 | random_point[self.axis2] = self.k; 290 | random_point - origin 291 | } 292 | } 293 | 294 | #[derive(new)] 295 | pub struct FlipFace { 296 | ptr: Arc, 297 | } 298 | 299 | impl Hittable for FlipFace { 300 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option { 301 | if let Some(h) = self.ptr.hit(r, tmin, tmax) { 302 | Some(HitRec::new( 303 | h.p, h.normal, h.t, h.u, h.v, !h.front, h.material, 304 | )) 305 | } else { 306 | None 307 | } 308 | } 309 | fn bounding_box(&self, t0: f32, t1: f32) -> Option { 310 | self.ptr.bounding_box(t0, t1) 311 | } 312 | } 313 | 314 | pub struct Boxy { 315 | box_min: Vec3, 316 | box_max: Vec3, 317 | sides: Vec>, 318 | } 319 | 320 | impl Boxy { 321 | pub fn new(p0: Vec3, p1: Vec3, mat: Arc) -> Boxy { 322 | assert!(p0.x < p1.x); 323 | assert!(p0.y < p1.y); 324 | assert!(p0.z < p1.z); 325 | let sides: Vec> = vec![ 326 | Arc::new(Rect::XYRect(p0.x, p1.x, p0.y, p1.y, p1.z, mat.clone())), 327 | Arc::new(FlipFace::new(Arc::new(Rect::XYRect( 328 | p0.x, 329 | p1.x, 330 | p0.y, 331 | p1.y, 332 | p0.z, 333 | mat.clone(), 334 | )))), 335 | Arc::new(Rect::XZRect(p0.x, p1.x, p0.z, p1.z, p1.y, mat.clone())), 336 | Arc::new(FlipFace::new(Arc::new(Rect::XZRect( 337 | p0.x, 338 | p1.x, 339 | p0.z, 340 | p1.z, 341 | p0.y, 342 | mat.clone(), 343 | )))), 344 | Arc::new(Rect::YZRect(p0.y, p1.y, p0.z, p1.z, p1.x, mat.clone())), 345 | Arc::new(FlipFace::new(Arc::new(Rect::YZRect( 346 | p0.y, 347 | p1.y, 348 | p0.z, 349 | p1.z, 350 | p0.x, 351 | mat.clone(), 352 | )))), 353 | ]; 354 | Boxy { 355 | box_min: p0, 356 | box_max: p1, 357 | sides, 358 | } 359 | } 360 | } 361 | 362 | impl Hittable for Boxy { 363 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option { 364 | self.sides.hit(r, tmin, tmax) 365 | } 366 | 367 | fn bounding_box(&self, _t0: f32, _t1: f32) -> Option { 368 | Some(AxisBB::new(self.box_min, self.box_max)) 369 | } 370 | 371 | fn pdf_value(&self, o: Vec3, v: Vec3) -> f32 { 372 | self.sides.pdf_value(o, v) 373 | } 374 | 375 | fn random(&self, o: Vec3) -> Vec3 { 376 | self.sides.random(o) 377 | } 378 | } 379 | 380 | impl Hittable for Vec> { 381 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option { 382 | let mut closest_dist = tmax; 383 | let mut closest_rec: Option = None; 384 | for w in self { 385 | if let Some(rec) = w.hit(&r, tmin, closest_dist) { 386 | if rec.t < closest_dist { 387 | closest_dist = rec.t; 388 | closest_rec = Some(rec); 389 | } 390 | } 391 | } 392 | 393 | closest_rec 394 | } 395 | 396 | fn bounding_box(&self, t0: f32, t1: f32) -> Option { 397 | if self.is_empty() { 398 | return None; 399 | } 400 | 401 | let mut output_box: Option = None; 402 | let mut first_box = true; 403 | 404 | for o in self { 405 | if let Some(bb) = o.bounding_box(t0, t1) { 406 | if first_box { 407 | output_box = Some(bb); 408 | } else { 409 | output_box = Some(AxisBB::surrounding_box(bb, output_box.unwrap())); 410 | } 411 | } else { 412 | return None; 413 | } 414 | first_box = false; 415 | } 416 | 417 | output_box 418 | } 419 | 420 | fn pdf_value(&self, o: Vec3, v: Vec3) -> f32 { 421 | let weight = 1.0 / (self.len() as f32); 422 | let mut sum = 0.0_f32; 423 | for obj in self { 424 | sum += weight * obj.pdf_value(o, v); 425 | } 426 | sum 427 | } 428 | 429 | fn random(&self, o: Vec3) -> Vec3 { 430 | let mut rng = rand::thread_rng(); 431 | let obj = self.choose(&mut rng).unwrap(); 432 | obj.random(o) 433 | } 434 | } 435 | 436 | pub struct ConstantMedium { 437 | boundary: Arc, 438 | phase_function: Arc, 439 | neg_inv_density: f32, 440 | } 441 | 442 | impl ConstantMedium { 443 | pub fn new(boundary: Arc, density: f32, albedo: Arc) -> ConstantMedium { 444 | ConstantMedium { 445 | boundary, 446 | phase_function: Arc::new(Isotropic::new(albedo.clone())), 447 | neg_inv_density: -1.0 / density, 448 | } 449 | } 450 | } 451 | 452 | impl Hittable for ConstantMedium { 453 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option { 454 | let mut rng = rand::thread_rng(); 455 | // rec1 and rec2 here define the near and far points where 456 | // the ray hits the boundary of the object 457 | if let Some(mut rec1) = self.boundary.hit(r, f32::NEG_INFINITY, f32::INFINITY) { 458 | if let Some(mut rec2) = self.boundary.hit(r, rec1.t + 0.0001, f32::INFINITY) { 459 | if rec1.t < tmin { 460 | rec1.t = tmin 461 | } 462 | if rec2.t > tmax { 463 | rec2.t = tmax 464 | } 465 | if rec1.t >= rec2.t { 466 | return None; 467 | } 468 | if rec1.t < 0.0 { 469 | rec1.t = 0.0; 470 | } 471 | let ray_length = r.direction.length(); 472 | let distance_inside_boundary = (rec2.t - rec1.t) * ray_length; 473 | let hit_distance = self.neg_inv_density * rng.gen::().ln(); 474 | if hit_distance > distance_inside_boundary { 475 | return None; 476 | } 477 | 478 | // Construct the output 479 | let t = rec1.t + hit_distance / ray_length; 480 | 481 | return Some(HitRec::new( 482 | r.at(t), 483 | Vec3::new(1.0, 0.0, 0.0), // arbitrary 484 | t, 485 | rec1.u, 486 | rec1.v, // aribtrary 487 | true, // arbitrary 488 | self.phase_function.clone(), 489 | )); 490 | } 491 | } 492 | None 493 | } 494 | 495 | fn bounding_box(&self, t0: f32, t1: f32) -> Option { 496 | self.boundary.bounding_box(t0, t1) 497 | } 498 | } 499 | 500 | #[derive(new)] 501 | pub struct Translate { 502 | ptr: Arc, 503 | offset: Vec3, 504 | } 505 | 506 | impl Hittable for Translate { 507 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option { 508 | let moved_r = Ray::new_with_time(r.origin - self.offset, r.direction, r.time); 509 | if let Some(rec) = self.ptr.hit(&moved_r, tmin, tmax) { 510 | let mut moved_rec = HitRec::new( 511 | rec.p + self.offset, 512 | rec.normal, 513 | rec.t, 514 | rec.u, 515 | rec.v, 516 | rec.front, 517 | rec.material, 518 | ); 519 | moved_rec.set_face_normal(&moved_r, rec.normal); 520 | Some(moved_rec) 521 | } else { 522 | None 523 | } 524 | } 525 | fn bounding_box(&self, t0: f32, t1: f32) -> Option { 526 | if let Some(bb) = self.ptr.bounding_box(t0, t1) { 527 | Some(AxisBB::new(bb.min + self.offset, bb.max + self.offset)) 528 | } else { 529 | None 530 | } 531 | } 532 | } 533 | 534 | pub struct RotateY { 535 | ptr: Arc, 536 | sin_theta: f32, 537 | cos_theta: f32, 538 | bb: Option, 539 | } 540 | 541 | impl RotateY { 542 | pub fn new(p: Arc, angle: f32) -> RotateY { 543 | let sin_theta = angle.to_radians().sin(); 544 | let cos_theta = angle.to_radians().cos(); 545 | 546 | let bbox = p.bounding_box(0.0, 1.0).unwrap(); 547 | 548 | let mut min = Vec3::new_const(f32::INFINITY); 549 | let mut max = Vec3::new_const(f32::NEG_INFINITY); 550 | 551 | for i in 0..2 { 552 | for j in 0..2 { 553 | for k in 0..2 { 554 | let x = if i == 1 { bbox.max.x } else { bbox.min.x }; 555 | let y = if j == 1 { bbox.max.y } else { bbox.min.y }; 556 | let z = if k == 1 { bbox.max.z } else { bbox.min.z }; 557 | 558 | let new_x = cos_theta * x + sin_theta * z; 559 | let new_z = -sin_theta * x + cos_theta * z; 560 | let tester = Vec3::new(new_x, y, new_z); 561 | for c in 0..3 { 562 | min[c] = fmin(min[c], tester[c]); 563 | max[c] = fmax(max[c], tester[c]); 564 | } 565 | } 566 | } 567 | } 568 | 569 | RotateY { 570 | ptr: p, 571 | sin_theta, 572 | cos_theta, 573 | bb: Some(AxisBB::new(min, max)), 574 | } 575 | } 576 | } 577 | 578 | impl Hittable for RotateY { 579 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option { 580 | let mut origin = r.origin; 581 | let mut direction = r.direction; 582 | 583 | // Note that origin/direction are in world coordinates and 584 | // need to be transformed into object coords to do hit check. 585 | // This is the inverse of the transformation done when computing 586 | // the normal below, and relies on the fact that the inverse of 587 | // a rotation by t is a rotation by -t, and 588 | // cos(-t) = cos(t) 589 | // sin(-t) = -sin(t) 590 | // See https://github.com/RayTracing/raytracing.github.io/issues/544 591 | origin.x = self.cos_theta * r.origin.x - self.sin_theta * r.origin.z; 592 | origin.z = self.sin_theta * r.origin.x + self.cos_theta * r.origin.z; 593 | 594 | direction.x = self.cos_theta * r.direction.x - self.sin_theta * r.direction.z; 595 | direction.z = self.sin_theta * r.direction.x + self.cos_theta * r.direction.z; 596 | 597 | let rotated_r = Ray::new_with_time(origin, direction, r.time); 598 | 599 | if let Some(rec) = self.ptr.hit(&rotated_r, tmin, tmax) { 600 | let mut p = rec.p; 601 | let mut normal = rec.normal; 602 | 603 | p.x = self.cos_theta * rec.p.x + self.sin_theta * rec.p.z; 604 | p.z = -self.sin_theta * rec.p.x + self.cos_theta * rec.p.z; 605 | 606 | normal.x = self.cos_theta * rec.normal.x + self.sin_theta * rec.normal.z; 607 | normal.z = -self.sin_theta * rec.normal.x + self.cos_theta * rec.normal.z; 608 | 609 | let mut out_rec = HitRec::new( 610 | p, 611 | normal, 612 | rec.t, 613 | rec.u, 614 | rec.v, 615 | rec.front, 616 | rec.material.clone(), 617 | ); 618 | out_rec.set_face_normal(&rotated_r, normal); 619 | 620 | return Some(out_rec); 621 | } 622 | 623 | None 624 | } 625 | 626 | fn bounding_box(&self, _t0: f32, _t1: f32) -> Option { 627 | self.bb 628 | } 629 | } 630 | 631 | pub struct RotateX { 632 | ptr: Arc, 633 | sin_theta: f32, 634 | cos_theta: f32, 635 | bb: Option, 636 | } 637 | 638 | impl RotateX { 639 | pub fn new(p: Arc, angle: f32) -> RotateX { 640 | let sin_theta = angle.to_radians().sin(); 641 | let cos_theta = angle.to_radians().cos(); 642 | 643 | let bbox = p.bounding_box(0.0, 1.0).unwrap(); 644 | 645 | let mut min = Vec3::new_const(f32::INFINITY); 646 | let mut max = Vec3::new_const(f32::NEG_INFINITY); 647 | 648 | for i in 0..2 { 649 | for j in 0..2 { 650 | for k in 0..2 { 651 | let x = if i == 1 { bbox.max.x } else { bbox.min.x }; 652 | let y = if j == 1 { bbox.max.y } else { bbox.min.y }; 653 | let z = if k == 1 { bbox.max.z } else { bbox.min.z }; 654 | 655 | let new_y = cos_theta * y - sin_theta * z; 656 | let new_z = sin_theta * y + cos_theta * z; 657 | let tester = Vec3::new(x, new_y, new_z); 658 | for c in 0..3 { 659 | min[c] = fmin(min[c], tester[c]); 660 | max[c] = fmax(max[c], tester[c]); 661 | } 662 | } 663 | } 664 | } 665 | 666 | RotateX { 667 | ptr: p, 668 | sin_theta, 669 | cos_theta, 670 | bb: Some(AxisBB::new(min, max)), 671 | } 672 | } 673 | } 674 | 675 | impl Hittable for RotateX { 676 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option { 677 | let mut origin = r.origin; 678 | let mut direction = r.direction; 679 | 680 | origin.y = self.cos_theta * r.origin.y + self.sin_theta * r.origin.z; 681 | origin.z = -self.sin_theta * r.origin.y + self.cos_theta * r.origin.z; 682 | 683 | direction.y = self.cos_theta * r.direction.y + self.sin_theta * r.direction.z; 684 | direction.z = -self.sin_theta * r.direction.y + self.cos_theta * r.direction.z; 685 | 686 | let rotated_r = Ray::new_with_time(origin, direction, r.time); 687 | 688 | if let Some(rec) = self.ptr.hit(&rotated_r, tmin, tmax) { 689 | let mut p = rec.p; 690 | let mut normal = rec.normal; 691 | 692 | p.y = self.cos_theta * rec.p.y - self.sin_theta * rec.p.z; 693 | p.z = self.sin_theta * rec.p.y + self.cos_theta * rec.p.z; 694 | 695 | normal.y = self.cos_theta * rec.normal.y - self.sin_theta * rec.normal.z; 696 | normal.z = self.sin_theta * rec.normal.y + self.cos_theta * rec.normal.z; 697 | 698 | let mut out_rec = HitRec::new( 699 | p, 700 | normal, 701 | rec.t, 702 | rec.u, 703 | rec.v, 704 | rec.front, 705 | rec.material.clone(), 706 | ); 707 | out_rec.set_face_normal(&rotated_r, normal); 708 | 709 | return Some(out_rec); 710 | } 711 | 712 | None 713 | } 714 | 715 | fn bounding_box(&self, _t0: f32, _t1: f32) -> Option { 716 | self.bb 717 | } 718 | } 719 | 720 | pub struct RotateZ { 721 | ptr: Arc, 722 | sin_theta: f32, 723 | cos_theta: f32, 724 | bb: Option, 725 | } 726 | 727 | impl RotateZ { 728 | pub fn new(p: Arc, angle: f32) -> RotateZ { 729 | let sin_theta = angle.to_radians().sin(); 730 | let cos_theta = angle.to_radians().cos(); 731 | 732 | let bbox = p.bounding_box(0.0, 1.0).unwrap(); 733 | 734 | let mut min = Vec3::new_const(f32::INFINITY); 735 | let mut max = Vec3::new_const(f32::NEG_INFINITY); 736 | 737 | for i in 0..2 { 738 | for j in 0..2 { 739 | for k in 0..2 { 740 | let x = if i == 1 { bbox.max.x } else { bbox.min.x }; 741 | let y = if j == 1 { bbox.max.y } else { bbox.min.y }; 742 | let z = if k == 1 { bbox.max.z } else { bbox.min.z }; 743 | 744 | let new_x = cos_theta * x - sin_theta * y; 745 | let new_y = sin_theta * x + cos_theta * y; 746 | let tester = Vec3::new(new_x, new_y, z); 747 | for c in 0..3 { 748 | min[c] = fmin(min[c], tester[c]); 749 | max[c] = fmax(max[c], tester[c]); 750 | } 751 | } 752 | } 753 | } 754 | 755 | RotateZ { 756 | ptr: p, 757 | sin_theta, 758 | cos_theta, 759 | bb: Some(AxisBB::new(min, max)), 760 | } 761 | } 762 | } 763 | 764 | impl Hittable for RotateZ { 765 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option { 766 | let mut origin = r.origin; 767 | let mut direction = r.direction; 768 | 769 | origin.x = self.cos_theta * r.origin.x + self.sin_theta * r.origin.y; 770 | origin.y = -self.sin_theta * r.origin.x + self.cos_theta * r.origin.y; 771 | 772 | direction.x = self.cos_theta * r.direction.x + self.sin_theta * r.direction.y; 773 | direction.y = -self.sin_theta * r.direction.x + self.cos_theta * r.direction.y; 774 | 775 | let rotated_r = Ray::new_with_time(origin, direction, r.time); 776 | 777 | if let Some(rec) = self.ptr.hit(&rotated_r, tmin, tmax) { 778 | let mut p = rec.p; 779 | let mut normal = rec.normal; 780 | 781 | p.x = self.cos_theta * rec.p.x - self.sin_theta * rec.p.y; 782 | p.y = self.sin_theta * rec.p.x + self.cos_theta * rec.p.y; 783 | 784 | normal.x = self.cos_theta * rec.normal.x - self.sin_theta * rec.normal.y; 785 | normal.y = self.sin_theta * rec.normal.x + self.cos_theta * rec.normal.y; 786 | 787 | let mut out_rec = HitRec::new( 788 | p, 789 | normal, 790 | rec.t, 791 | rec.u, 792 | rec.v, 793 | rec.front, 794 | rec.material.clone(), 795 | ); 796 | out_rec.set_face_normal(&rotated_r, normal); 797 | 798 | return Some(out_rec); 799 | } 800 | 801 | None 802 | } 803 | 804 | fn bounding_box(&self, _t0: f32, _t1: f32) -> Option { 805 | self.bb 806 | } 807 | } 808 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | #[macro_use] 4 | extern crate derive_new; 5 | extern crate rand; 6 | extern crate rayon; 7 | 8 | use accel::BVHNode; 9 | use hittable::HittableSS; 10 | use rand::Rng; 11 | use rayon::prelude::*; 12 | use scene::{bowser_demo,cornell_box,final_scene,random_spheres_demo,perlin_demo,balls_demo}; 13 | use std::fs::File; 14 | use std::io::prelude::*; 15 | use std::io::BufWriter; 16 | use std::sync::Arc; 17 | use std::time::Instant; 18 | use util::{random_in_unit_disk,HittablePDF,MixturePDF,PDF}; 19 | use vec3::Vec3; 20 | 21 | mod accel; 22 | mod hittable; 23 | mod material; 24 | mod scene; 25 | mod util; 26 | mod vec3; 27 | 28 | const SAMPLES_PER_PIXEL: usize = 1000; 29 | const MAX_DEPTH: u32 = 100; 30 | 31 | #[derive(Copy, Clone)] 32 | pub struct Ray { 33 | origin: Vec3, 34 | direction: Vec3, 35 | time: f32, 36 | } 37 | 38 | impl Ray { 39 | fn new(origin: Vec3, direction: Vec3) -> Ray { 40 | Ray::new_with_time(origin, direction, 0.0) 41 | } 42 | 43 | fn new_with_time(origin: Vec3, direction: Vec3, time: f32) -> Ray { 44 | Ray { 45 | origin, 46 | direction, 47 | time, 48 | } 49 | } 50 | 51 | fn at(&self, t: f32) -> Vec3 { 52 | self.origin + self.direction * t 53 | } 54 | } 55 | 56 | #[derive(Copy, Clone)] 57 | pub struct Camera { 58 | origin: Vec3, 59 | lower_left_corner: Vec3, 60 | horizontal: Vec3, 61 | vertical: Vec3, 62 | u: Vec3, 63 | v: Vec3, 64 | w: Vec3, 65 | lens_radius: f32, 66 | time0: f32, 67 | time1: f32, 68 | } 69 | 70 | impl Camera { 71 | fn new( 72 | lookfrom: Vec3, 73 | lookat: Vec3, 74 | vup: Vec3, 75 | vfov: f32, 76 | aspect_ratio: f32, 77 | aperture: f32, 78 | focus_dist: f32, 79 | time0: f32, 80 | time1: f32, 81 | ) -> Camera { 82 | let theta = vfov.to_radians(); 83 | let h = (theta / 2.0).tan(); 84 | let viewport_height = h * 2.0; 85 | let viewport_width = aspect_ratio * viewport_height; 86 | 87 | let w = (lookfrom - lookat).unit_vector(); 88 | let u = vup.cross(w).unit_vector(); 89 | let v = w.cross(u); 90 | 91 | let origin = lookfrom; 92 | let horizontal = u * viewport_width * focus_dist; 93 | let vertical = v * viewport_height * focus_dist; 94 | let lower_left_corner = origin - horizontal / 2.0 - vertical / 2.0 - w * focus_dist; 95 | let lens_radius = aperture / 2.0; 96 | 97 | Camera { 98 | origin, 99 | lower_left_corner, 100 | horizontal, 101 | vertical, 102 | u, 103 | v, 104 | w, 105 | lens_radius, 106 | time0, 107 | time1, 108 | } 109 | } 110 | 111 | fn get_ray(&self, s: f32, t: f32) -> Ray { 112 | let mut rng = rand::thread_rng(); 113 | let rd = random_in_unit_disk() * self.lens_radius; 114 | let offset = self.u * rd.x + self.v * rd.y; 115 | Ray::new_with_time( 116 | self.origin + offset, 117 | self.lower_left_corner + self.horizontal * s + self.vertical * t - self.origin - offset, 118 | rng.gen_range(self.time0, self.time1), 119 | ) 120 | } 121 | } 122 | 123 | fn ray_color(r: Ray, world: Arc, lights: Arc, depth: u32) -> Vec3 { 124 | let background = Vec3::new_const(0.0); 125 | 126 | if depth > MAX_DEPTH { 127 | return Vec3::new_const(0.0); 128 | } 129 | 130 | if let Some(c) = world.hit(&r, 0.001, f32::INFINITY) { 131 | let emitted = c.material.emitted(&c, c.u, c.v, c.p); 132 | if let Some(srec) = c.material.scatter_with_pdf(r, &c) { 133 | // Specular! 134 | if let Some(spec) = srec.specular_ray { 135 | return srec.attenuation * 136 | ray_color(spec, world, lights, depth + 1); 137 | } 138 | 139 | let p_important = HittablePDF::new(lights.clone(), c.p); 140 | let p = MixturePDF::new(Arc::new(p_important), 0.5, srec.pdf.clone(), 0.5); 141 | 142 | let scattered = Ray::new_with_time(c.p, p.generate(), r.time); 143 | let pdf = p.value(scattered.direction); 144 | emitted + 145 | srec.attenuation * c.material.scattering_pdf(r, &c, scattered) * 146 | ray_color(scattered, world, lights, depth + 1) / pdf 147 | } else { 148 | emitted 149 | } 150 | } else { 151 | background 152 | } 153 | } 154 | 155 | fn main() -> Result<(), std::io::Error> { 156 | // Camera and world 157 | eprintln!("Generating scene..."); 158 | 159 | let mut config = match 1 { 160 | 0 => bowser_demo(), 161 | 1 => cornell_box(), 162 | 2 => final_scene(), 163 | 3 => random_spheres_demo(), 164 | 4 => perlin_demo(), 165 | 5 => balls_demo(), 166 | _ => panic!("Not a valid scene"), 167 | }; 168 | let world_bvh = Arc::new(BVHNode::new(&mut config.world[..])); 169 | let important = Arc::new(config.lights); 170 | 171 | let width: usize = 900; 172 | let height: usize = ((width as f32) / config.aspect_ratio) as usize; 173 | let mut pixels: Vec = vec![Vec3::new_const(0.0); width * height]; 174 | 175 | let mut file_idx = 0; 176 | for cam in config.cam_iter { 177 | // Timing 178 | let start = Instant::now(); 179 | 180 | // Trace rays 181 | pixels.par_iter_mut().enumerate().for_each(|(i, pix)| { 182 | let x = i % width; 183 | let y = i / width; 184 | let mut rng = rand::thread_rng(); 185 | let mut c = Vec3::new_const(0.0); 186 | for _ in 0..SAMPLES_PER_PIXEL { 187 | let u = ((x as f32) + rng.gen::()) / ((width - 1) as f32); 188 | let v = ((y as f32) + rng.gen::()) / ((height - 1) as f32); 189 | let ray = cam.get_ray(u, v); 190 | let color = ray_color(ray, world_bvh.clone(), important.clone(), 1); 191 | // Filter NaN and infinity 192 | if color.x.is_finite() && color.y.is_finite() && color.z.is_finite() { 193 | c += color; 194 | } 195 | } 196 | c /= SAMPLES_PER_PIXEL as f32; 197 | *pix = c; 198 | }); 199 | 200 | // Write output 201 | let filename = format!("output_{:04}.ppm", file_idx); 202 | let file = File::create(filename.clone()).unwrap(); 203 | let mut wr = BufWriter::new(&file); 204 | 205 | writeln!(&mut wr, "P3")?; 206 | writeln!(&mut wr, "{} {}", width, height)?; 207 | writeln!(&mut wr, "255")?; 208 | 209 | for y in { 0..height }.rev() { 210 | for x in 0..width { 211 | let (ir, ig, ib) = pixels[y * width + x].to_color(); 212 | writeln!(&mut wr, "{} {} {}", ir, ig, ib)?; 213 | } 214 | } 215 | eprintln!("Wrote frame {} in {}s", filename, start.elapsed().as_secs()); 216 | 217 | file_idx += 1; 218 | } 219 | 220 | Ok(()) 221 | } 222 | -------------------------------------------------------------------------------- /src/material.rs: -------------------------------------------------------------------------------- 1 | // Materials and textures 2 | 3 | use crate::hittable::HitRec; 4 | use crate::rand::Rng; 5 | use crate::util::{random_in_unit_sphere, reflect, refract, schlick, CosinePDF, PDFSS}; 6 | use crate::vec3::Vec3; 7 | use crate::Ray; 8 | use array_init::array_init; 9 | use rand::seq::SliceRandom; 10 | use std::fs::File; 11 | use std::sync::Arc; 12 | 13 | #[derive(new)] 14 | pub struct ScatterRec { 15 | pub specular_ray: Option, 16 | pub attenuation: Vec3, 17 | pub pdf: Arc, 18 | } 19 | 20 | pub trait Material { 21 | fn scatter(&self, r: Ray, rec: &HitRec) -> Option<(Vec3, Ray)> { 22 | if let Some(srec) = self.scatter_with_pdf(r, rec) { 23 | Some((srec.attenuation, srec.specular_ray.unwrap())) 24 | } 25 | else { 26 | None 27 | } 28 | } 29 | 30 | fn scatter_with_pdf(&self, _r: Ray, _rec: &HitRec) -> Option { 31 | None 32 | } 33 | 34 | fn scattering_pdf(&self, _r: Ray, _rec: &HitRec, _s: Ray) -> f32 { 35 | 0.0 36 | } 37 | 38 | fn emitted(&self, _rec: &HitRec, _u: f32, _v: f32, _p: Vec3) -> Vec3 { 39 | Vec3::new_const(0.0) 40 | } 41 | } 42 | 43 | pub type MaterialSS = dyn Material + Send + Sync; 44 | 45 | #[derive(new)] 46 | pub struct Lambertian { 47 | albedo: Arc, 48 | } 49 | 50 | impl Lambertian { 51 | fn random() -> Vec3 { 52 | let mut rng = rand::thread_rng(); 53 | let a = rng.gen_range(0.0, 2.0 * std::f32::consts::PI); 54 | let z = rng.gen_range(-1.0, 1.0); 55 | let r = ((1.0 - z * z) as f32).sqrt(); 56 | 57 | Vec3::new(r * a.cos(), r * a.sin(), z) 58 | } 59 | 60 | fn random_in_hemisphere(normal: Vec3) -> Vec3 { 61 | let in_unit_sphere = random_in_unit_sphere(); 62 | if in_unit_sphere.dot(normal) > 0.0 { 63 | in_unit_sphere 64 | } 65 | else { 66 | -in_unit_sphere 67 | } 68 | } 69 | 70 | fn random_cosine_direction() -> Vec3 { 71 | let mut rng = rand::thread_rng(); 72 | let r1 = rng.gen::(); 73 | let r2 = rng.gen::(); 74 | let z = (1.0-r2).sqrt(); 75 | 76 | let phi = 2.0*r1*std::f32::consts::PI; 77 | let x = phi.cos()*r2.sqrt(); 78 | let y = phi.sin()*r2.sqrt(); 79 | 80 | Vec3::new(x, y, z) 81 | } 82 | } 83 | 84 | impl Material for Lambertian { 85 | fn scatter(&self, r: Ray, rec: &HitRec) -> Option<(Vec3,Ray)> { 86 | let scatter_direction = rec.normal + Lambertian::random(); 87 | let scattered = Ray::new_with_time(rec.p, scatter_direction, r.time); 88 | let attenuation = self.albedo.value(rec.u, rec.v, rec.p); 89 | Some((attenuation, scattered)) 90 | } 91 | 92 | fn scatter_with_pdf(&self, _r: Ray, rec: &HitRec) -> Option { 93 | Some(ScatterRec::new( 94 | None, 95 | self.albedo.value(rec.u, rec.v, rec.p), 96 | Arc::new(CosinePDF::new(rec.normal)), 97 | )) 98 | } 99 | 100 | fn scattering_pdf(&self, _r: Ray, rec: &HitRec, s: Ray) -> f32 { 101 | let cos = rec.normal.dot(s.direction.unit_vector()); 102 | if cos < 0.0 { 103 | 0.0 104 | } 105 | else { 106 | cos / std::f32::consts::PI 107 | } 108 | } 109 | } 110 | 111 | #[derive(new)] 112 | pub struct Metal { 113 | albedo: Arc, 114 | fuzz: f32, 115 | } 116 | 117 | impl Material for Metal { 118 | fn scatter(&self, r: Ray, rec: &HitRec) -> Option<(Vec3,Ray)> { 119 | let reflected = reflect(r.direction.unit_vector(), rec.normal); 120 | let scattered = Ray::new_with_time( 121 | rec.p, 122 | reflected + random_in_unit_sphere() * self.fuzz, 123 | r.time, 124 | ); 125 | let attenuation = self.albedo.value(rec.u, rec.v, rec.p); 126 | if scattered.direction.dot(rec.normal) > 0.0 { 127 | Some((attenuation, scattered)) 128 | } 129 | else { 130 | None 131 | } 132 | } 133 | 134 | fn scatter_with_pdf(&self, r: Ray, rec: &HitRec) -> Option { 135 | let reflected = reflect(r.direction.unit_vector(), rec.normal); 136 | Some(ScatterRec::new( 137 | Some(Ray::new(rec.p, reflected+random_in_unit_sphere()*self.fuzz)), 138 | self.albedo.value(rec.u, rec.v, rec.p), 139 | Arc::new(CosinePDF::new(rec.normal)), // No null pointers in rust - make it an Option? 140 | )) 141 | } 142 | } 143 | 144 | #[derive(new)] 145 | pub struct Dielectric { 146 | ref_idx: f32, 147 | } 148 | 149 | impl Material for Dielectric { 150 | fn scatter(&self, r: Ray, rec: &HitRec) -> Option<(Vec3,Ray)> { 151 | let mut rng = rand::thread_rng(); 152 | let attenuation = Vec3::new_const(1.0); 153 | let etai_over_etat = if rec.front { 154 | 1.0 / self.ref_idx 155 | } else { 156 | self.ref_idx 157 | }; 158 | let unit_direction = r.direction.unit_vector(); 159 | let cos_theta = (-unit_direction).dot(rec.normal).min(1.0); 160 | let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); 161 | if etai_over_etat * sin_theta > 1.0 { 162 | let reflected = reflect(unit_direction, rec.normal); 163 | let scattered = Ray::new_with_time(rec.p, reflected, r.time); 164 | return Some((attenuation, scattered)); 165 | } 166 | let reflect_prob = schlick(cos_theta, etai_over_etat); 167 | if rng.gen::() < reflect_prob { 168 | let reflected = reflect(unit_direction, rec.normal); 169 | let scattered = Ray::new_with_time(rec.p, reflected, r.time); 170 | return Some((attenuation, scattered)); 171 | } 172 | let refracted = refract(unit_direction, rec.normal, etai_over_etat); 173 | let scattered = Ray::new_with_time(rec.p, refracted, r.time); 174 | Some((attenuation, scattered)) 175 | } 176 | 177 | fn scatter_with_pdf(&self, r: Ray, rec: &HitRec) -> Option { 178 | let mut rng = rand::thread_rng(); 179 | let mut srec = ScatterRec::new( 180 | None, // fill in later 181 | Vec3::new_const(1.0), // white 182 | Arc::new(CosinePDF::new(rec.normal)), // No null pointers in rust - make it an Option? 183 | ); 184 | let etai_over_etat = if rec.front { 185 | 1.0 / self.ref_idx 186 | } else { 187 | self.ref_idx 188 | }; 189 | let unit_direction = r.direction.unit_vector(); 190 | let cos_theta = (-unit_direction).dot(rec.normal).min(1.0); 191 | let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); 192 | if etai_over_etat * sin_theta > 1.0 { 193 | let reflected = reflect(unit_direction, rec.normal); 194 | srec.specular_ray = Some(Ray::new_with_time(rec.p, reflected, r.time)); 195 | return Some(srec); 196 | } 197 | let reflect_prob = schlick(cos_theta, etai_over_etat); 198 | if rng.gen::() < reflect_prob { 199 | let reflected = reflect(unit_direction, rec.normal); 200 | srec.specular_ray = Some(Ray::new_with_time(rec.p, reflected, r.time)); 201 | return Some(srec); 202 | } 203 | let refracted = refract(unit_direction, rec.normal, etai_over_etat); 204 | srec.specular_ray = Some(Ray::new_with_time(rec.p, refracted, r.time)); 205 | Some(srec) 206 | } 207 | } 208 | 209 | #[derive(new)] 210 | pub struct DiffuseLight { 211 | emit: Arc, 212 | } 213 | 214 | impl Material for DiffuseLight { 215 | fn scatter(&self, _r: Ray, _rec: &HitRec) -> Option<(Vec3,Ray)> { 216 | None 217 | } 218 | fn emitted(&self, rec: &HitRec, u: f32, v: f32, p: Vec3) -> Vec3 { 219 | if rec.front { 220 | self.emit.value(u, v, p) 221 | } 222 | else { 223 | Vec3::new_const(0.0) 224 | } 225 | } 226 | } 227 | 228 | pub trait Texture { 229 | fn value(&self, u: f32, v: f32, p: Vec3) -> Vec3; 230 | } 231 | pub type TextureSS = dyn Texture + Send + Sync; 232 | 233 | #[derive(new)] 234 | pub struct SolidColor { 235 | color_value: Vec3, 236 | } 237 | 238 | impl Texture for SolidColor { 239 | fn value(&self, _u: f32, _v: f32, _p: Vec3) -> Vec3 { 240 | self.color_value 241 | } 242 | } 243 | 244 | #[derive(new)] 245 | pub struct Checker { 246 | odd: Arc, 247 | even: Arc, 248 | } 249 | 250 | impl Texture for Checker { 251 | fn value(&self, u: f32, v: f32, p: Vec3) -> Vec3 { 252 | let sins = (10.0 * p.x).sin() * (10.0 * p.y).sin() * (10.0 * p.z).sin(); 253 | if sins < 0.0 { 254 | self.odd.value(u, v, p) 255 | } else { 256 | self.even.value(u, v, p) 257 | } 258 | } 259 | } 260 | 261 | pub struct ImageTexture { 262 | buf: Vec, 263 | width: usize, 264 | height: usize, 265 | } 266 | 267 | const BPP: usize = 3; 268 | impl ImageTexture { 269 | pub fn new(path: &str) -> ImageTexture { 270 | let decoder = png::Decoder::new(File::open(path).unwrap()); 271 | let (info, mut reader) = decoder.read_info().unwrap(); 272 | let mut buf = vec![0; info.buffer_size()]; 273 | reader.next_frame(&mut buf).unwrap(); 274 | ImageTexture { 275 | buf, 276 | width: info.width as usize, 277 | height: info.height as usize, 278 | } 279 | } 280 | } 281 | 282 | impl Texture for ImageTexture { 283 | fn value(&self, u: f32, v: f32, _p: Vec3) -> Vec3 { 284 | let u = Vec3::clamp(u, 0.0, 1.0); 285 | let v = 1.0 - Vec3::clamp(v, 0.0, 1.0); 286 | let mut i = (u * self.width as f32) as usize; 287 | let mut j = (v * self.height as f32) as usize; 288 | if i >= self.width { 289 | i = self.width - 1; 290 | } 291 | if j >= self.height { 292 | j = self.height - 1; 293 | } 294 | 295 | let buf_start = j * self.width * BPP + i * BPP; 296 | let pix = &self.buf[buf_start..buf_start + BPP]; 297 | let color_scale = 1.0 / 255.0; 298 | Vec3::new( 299 | color_scale * pix[0] as f32, 300 | color_scale * pix[1] as f32, 301 | color_scale * pix[2] as f32, 302 | ) 303 | } 304 | } 305 | 306 | pub struct Perlin { 307 | random_data: [Vec3; 256], 308 | perm_x: [usize; 256], 309 | perm_y: [usize; 256], 310 | perm_z: [usize; 256], 311 | } 312 | 313 | pub fn trilinear_interp(c: &[[[f32; 2]; 2]; 2], u: f32, v: f32, w: f32) -> f32 { 314 | let mut accum: f32 = 0.0; 315 | for (i, c1) in c.iter().enumerate() { 316 | for (j, c2) in c1.iter().enumerate() { 317 | for (k, c3) in c2.iter().enumerate() { 318 | let fi = i as f32; 319 | let fj = j as f32; 320 | let fk = k as f32; 321 | accum += (fi * u + (1.0 - fi) * (1.0 - u)) 322 | * (fj * v + (1.0 - fj) * (1.0 - v)) 323 | * (fk * w + (1.0 - fk) * (1.0 - w)) 324 | * c3; 325 | } 326 | } 327 | } 328 | accum 329 | } 330 | 331 | pub fn perlin_interp(c: &[[[Vec3; 2]; 2]; 2], u: f32, v: f32, w: f32) -> f32 { 332 | let mut accum: f32 = 0.0; 333 | let uu = u * u * (3.0 - 2.0 * u); 334 | let vv = v * v * (3.0 - 2.0 * v); 335 | let ww = w * w * (3.0 - 2.0 * w); 336 | 337 | for (i, c1) in c.iter().enumerate() { 338 | for (j, c2) in c1.iter().enumerate() { 339 | for (k, c3) in c2.iter().enumerate() { 340 | let fi = i as f32; 341 | let fj = j as f32; 342 | let fk = k as f32; 343 | let weight_v = Vec3::new(u - fi, v - fj, w - fk); 344 | accum += (fi * uu + (1.0 - fi) * (1.0 - uu)) 345 | * (fj * vv + (1.0 - fj) * (1.0 - vv)) 346 | * (fk * ww + (1.0 - fk) * (1.0 - ww)) 347 | * c3.dot(weight_v); 348 | } 349 | } 350 | } 351 | accum 352 | } 353 | 354 | impl Perlin { 355 | const NUM_POINTS: usize = 256; 356 | 357 | pub fn new() -> Perlin { 358 | let mut rng = rand::thread_rng(); 359 | 360 | let random_data: [Vec3; Perlin::NUM_POINTS] = 361 | array_init(|_| Vec3::random_range(-1.0, 1.0).unit_vector()); 362 | 363 | // Permutation arrays 364 | let mut perm_x: [usize; Perlin::NUM_POINTS] = array_init(|i| i); 365 | let mut perm_y: [usize; Perlin::NUM_POINTS] = array_init(|i| i); 366 | let mut perm_z: [usize; Perlin::NUM_POINTS] = array_init(|i| i); 367 | perm_x.shuffle(&mut rng); 368 | perm_y.shuffle(&mut rng); 369 | perm_z.shuffle(&mut rng); 370 | 371 | Perlin { 372 | random_data, 373 | perm_x, 374 | perm_y, 375 | perm_z, 376 | } 377 | } 378 | 379 | pub fn turb(&self, p: Vec3, depth: usize) -> f32 { 380 | let mut accum = 0.0; 381 | let mut temp_p = p; 382 | let mut weight = 1.0_f32; 383 | for _ in 0..depth { 384 | accum += weight * self.noise(temp_p); 385 | weight *= 0.5; 386 | temp_p *= 2.0; 387 | } 388 | 389 | accum.abs() 390 | } 391 | 392 | pub fn noise(&self, p: Vec3) -> f32 { 393 | let u = p.x - p.x.floor(); 394 | let v = p.y - p.y.floor(); 395 | let w = p.z - p.z.floor(); 396 | 397 | let mut c = [[[Vec3::new_const(0.0); 2]; 2]; 2]; 398 | 399 | let i = p.x.floor() as usize; 400 | let j = p.y.floor() as usize; 401 | let k = p.z.floor() as usize; 402 | for (di, c1) in c.iter_mut().enumerate() { 403 | for (dj, c2) in c1.iter_mut().enumerate() { 404 | for (dk, c3) in c2.iter_mut().enumerate() { 405 | *c3 = self.random_data[self.perm_x[(i + di) & 255] 406 | ^ self.perm_y[(j + dj) & 255] 407 | ^ self.perm_z[(k + dk) & 255]]; 408 | } 409 | } 410 | } 411 | 412 | perlin_interp(&c, u, v, w) 413 | } 414 | } 415 | 416 | pub struct NoiseTexture { 417 | noise: Perlin, 418 | scale: f32, 419 | } 420 | 421 | impl NoiseTexture { 422 | pub fn new(scale: f32) -> NoiseTexture { 423 | NoiseTexture { 424 | noise: Perlin::new(), 425 | scale, 426 | } 427 | } 428 | } 429 | 430 | impl Texture for NoiseTexture { 431 | fn value(&self, _u: f32, _v: f32, p: Vec3) -> Vec3 { 432 | Vec3::new_const(1.0) * 0.5 * (1.0 + (self.scale * p.z + 10.0 * self.noise.turb(p, 7)).sin()) 433 | } 434 | } 435 | 436 | #[derive(new)] 437 | pub struct Isotropic { 438 | albedo: Arc, 439 | } 440 | 441 | impl Material for Isotropic { 442 | fn scatter(&self, r: Ray, rec: &HitRec) -> Option<(Vec3,Ray)> { 443 | let scattered = Ray::new_with_time(rec.p, random_in_unit_sphere(), r.time); 444 | let attenuation = self.albedo.value(rec.u, rec.v, rec.p); 445 | Some((attenuation, scattered)) 446 | } 447 | 448 | fn scatter_with_pdf(&self, _r: Ray, rec: &HitRec) -> Option { 449 | Some(ScatterRec::new( 450 | None, 451 | self.albedo.value(rec.u, rec.v, rec.p), 452 | Arc::new(CosinePDF::new(rec.normal)), 453 | )) 454 | } 455 | 456 | fn scattering_pdf(&self, _r: Ray, rec: &HitRec, s: Ray) -> f32 { 457 | let cos = rec.normal.dot(s.direction.unit_vector()); 458 | if cos < 0.0 { 459 | 0.0 460 | } 461 | else { 462 | cos / std::f32::consts::PI 463 | } 464 | } 465 | } 466 | 467 | #[derive(new)] 468 | pub struct SpecDiffuse { 469 | specular: Arc, 470 | diffuse: Arc, 471 | pct: f32, 472 | } 473 | 474 | impl Material for SpecDiffuse { 475 | fn scatter_with_pdf(&self, r: Ray, rec: &HitRec) -> Option { 476 | let mut rng = rand::thread_rng(); 477 | if rng.gen::() < self.pct { 478 | self.specular.scatter_with_pdf(r, rec) 479 | } 480 | else { 481 | self.diffuse.scatter_with_pdf(r, rec) 482 | } 483 | } 484 | 485 | fn scattering_pdf(&self, r: Ray, rec: &HitRec, s: Ray) -> f32 { 486 | self.diffuse.scattering_pdf(r, rec, s) 487 | } 488 | } 489 | -------------------------------------------------------------------------------- /src/scene.rs: -------------------------------------------------------------------------------- 1 | use crate::accel::{AxisBB, BVHNode}; 2 | use crate::hittable::HitRec; 3 | use crate::hittable::{ 4 | Boxy, ConstantMedium, FlipFace, Hittable, HittableSS, MovingSphere, Rect, RotateX, RotateY, 5 | RotateZ, Sphere, Translate, 6 | }; 7 | use crate::material::{ 8 | Checker, Dielectric, DiffuseLight, ImageTexture, Lambertian, Metal, NoiseTexture, SolidColor, 9 | }; 10 | use crate::vec3::Vec3; 11 | use crate::Ray; 12 | use crate::Camera; 13 | use rand::Rng; 14 | use std::sync::Arc; 15 | 16 | #[derive(new)] 17 | pub struct SceneConfig { 18 | pub world: Vec>, 19 | pub lights: Vec>, 20 | pub cam_iter: Box>, 21 | pub aspect_ratio: f32, 22 | } 23 | 24 | struct FixedCamera { 25 | cam: Camera, 26 | called: bool, 27 | } 28 | 29 | impl FixedCamera { 30 | fn new(cam: Camera) -> FixedCamera { 31 | FixedCamera { cam, called: false } 32 | } 33 | } 34 | 35 | impl Iterator for FixedCamera { 36 | type Item = Camera; 37 | fn next(&mut self) -> Option { 38 | if !self.called { 39 | self.called = true; 40 | Some(self.cam) 41 | } 42 | else { 43 | None 44 | } 45 | } 46 | } 47 | 48 | #[derive(new)] 49 | struct RotatingCamera { 50 | lookat: Vec3, 51 | vup: Vec3, 52 | vfov: f32, 53 | aspect_ratio: f32, 54 | aperture: f32, 55 | focus_dist: f32, 56 | time0: f32, 57 | time1: f32, 58 | height: f32, 59 | angle: f32, 60 | radius: f32, 61 | incr: f32, 62 | limit: f32, 63 | } 64 | 65 | impl Iterator for RotatingCamera { 66 | type Item = Camera; 67 | fn next(&mut self) -> Option { 68 | if self.angle > self.limit { 69 | return None; 70 | } 71 | 72 | // Set up camera 73 | let look_x = self.radius * self.angle.to_radians().cos(); 74 | let look_z = self.radius * self.angle.to_radians().sin(); 75 | let lookfrom = Vec3::new(look_x, self.height, look_z); 76 | 77 | let cam = Camera::new( 78 | lookfrom, 79 | self.lookat, 80 | self.vup, 81 | self.vfov, 82 | self.aspect_ratio, 83 | self.aperture, 84 | self.focus_dist, 85 | self.time0, 86 | self.time1, 87 | ); 88 | self.angle += self.incr; 89 | Some(cam) 90 | } 91 | } 92 | 93 | pub fn balls_demo() -> SceneConfig { 94 | let mut world: Vec> = vec![]; 95 | let mut lights: Vec> = vec![]; 96 | world.push(Arc::new(Sphere::new( 97 | Vec3::new(0.0, 0.0, -1.0), 98 | 0.5, 99 | Arc::new(Lambertian::new(Arc::new(SolidColor::new(Vec3::new( 100 | 0.1, 0.2, 0.5, 101 | ))))), 102 | ))); 103 | world.push(Arc::new(Sphere::new( 104 | Vec3::new(0.0, -100.5, -1.0), 105 | 100.0, 106 | Arc::new(Lambertian::new(Arc::new(SolidColor::new(Vec3::new( 107 | 0.8, 0.8, 0.8, 108 | ))))), 109 | ))); 110 | world.push(Arc::new(Sphere::new( 111 | Vec3::new(1.0, 0.0, -1.0), 112 | 0.5, 113 | Arc::new(Metal::new( 114 | Arc::new(SolidColor::new(Vec3::new(0.8, 0.6, 0.2))), 115 | 0.3, 116 | )), 117 | ))); 118 | world.push(Arc::new(Sphere::new( 119 | Vec3::new(-1.0, 0.0, -1.0), 120 | 0.5, 121 | Arc::new(Dielectric::new(1.5)), 122 | ))); 123 | world.push(Arc::new(Sphere::new( 124 | Vec3::new(-1.0, 0.0, -1.0), 125 | -0.45, 126 | Arc::new(Dielectric::new(1.5)), 127 | ))); 128 | 129 | // Light 130 | let light_shape = Arc::new(Rect::XZRect( 131 | -6.0, 132 | 6.0, 133 | -6.0, 134 | 6.0, 135 | 8.0, 136 | Arc::new(DiffuseLight::new(Arc::new(SolidColor::new( 137 | Vec3::new_const(4.0), 138 | )))))); 139 | world.push(Arc::new(FlipFace::new(light_shape.clone()))); 140 | lights.push(light_shape); 141 | 142 | let lookfrom = Vec3::new(0.0, 2.0, 10.0); 143 | let lookat = Vec3::new(0.0,1.0,0.0); 144 | let vup = Vec3::new(0.0,1.0,0.0); 145 | let dist_to_focus = 10.0; 146 | let aperture = 0.0; 147 | let vfov = 40.0; 148 | let aspect_ratio = 16.0/9.0; 149 | let time0 = 0.0; 150 | let time1 = 1.0; 151 | 152 | let cam = Camera::new( 153 | lookfrom, 154 | lookat, 155 | vup, 156 | vfov, 157 | aspect_ratio, 158 | aperture, 159 | dist_to_focus, 160 | time0, 161 | time1, 162 | ); 163 | 164 | SceneConfig::new(world, lights, Box::new(FixedCamera::new(cam)), aspect_ratio) 165 | } 166 | 167 | pub fn random_spheres_demo() -> SceneConfig { 168 | let mut world: Vec> = vec![]; 169 | let mut lights: Vec> = vec![]; 170 | // Ground 171 | let checker = Checker::new( 172 | Arc::new(SolidColor::new(Vec3::new(0.1, 0.1, 0.1))), 173 | Arc::new(SolidColor::new(Vec3::new(0.9, 0.9, 0.9))), 174 | ); 175 | let ground_material = Arc::new(Lambertian::new(Arc::new(checker))); 176 | world.push(Arc::new(Sphere::new( 177 | Vec3::new(0.0, -1000.0, 0.0), 178 | 1000.0, 179 | ground_material, 180 | ))); 181 | 182 | // Random spheres 183 | let mut rng = rand::thread_rng(); 184 | for a in -11..11 { 185 | for b in -11..11 { 186 | let choose_mat = rng.gen::(); 187 | let center = Vec3::new( 188 | a as f32 + 0.9 * rng.gen::(), 189 | 0.2, 190 | b as f32 + 0.9 * rng.gen::(), 191 | ); 192 | 193 | if (center - Vec3::new(4.0, 0.2, 0.0)).length() > 0.9 { 194 | if choose_mat < 0.8 { 195 | let albedo = Vec3::random() * Vec3::random(); 196 | world.push(Arc::new(Sphere::new( 197 | center, 198 | 0.2, 199 | Arc::new(Lambertian::new(Arc::new(SolidColor::new(albedo)))), 200 | ))); 201 | } else if choose_mat < 0.95 { 202 | let albedo = SolidColor::new(Vec3::random_range(0.5, 1.0)); 203 | let fuzz = rng.gen_range(0.0, 0.5); 204 | world.push(Arc::new(Sphere::new( 205 | center, 206 | 0.2, 207 | Arc::new(Metal::new(Arc::new(albedo), fuzz)), 208 | ))); 209 | } else { 210 | world.push(Arc::new(Sphere::new( 211 | center, 212 | 0.2, 213 | Arc::new(Dielectric::new(1.5)), 214 | ))); 215 | } 216 | } 217 | } 218 | } 219 | 220 | // Three big spheres 221 | world.push(Arc::new(Sphere::new( 222 | Vec3::new(0.0, 1.0, 0.0), 223 | 1.0, 224 | Arc::new(Dielectric::new(1.5)), 225 | ))); 226 | world.push(Arc::new(Sphere::new( 227 | Vec3::new(-4.0, 1.0, 0.0), 228 | 1.0, 229 | Arc::new(Lambertian::new(Arc::new(ImageTexture::new( 230 | "assets/earthmap.png", 231 | )))), 232 | ))); 233 | world.push(Arc::new(Sphere::new( 234 | Vec3::new(4.0, 1.0, 0.0), 235 | 1.0, 236 | Arc::new(Metal::new( 237 | Arc::new(SolidColor::new(Vec3::new(0.7, 0.6, 0.5))), 238 | 0.0, 239 | )), 240 | ))); 241 | 242 | // Big light in the sky 243 | 244 | let light = Arc::new(DiffuseLight::new(Arc::new(SolidColor::new( 245 | Vec3::new(1.0, 0.77, 0.56)*2.0, 246 | )))); 247 | let light_shape = Arc::new(Rect::XZRect( 248 | -11.0, 11.0, -11.0, 11.0, 8.0, light, 249 | )); 250 | world.push(Arc::new(FlipFace::new(light_shape.clone()))); 251 | lights.push(light_shape); 252 | 253 | // Camera 254 | let radius = 20.0; 255 | let height = 2.5; 256 | let lookat = Vec3::new(0.0, 1.5, 0.0); 257 | let vup = Vec3::new(0.0, 1.0, 0.0); 258 | let dist_to_focus = 10.0; 259 | let aperture = 0.0; 260 | let vfov = 20.0; 261 | let time0 = 0.0; 262 | let time1 = 1.0; 263 | let limit = 360.0; 264 | let incr = 0.5; 265 | let angle = 25.0; 266 | let aspect_ratio = 16.0/9.0; 267 | let cam_iter = Box::new(RotatingCamera::new( 268 | lookat, 269 | vup, 270 | vfov, 271 | aspect_ratio, 272 | aperture, 273 | dist_to_focus, 274 | time0, 275 | time1, 276 | height, 277 | angle, 278 | radius, 279 | incr, 280 | limit, 281 | )); 282 | 283 | SceneConfig::new(world, lights, cam_iter, aspect_ratio) 284 | } 285 | 286 | pub fn perlin_demo() -> SceneConfig { 287 | let mut world: Vec> = vec![]; 288 | let mut lights: Vec> = vec![]; 289 | // Ground 290 | let pertext = Arc::new(Lambertian::new(Arc::new(NoiseTexture::new(2.0)))); 291 | world.push(Arc::new(Sphere::new( 292 | Vec3::new(0.0, -1000.0, 0.0), 293 | 1000.0, 294 | pertext.clone(), 295 | ))); 296 | 297 | world.push(Arc::new(Sphere::new( 298 | Vec3::new(0.0, 2.0, 0.0), 299 | 2.0, 300 | pertext, 301 | ))); 302 | 303 | // Light 304 | let light_shape = Arc::new(Rect::XZRect( 305 | -6.0, 306 | 6.0, 307 | -6.0, 308 | 6.0, 309 | 8.0, 310 | Arc::new(DiffuseLight::new(Arc::new(SolidColor::new( 311 | Vec3::new_const(4.0), 312 | )))))); 313 | world.push(Arc::new(FlipFace::new(light_shape.clone()))); 314 | lights.push(light_shape); 315 | 316 | let lookfrom = Vec3::new(0.0, 2.0, 10.0); 317 | let lookat = Vec3::new(0.0,1.0,0.0); 318 | let vup = Vec3::new(0.0,1.0,0.0); 319 | let dist_to_focus = 10.0; 320 | let aperture = 0.0; 321 | let vfov = 40.0; 322 | let aspect_ratio = 16.0/9.0; 323 | let time0 = 0.0; 324 | let time1 = 1.0; 325 | 326 | let cam = Camera::new( 327 | lookfrom, 328 | lookat, 329 | vup, 330 | vfov, 331 | aspect_ratio, 332 | aperture, 333 | dist_to_focus, 334 | time0, 335 | time1, 336 | ); 337 | SceneConfig::new(world, lights, Box::new(FixedCamera::new(cam)), aspect_ratio) 338 | } 339 | 340 | struct Bowser { 341 | parts: Arc, 342 | } 343 | 344 | impl Bowser { 345 | pub fn new(x: f32, y: f32, z: f32) -> Bowser { 346 | let mut world: Vec> = vec![]; 347 | // Face 348 | world.push(Arc::new(Rect::XYRect( 349 | x - 2.0, 350 | x + 2.0, 351 | y - 1.875 + 1.0, 352 | y - 1.875 + 4.0, 353 | z + 4.5 - 3.0, 354 | Arc::new(Lambertian::new(Arc::new(ImageTexture::new( 355 | "assets/bowser_face.png", 356 | )))), 357 | ))); 358 | // Top 359 | world.push(Arc::new(Rect::XZRect( 360 | x - 2.0, 361 | x + 2.0, 362 | z + 4.5 - 6.0, 363 | z + 4.5 - 3.0, 364 | y - 1.875 + 4.0, 365 | Arc::new(Lambertian::new(Arc::new(ImageTexture::new( 366 | "assets/bowser_top.png", 367 | )))), 368 | ))); 369 | // Back 370 | world.push(Arc::new(Rect::XYRect( 371 | x - 2.0, 372 | x + 2.0, 373 | y - 1.875 + 1.0, 374 | y - 1.875 + 4.0, 375 | z + 4.5 - 6.0, 376 | Arc::new(Lambertian::new(Arc::new(ImageTexture::new( 377 | "assets/bowser_back.png", 378 | )))), 379 | ))); 380 | // Sides 381 | world.push(Arc::new(Rect::YZRect( 382 | y - 1.875 + 1.0, 383 | y - 1.875 + 4.0, 384 | z + 4.5 - 6.0, 385 | z + 4.5 - 3.0, 386 | x - 2.0, 387 | Arc::new(Lambertian::new(Arc::new(ImageTexture::new( 388 | "assets/bowser_side.png", 389 | )))), 390 | ))); 391 | world.push(Arc::new(Rect::YZRect( 392 | y - 1.875 + 1.0, 393 | y - 1.875 + 4.0, 394 | z + 4.5 - 6.0, 395 | z + 4.5 - 3.0, 396 | x + 2.0, 397 | Arc::new(Lambertian::new(Arc::new(ImageTexture::new( 398 | "assets/bowser_side.png", 399 | )))), 400 | ))); 401 | // Bottom 402 | let grey = Arc::new(Lambertian::new(Arc::new(SolidColor::new(Vec3::new( 403 | 0.278, 0.387, 0.438, 404 | ))))); 405 | world.push(Arc::new(Rect::XZRect( 406 | x - 2.0, 407 | x + 2.0, 408 | z + 4.5 - 6.0, 409 | z + 4.5 - 3.0, 410 | y - 1.875 + 1.0, 411 | grey.clone(), 412 | ))); 413 | // Feet 414 | world.push(Arc::new(Boxy::new( 415 | Vec3::new(x - 1.5, y - 1.875 + 0.5, z + 4.5 - 4.75), 416 | Vec3::new(x - 0.5, y - 1.875 + 1.0, z + 4.5 - 4.25), 417 | grey.clone(), 418 | ))); 419 | world.push(Arc::new(Boxy::new( 420 | Vec3::new(x + 0.5, y - 1.875 + 0.5, z + 4.5 - 4.75), 421 | Vec3::new(x + 1.5, y - 1.875 + 1.0, z + 4.5 - 4.25), 422 | grey.clone(), 423 | ))); 424 | world.push(Arc::new(Boxy::new( 425 | Vec3::new(x - 1.5, y - 1.875 + 0.25, z + 4.5 - 4.75), 426 | Vec3::new(x - 0.5, y - 1.875 + 0.5, z + 4.5 - 3.5), 427 | grey.clone(), 428 | ))); 429 | world.push(Arc::new(Boxy::new( 430 | Vec3::new(x + 0.5, y - 1.875 + 0.25, z + 4.5 - 4.75), 431 | Vec3::new(x + 1.5, y - 1.875 + 0.5, z + 4.5 - 3.5), 432 | grey, 433 | ))); 434 | // Arms 435 | let brown = Arc::new(Lambertian::new(Arc::new(SolidColor::new(Vec3::new( 436 | 0.4, 0.2, 0.1, 437 | ))))); 438 | world.push(Arc::new(Boxy::new( 439 | Vec3::new(x - 2.25, y - 1.875 + 1.75, z + 4.5 - 4.65), 440 | Vec3::new(x - 2.00, y - 1.875 + 2.75, z + 4.5 - 4.35), 441 | brown.clone(), 442 | ))); 443 | world.push(Arc::new(Boxy::new( 444 | Vec3::new(x - 2.50, y - 1.875 + 1.75, z + 4.5 - 4.65), 445 | Vec3::new(x - 2.25, y - 1.875 + 2.50, z + 4.5 - 4.35), 446 | brown.clone(), 447 | ))); 448 | world.push(Arc::new(Boxy::new( 449 | Vec3::new(x - 2.75, y - 1.875 + 1.75, z + 4.5 - 4.65), 450 | Vec3::new(x - 2.50, y - 1.875 + 2.25, z + 4.5 - 4.35), 451 | brown.clone(), 452 | ))); 453 | world.push(Arc::new(Boxy::new( 454 | Vec3::new(x + 2.00, y - 1.875 + 1.75, z + 4.5 - 4.65), 455 | Vec3::new(x + 2.25, y - 1.875 + 2.75, z + 4.5 - 4.35), 456 | brown.clone(), 457 | ))); 458 | world.push(Arc::new(Boxy::new( 459 | Vec3::new(x + 2.25, y - 1.875 + 1.75, z + 4.5 - 4.65), 460 | Vec3::new(x + 2.50, y - 1.875 + 2.50, z + 4.5 - 4.35), 461 | brown.clone(), 462 | ))); 463 | world.push(Arc::new(Boxy::new( 464 | Vec3::new(x + 2.50, y - 1.875 + 1.75, z + 4.5 - 4.65), 465 | Vec3::new(x + 2.75, y - 1.875 + 2.25, z + 4.5 - 4.35), 466 | brown, 467 | ))); 468 | // Face rim 469 | let lightgrey = Arc::new(Lambertian::new(Arc::new(SolidColor::new(Vec3::new( 470 | 0.601, 0.687, 0.723, 471 | ))))); 472 | world.push(Arc::new(Boxy::new( 473 | Vec3::new(x - 2.0, y - 1.875 + 3.875, z + 4.5 - 3.00), 474 | Vec3::new(x + 2.0, y - 1.875 + 4.00, z + 4.5 - 2.875), 475 | lightgrey.clone(), 476 | ))); 477 | world.push(Arc::new(Boxy::new( 478 | Vec3::new(x - 2.0, y - 1.875 + 1.0, z + 4.5 - 3.00), 479 | Vec3::new(x + 2.0, y - 1.875 + 1.125, z + 4.5 - 2.875), 480 | lightgrey.clone(), 481 | ))); 482 | world.push(Arc::new(Boxy::new( 483 | Vec3::new(x - 2.0, y - 1.875 + 1.125, z + 4.5 - 3.00), 484 | Vec3::new(x - 1.875, y - 1.875 + 3.875, z + 4.5 - 2.875), 485 | lightgrey.clone(), 486 | ))); 487 | world.push(Arc::new(Boxy::new( 488 | Vec3::new(x + 1.875, y - 1.875 + 1.125, z + 4.5 - 3.00), 489 | Vec3::new(x + 2.0, y - 1.875 + 3.875, z + 4.5 - 2.875), 490 | lightgrey.clone(), 491 | ))); 492 | 493 | // Butt ports 494 | world.push(Arc::new(Boxy::new( 495 | Vec3::new(x - 1.875, y - 1.875 + 1.625, z + 4.5 - 6.125), 496 | Vec3::new(x - 0.875, y - 1.875 + 1.75, z + 4.5 - 6.0), 497 | lightgrey.clone(), 498 | ))); 499 | world.push(Arc::new(Boxy::new( 500 | Vec3::new(x - 1.875, y - 1.875 + 1.125, z + 4.5 - 6.125), 501 | Vec3::new(x - 0.875, y - 1.875 + 1.25, z + 4.5 - 6.0), 502 | lightgrey.clone(), 503 | ))); 504 | world.push(Arc::new(Boxy::new( 505 | Vec3::new(x - 1.875, y - 1.875 + 1.25, z + 4.5 - 6.125), 506 | Vec3::new(x - 1.750, y - 1.875 + 1.625, z + 4.5 - 6.0), 507 | lightgrey.clone(), 508 | ))); 509 | world.push(Arc::new(Boxy::new( 510 | Vec3::new(x - 1.0, y - 1.875 + 1.25, z + 4.5 - 6.125), 511 | Vec3::new(x - 0.875, y - 1.875 + 1.625, z + 4.5 - 6.0), 512 | lightgrey.clone(), 513 | ))); 514 | 515 | world.push(Arc::new(Boxy::new( 516 | Vec3::new(x + 0.875, y - 1.875 + 1.625, z + 4.5 - 6.125), 517 | Vec3::new(x + 1.875, y - 1.875 + 1.75, z + 4.5 - 6.0), 518 | lightgrey.clone(), 519 | ))); 520 | world.push(Arc::new(Boxy::new( 521 | Vec3::new(x + 0.875, y - 1.875 + 1.125, z + 4.5 - 6.125), 522 | Vec3::new(x + 1.875, y - 1.875 + 1.25, z + 4.5 - 6.0), 523 | lightgrey.clone(), 524 | ))); 525 | world.push(Arc::new(Boxy::new( 526 | Vec3::new(x + 1.750, y - 1.875 + 1.25, z + 4.5 - 6.125), 527 | Vec3::new(x + 1.875, y - 1.875 + 1.625, z + 4.5 - 6.0), 528 | lightgrey.clone(), 529 | ))); 530 | world.push(Arc::new(Boxy::new( 531 | Vec3::new(x + 0.875, y - 1.875 + 1.25, z + 4.5 - 6.125), 532 | Vec3::new(x + 1.0, y - 1.875 + 1.625, z + 4.5 - 6.0), 533 | lightgrey, 534 | ))); 535 | 536 | Bowser { 537 | parts: Arc::new(BVHNode::new(&mut world[..])), 538 | } 539 | } 540 | } 541 | 542 | impl Hittable for Bowser { 543 | fn hit(&self, r: &Ray, tmin: f32, tmax: f32) -> Option { 544 | self.parts.hit(r, tmin, tmax) 545 | } 546 | fn bounding_box(&self, t0: f32, t1: f32) -> Option { 547 | self.parts.bounding_box(t0, t1) 548 | } 549 | } 550 | 551 | pub fn bowser_demo() -> SceneConfig { 552 | let mut world: Vec> = vec![]; 553 | let mut lights: Vec> = vec![]; 554 | // Ground 555 | let checker = Checker::new( 556 | Arc::new(SolidColor::new(Vec3::new(0.1, 0.1, 0.1))), 557 | Arc::new(SolidColor::new(Vec3::new(0.9, 0.9, 0.9))), 558 | ); 559 | let ground_material = Arc::new(Lambertian::new(Arc::new(checker))); 560 | world.push(Arc::new(Sphere::new( 561 | Vec3::new(0.0, -1000.0, 0.0), 562 | 1000.0, 563 | ground_material, 564 | ))); 565 | 566 | // Bowser! Goofed a little when creating by putting him .25 units 567 | // off the ground, so fix that with a Translate here. Rotate him too. 568 | world.push(Arc::new(Translate::new( 569 | Arc::new(RotateX::new( 570 | Arc::new(RotateY::new( 571 | Arc::new(RotateZ::new(Arc::new(Bowser::new(0.0, 0.0, 0.0)), 0.0)), 572 | 0.0, 573 | )), 574 | 0.0, 575 | )), 576 | Vec3::new(0.0, 1.625, -4.5), 577 | ))); 578 | 579 | // Light 580 | let light_shape = Arc::new(Rect::XYRect( 581 | -2.0, 582 | 2.0, 583 | 1.0, 584 | 4.0, 585 | 3.0, 586 | Arc::new(DiffuseLight::new(Arc::new( 587 | ImageTexture::new( 588 | "assets/twitter.png", 589 | ), 590 | // SolidColor::new(Vec3::new_const(4.0)), 591 | ))), 592 | )); 593 | let light = Arc::new(FlipFace::new(light_shape.clone())); 594 | world.push(light); 595 | lights.push(light_shape); 596 | 597 | // Camera 598 | let radius = 20.0; 599 | let height = 2.5; 600 | let lookat = Vec3::new(0.0, 2.0, 0.0); 601 | let vup = Vec3::new(0.0, 1.0, 0.0); 602 | let dist_to_focus = 10.0; 603 | let aperture = 0.0; 604 | let vfov = 20.0; 605 | let time0 = 0.0; 606 | let time1 = 1.0; 607 | let limit = 360.0-35.0; 608 | let incr = 0.5; 609 | let angle = -35.0; 610 | let aspect_ratio = 16.0/9.0; 611 | let cam_iter = Box::new(RotatingCamera::new( 612 | lookat, 613 | vup, 614 | vfov, 615 | aspect_ratio, 616 | aperture, 617 | dist_to_focus, 618 | time0, 619 | time1, 620 | height, 621 | angle, 622 | radius, 623 | incr, 624 | limit, 625 | )); 626 | 627 | SceneConfig::new(world, lights, cam_iter, aspect_ratio) 628 | } 629 | 630 | pub fn cornell_box() -> SceneConfig { 631 | let mut world: Vec> = vec![]; 632 | let mut lights: Vec> = vec![]; 633 | let red = Arc::new(Lambertian::new(Arc::new(SolidColor::new(Vec3::new( 634 | 0.65, 0.05, 0.05, 635 | ))))); 636 | let white = Arc::new(Lambertian::new(Arc::new(SolidColor::new(Vec3::new( 637 | 0.73, 0.73, 0.73, 638 | ))))); 639 | let green = Arc::new(Lambertian::new(Arc::new(SolidColor::new(Vec3::new( 640 | 0.12, 0.45, 0.15, 641 | ))))); 642 | let light = Arc::new(DiffuseLight::new(Arc::new(SolidColor::new(Vec3::new( 643 | 15.0, 15.0, 15.0, 644 | ))))); 645 | world.push(Arc::new(FlipFace::new(Arc::new(Rect::YZRect( 646 | 0.0, 555.0, 0.0, 555.0, 555.0, green, 647 | ))))); 648 | world.push(Arc::new(Rect::YZRect(0.0, 555.0, 0.0, 555.0, 0.0, red))); 649 | world.push(Arc::new(FlipFace::new(Arc::new(Rect::XZRect( 650 | 0.0, 651 | 555.0, 652 | 0.0, 653 | 555.0, 654 | 0.0, 655 | white.clone(), 656 | ))))); 657 | world.push(Arc::new(Rect::XZRect( 658 | 0.0, 659 | 555.0, 660 | 0.0, 661 | 555.0, 662 | 555.0, 663 | white.clone(), 664 | ))); 665 | world.push(Arc::new(FlipFace::new(Arc::new(Rect::XYRect( 666 | 0.0, 667 | 555.0, 668 | 0.0, 669 | 555.0, 670 | 555.0, 671 | white.clone(), 672 | ))))); 673 | 674 | let box1 = Arc::new(Boxy::new( 675 | Vec3::new_const(0.0), 676 | Vec3::new(165.0, 330.0, 165.0), 677 | white, 678 | )); 679 | world.push(Arc::new(Translate::new( 680 | Arc::new(RotateY::new(box1, 15.0)), 681 | Vec3::new(265.0, 0.0, 295.0), 682 | ))); 683 | 684 | world.push(Arc::new(Sphere::new( 685 | Vec3::new(190.0, 90.0, 190.0), 686 | 90.0, 687 | Arc::new(Dielectric::new(1.5)), 688 | ))); 689 | 690 | let light_shape = Arc::new(Rect::XZRect( 691 | 213.0, 343.0, 227.0, 332.0, 554.0, light 692 | )); 693 | world.push(Arc::new(FlipFace::new(light_shape.clone()))); 694 | lights.push(light_shape); 695 | 696 | /* 697 | let box2 = Arc::new(Boxy::new( 698 | Vec3::new_const(0.0), 699 | Vec3::new(165.0, 165.0, 165.0), 700 | white, 701 | )); 702 | world.push(Arc::new(Translate::new( 703 | Arc::new(RotateY::new(box2, -18.0)), 704 | Vec3::new(130.0, 0.0, 65.0), 705 | ))); 706 | */ 707 | 708 | let lookfrom = Vec3::new(278.0, 278.0, -800.0); 709 | let lookat = Vec3::new(278.0,278.0,0.0); 710 | let vup = Vec3::new(0.0,1.0,0.0); 711 | let dist_to_focus = 10.0; 712 | let aperture = 0.0; 713 | let vfov = 40.0; 714 | let aspect_ratio = 1.0; 715 | let time0 = 0.0; 716 | let time1 = 1.0; 717 | 718 | let cam = Camera::new( 719 | lookfrom, 720 | lookat, 721 | vup, 722 | vfov, 723 | aspect_ratio, 724 | aperture, 725 | dist_to_focus, 726 | time0, 727 | time1, 728 | ); 729 | SceneConfig::new(world, lights, Box::new(FixedCamera::new(cam)), aspect_ratio) 730 | } 731 | 732 | pub fn final_scene() -> SceneConfig { 733 | let mut objects: Vec> = vec![]; 734 | let mut lights: Vec> = vec![]; 735 | let mut rng = rand::thread_rng(); 736 | let mut boxes1: Vec> = vec![]; 737 | let ground = Arc::new(Lambertian::new(Arc::new(SolidColor::new(Vec3::new( 738 | 0.48, 0.83, 0.53, 739 | ))))); 740 | 741 | const BOXES_PER_SIDE: usize = 20; 742 | 743 | for i in 0..BOXES_PER_SIDE { 744 | for j in 0..BOXES_PER_SIDE { 745 | let w = 100.0; 746 | let x0 = -1000.0 + (i as f32) * w; 747 | let z0 = -1000.0 + (j as f32) * w; 748 | let y0 = 0.0; 749 | let x1 = x0 + w; 750 | let z1 = z0 + w; 751 | let y1 = rng.gen_range(1.0, 101.0); 752 | boxes1.push(Arc::new(Boxy::new( 753 | Vec3::new(x0, y0, z0), 754 | Vec3::new(x1, y1, z1), 755 | ground.clone(), 756 | ))); 757 | } 758 | } 759 | 760 | objects.push(Arc::new(BVHNode::new(&mut boxes1))); 761 | 762 | let light = Arc::new(DiffuseLight::new(Arc::new(SolidColor::new( 763 | Vec3::new_const(7.0), 764 | )))); 765 | let light_shape = Arc::new(Rect::XZRect( 766 | 123.0, 423.0, 147.0, 412.0, 554.0, light, 767 | )); 768 | objects.push(Arc::new(FlipFace::new(light_shape.clone()))); 769 | lights.push(light_shape); 770 | 771 | let center1 = Vec3::new(400.0, 400.0, 200.0); 772 | let center2 = center1 + Vec3::new(30.0, 0.0, 0.0); 773 | let moving_sphere_material = Arc::new(Lambertian::new(Arc::new(SolidColor::new(Vec3::new( 774 | 0.7, 0.3, 0.1, 775 | ))))); 776 | objects.push(Arc::new(MovingSphere::new( 777 | center1, 778 | center2, 779 | 0.0, 780 | 1.0, 781 | 50.0, 782 | moving_sphere_material, 783 | ))); 784 | 785 | objects.push(Arc::new(Sphere::new( 786 | Vec3::new(260.0, 150.0, 45.0), 787 | 50.0, 788 | Arc::new(Dielectric::new(1.5)), 789 | ))); 790 | objects.push(Arc::new(Sphere::new( 791 | Vec3::new(0.0, 150.0, 145.0), 792 | 50.0, 793 | Arc::new(Metal::new( 794 | Arc::new(SolidColor::new(Vec3::new(0.8, 0.8, 0.9))), 795 | 10.0, 796 | )), 797 | ))); 798 | 799 | let boundary1 = Arc::new(Sphere::new( 800 | Vec3::new(360.0, 150.0, 145.0), 801 | 70.0, 802 | Arc::new(Dielectric::new(1.5)), 803 | )); 804 | objects.push(boundary1.clone()); 805 | objects.push(Arc::new(ConstantMedium::new( 806 | boundary1, 807 | 0.2, 808 | Arc::new(SolidColor::new(Vec3::new(0.2, 0.4, 0.9))), 809 | ))); 810 | let boundary2 = Arc::new(Sphere::new( 811 | Vec3::new_const(0.0), 812 | 5000.0, 813 | Arc::new(Dielectric::new(1.5)), 814 | )); 815 | objects.push(Arc::new(ConstantMedium::new( 816 | boundary2, 817 | 0.0001, 818 | Arc::new(SolidColor::new(Vec3::new_const(1.0))), 819 | ))); 820 | 821 | let emat = Arc::new(Lambertian::new(Arc::new(ImageTexture::new( 822 | "assets/earthmap.png", 823 | )))); 824 | objects.push(Arc::new(Sphere::new( 825 | Vec3::new(400.0, 200.0, 400.0), 826 | 100.0, 827 | emat, 828 | ))); 829 | let pertext = Arc::new(NoiseTexture::new(0.1)); 830 | objects.push(Arc::new(Sphere::new( 831 | Vec3::new(220.0, 280.0, 300.0), 832 | 80.0, 833 | Arc::new(Lambertian::new(pertext)), 834 | ))); 835 | 836 | let mut boxes2: Vec> = vec![]; 837 | let white = Arc::new(Lambertian::new(Arc::new(SolidColor::new(Vec3::new_const( 838 | 0.73, 839 | ))))); 840 | for _ in 0..1000 { 841 | boxes2.push(Arc::new(Sphere::new( 842 | Vec3::random_range(0.0, 165.0), 843 | 10.0, 844 | white.clone(), 845 | ))); 846 | } 847 | 848 | objects.push(Arc::new(Translate::new( 849 | Arc::new(RotateY::new(Arc::new(BVHNode::new(&mut boxes2)), 15.0)), 850 | Vec3::new(-100.0, 270.0, 395.0), 851 | ))); 852 | 853 | let lookfrom = Vec3::new(478.0, 278.0, -600.0); 854 | let lookat = Vec3::new(278.0, 278.0, 0.0); 855 | let vup = Vec3::new(0.0, 1.0, 0.0); 856 | let vfov = 40.0; 857 | let dist_to_focus = 10.0; 858 | let aperture = 0.0; 859 | let aspect_ratio = 1.0; 860 | let time0 = 0.0; 861 | let time1 = 1.0; 862 | let cam = Camera::new( 863 | lookfrom, 864 | lookat, 865 | vup, 866 | vfov, 867 | aspect_ratio, 868 | aperture, 869 | dist_to_focus, 870 | time0, 871 | time1, 872 | ); 873 | SceneConfig::new(objects, lights, Box::new(FixedCamera::new(cam)), aspect_ratio) 874 | } 875 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::hittable::HittableSS; 2 | use crate::rand::Rng; 3 | use crate::vec3::Vec3; 4 | use std::sync::Arc; 5 | 6 | pub fn fmin(f1: f32, f2: f32) -> f32 { 7 | f1.min(f2) 8 | } 9 | 10 | pub fn fmax(f1: f32, f2: f32) -> f32 { 11 | f1.max(f2) 12 | } 13 | 14 | pub fn reflect(v: Vec3, n: Vec3) -> Vec3 { 15 | v - n * v.dot(n) * 2.0 16 | } 17 | 18 | pub fn refract(uv: Vec3, n: Vec3, etai_over_etat: f32) -> Vec3 { 19 | let cos_theta = -uv.dot(n); 20 | let r_out_parallel = (uv + n * cos_theta) * etai_over_etat; 21 | let r_out_perp = n * -(1.0 - r_out_parallel.length2()).sqrt(); 22 | r_out_parallel + r_out_perp 23 | } 24 | 25 | pub fn schlick(cosine: f32, ref_idx: f32) -> f32 { 26 | let mut r0 = (1.0 - ref_idx) / (1.0 + ref_idx); 27 | r0 = r0 * r0; 28 | r0 + (1.0 - r0) * (1.0 - cosine).powf(5.0) 29 | } 30 | 31 | pub fn random_in_unit_sphere() -> Vec3 { 32 | loop { 33 | let p = Vec3::random_range(-1.0, 1.0); 34 | if p.length2() >= 1.0 { 35 | continue; 36 | } 37 | return p; 38 | } 39 | } 40 | 41 | pub fn random_in_unit_disk() -> Vec3 { 42 | let mut rng = rand::thread_rng(); 43 | loop { 44 | let p = Vec3::new(rng.gen_range(-1.0, 1.0), rng.gen_range(-1.0, 1.0), 0.0); 45 | if p.length2() >= 1.0 { 46 | continue; 47 | } 48 | return p; 49 | } 50 | } 51 | 52 | fn random_cosine_direction() -> Vec3 { 53 | let mut rng = rand::thread_rng(); 54 | let r1 = rng.gen::(); 55 | let r2 = rng.gen::(); 56 | let z = (1.0-r2).sqrt(); 57 | 58 | let phi = 2.0*r1*std::f32::consts::PI; 59 | let x = phi.cos()*r2.sqrt(); 60 | let y = phi.sin()*r2.sqrt(); 61 | 62 | Vec3::new(x, y, z) 63 | } 64 | 65 | pub struct ONB { 66 | pub u: Vec3, 67 | pub v: Vec3, 68 | pub w: Vec3, 69 | } 70 | 71 | impl std::ops::Index for ONB { 72 | type Output = Vec3; 73 | fn index(&self, i: usize) -> &Self::Output { 74 | match i { 75 | 0 => &self.u, 76 | 1 => &self.v, 77 | 2 => &self.w, 78 | _ => unreachable!(), 79 | } 80 | } 81 | } 82 | 83 | impl std::ops::IndexMut for ONB { 84 | fn index_mut(&mut self, i: usize) -> &mut Self::Output { 85 | match i { 86 | 0 => &mut self.u, 87 | 1 => &mut self.v, 88 | 2 => &mut self.w, 89 | _ => unreachable!(), 90 | } 91 | } 92 | } 93 | 94 | impl ONB { 95 | pub fn local(&self, a: Vec3) -> Vec3 { 96 | self.u*a.x + self.v*a.y + self.w*a.z 97 | } 98 | 99 | pub fn new_from_w(n: Vec3) -> ONB { 100 | let w = n.unit_vector(); 101 | let a = if w.x.abs() > 0.9 { 102 | Vec3::new(0.0, 1.0, 0.0) 103 | } 104 | else { 105 | Vec3::new(1.0, 0.0, 0.0) 106 | }; 107 | let v = w.cross(a).unit_vector(); 108 | let u = w.cross(v); 109 | ONB { u, v, w } 110 | } 111 | } 112 | 113 | pub trait PDF { 114 | fn value(&self, direction: Vec3) -> f32; 115 | 116 | fn generate(&self) -> Vec3; 117 | } 118 | 119 | pub type PDFSS = dyn PDF + Send + Sync; 120 | 121 | pub struct CosinePDF { 122 | uvw: ONB, 123 | } 124 | 125 | impl CosinePDF { 126 | pub fn new(w: Vec3) -> CosinePDF { 127 | CosinePDF { 128 | uvw: ONB::new_from_w(w) 129 | } 130 | } 131 | } 132 | 133 | impl PDF for CosinePDF { 134 | fn value(&self, direction: Vec3) -> f32 { 135 | let cos = direction.unit_vector().dot(self.uvw.w); 136 | if cos <= 0.0 { 137 | 0.0 138 | } 139 | else { 140 | cos / std::f32::consts::PI 141 | } 142 | } 143 | 144 | fn generate(&self) -> Vec3 { 145 | self.uvw.local(random_cosine_direction()) 146 | } 147 | } 148 | 149 | #[derive(new)] 150 | pub struct HittablePDF { 151 | ptr: Arc, 152 | o: Vec3, 153 | } 154 | 155 | impl PDF for HittablePDF { 156 | fn value(&self, direction: Vec3) -> f32 { 157 | self.ptr.pdf_value(self.o, direction) 158 | } 159 | fn generate(&self) -> Vec3 { 160 | self.ptr.random(self.o) 161 | } 162 | } 163 | 164 | #[derive(new)] 165 | pub struct MixturePDF { 166 | ptr1: Arc, 167 | f1: f32, 168 | ptr2: Arc, 169 | f2: f32, 170 | } 171 | 172 | impl PDF for MixturePDF { 173 | fn value(&self, direction: Vec3) -> f32 { 174 | self.f1 * self.ptr1.value(direction) + self.f2 * self.ptr2.value(direction) 175 | } 176 | 177 | fn generate(&self) -> Vec3 { 178 | let mut rng = rand::thread_rng(); 179 | if rng.gen::() < self.f1 { 180 | self.ptr1.generate() 181 | } 182 | else { 183 | self.ptr2.generate() 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/vec3.rs: -------------------------------------------------------------------------------- 1 | use crate::rand::Rng; 2 | 3 | #[derive(Debug, Copy, Clone, new)] 4 | pub struct Vec3 { 5 | pub x: f32, 6 | pub y: f32, 7 | pub z: f32, 8 | } 9 | 10 | impl Vec3 { 11 | pub fn new_const(x: f32) -> Vec3 { 12 | Vec3 { x, y: x, z: x } 13 | } 14 | 15 | pub fn zero() -> Vec3 { 16 | Vec3::new_const(0.0) 17 | } 18 | 19 | pub fn dot(&self, v: Vec3) -> f32 { 20 | self.x * v.x + self.y * v.y + self.z * v.z 21 | } 22 | 23 | pub fn cross(&self, v: Vec3) -> Vec3 { 24 | Vec3::new( 25 | self.y * v.z - self.z * v.y, 26 | self.z * v.x - self.x * v.z, 27 | self.x * v.y - self.y * v.x, 28 | ) 29 | } 30 | 31 | pub fn length2(&self) -> f32 { 32 | self.x * self.x + self.y * self.y + self.z * self.z 33 | } 34 | 35 | pub fn length(&self) -> f32 { 36 | self.length2().sqrt() 37 | } 38 | 39 | pub fn unit_vector(&self) -> Vec3 { 40 | let norm = self.length2().sqrt(); 41 | Vec3::new(self.x / norm, self.y / norm, self.z / norm) 42 | } 43 | 44 | pub fn clamp(x: f32, min: f32, max: f32) -> f32 { 45 | if x < min { 46 | min 47 | } else if x > max { 48 | max 49 | } else { 50 | x 51 | } 52 | } 53 | 54 | pub fn to_color(&self) -> (u32, u32, u32) { 55 | // Note: sqrt() here does gamma correction 56 | ( 57 | (256.0 * Vec3::clamp(self.x.sqrt(), 0.0, 0.999)) as u32, 58 | (256.0 * Vec3::clamp(self.y.sqrt(), 0.0, 0.999)) as u32, 59 | (256.0 * Vec3::clamp(self.z.sqrt(), 0.0, 0.999)) as u32, 60 | ) 61 | } 62 | 63 | #[allow(dead_code)] 64 | pub fn powf(&self, exp: f32) -> Vec3 { 65 | Vec3::new(self.x.powf(exp), self.y.powf(exp), self.z.powf(exp)) 66 | } 67 | 68 | pub fn random() -> Vec3 { 69 | let mut rng = rand::thread_rng(); 70 | 71 | Vec3::new(rng.gen::(), rng.gen::(), rng.gen::()) 72 | } 73 | 74 | pub fn random_range(min: f32, max: f32) -> Vec3 { 75 | let mut rng = rand::thread_rng(); 76 | 77 | Vec3::new( 78 | rng.gen_range(min, max), 79 | rng.gen_range(min, max), 80 | rng.gen_range(min, max), 81 | ) 82 | } 83 | } 84 | 85 | impl std::ops::Add for Vec3 { 86 | type Output = Vec3; 87 | fn add(self, rhs: Vec3) -> Self::Output { 88 | Vec3 { 89 | x: self.x + rhs.x, 90 | y: self.y + rhs.y, 91 | z: self.z + rhs.z, 92 | } 93 | } 94 | } 95 | 96 | impl std::ops::Mul for Vec3 { 97 | type Output = Vec3; 98 | fn mul(self, rhs: Vec3) -> Self::Output { 99 | Vec3 { 100 | x: self.x * rhs.x, 101 | y: self.y * rhs.y, 102 | z: self.z * rhs.z, 103 | } 104 | } 105 | } 106 | 107 | impl std::ops::Mul for Vec3 { 108 | type Output = Vec3; 109 | fn mul(self, rhs: f32) -> Self::Output { 110 | Vec3 { 111 | x: self.x * rhs, 112 | y: self.y * rhs, 113 | z: self.z * rhs, 114 | } 115 | } 116 | } 117 | 118 | impl std::ops::Div for Vec3 { 119 | type Output = Vec3; 120 | fn div(self, rhs: f32) -> Self::Output { 121 | Vec3 { 122 | x: self.x / rhs, 123 | y: self.y / rhs, 124 | z: self.z / rhs, 125 | } 126 | } 127 | } 128 | 129 | impl std::ops::Sub for Vec3 { 130 | type Output = Vec3; 131 | fn sub(self, rhs: Vec3) -> Self::Output { 132 | Vec3 { 133 | x: self.x - rhs.x, 134 | y: self.y - rhs.y, 135 | z: self.z - rhs.z, 136 | } 137 | } 138 | } 139 | 140 | impl std::ops::Neg for Vec3 { 141 | type Output = Vec3; 142 | fn neg(self) -> Self::Output { 143 | Vec3 { 144 | x: -self.x, 145 | y: -self.y, 146 | z: -self.z, 147 | } 148 | } 149 | } 150 | 151 | impl std::ops::AddAssign for Vec3 { 152 | fn add_assign(&mut self, t: Vec3) { 153 | *self = Self { 154 | x: self.x + t.x, 155 | y: self.y + t.y, 156 | z: self.z + t.z, 157 | }; 158 | } 159 | } 160 | 161 | impl std::ops::MulAssign for Vec3 { 162 | fn mul_assign(&mut self, t: f32) { 163 | *self = Self { 164 | x: self.x * t, 165 | y: self.y * t, 166 | z: self.z * t, 167 | }; 168 | } 169 | } 170 | 171 | impl std::ops::DivAssign for Vec3 { 172 | fn div_assign(&mut self, t: f32) { 173 | *self = Self { 174 | x: self.x / t, 175 | y: self.y / t, 176 | z: self.z / t, 177 | }; 178 | } 179 | } 180 | 181 | impl std::ops::Index for Vec3 { 182 | type Output = f32; 183 | fn index(&self, i: usize) -> &Self::Output { 184 | match i { 185 | 0 => &self.x, 186 | 1 => &self.y, 187 | 2 => &self.z, 188 | _ => unreachable!(), 189 | } 190 | } 191 | } 192 | 193 | impl std::ops::IndexMut for Vec3 { 194 | fn index_mut(&mut self, i: usize) -> &mut Self::Output { 195 | match i { 196 | 0 => &mut self.x, 197 | 1 => &mut self.y, 198 | 2 => &mut self.z, 199 | _ => unreachable!(), 200 | } 201 | } 202 | } 203 | 204 | impl std::cmp::PartialEq for Vec3 { 205 | fn eq(&self, other: &Self) -> bool { 206 | self.x == other.x && self.y == other.y && self.z == other.z 207 | } 208 | } 209 | --------------------------------------------------------------------------------