├── .gitattributes ├── .gitignore ├── .gitmodules ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── docs └── sample_render.png ├── sample-config.json ├── src ├── .gitignore ├── geometry │ ├── bbox.rs │ ├── mesh.rs │ ├── mod.rs │ ├── prim.rs │ └── prims │ │ ├── plane.rs │ │ ├── sphere.rs │ │ └── triangle.rs ├── light │ ├── light.rs │ ├── lights │ │ ├── pointlight.rs │ │ └── spherelight.rs │ └── mod.rs ├── main.rs ├── mat4.rs ├── material │ ├── material.rs │ ├── materials │ │ ├── cooktorrancematerial.rs │ │ ├── flatmaterial.rs │ │ └── phongmaterial.rs │ ├── mod.rs │ ├── texture.rs │ └── textures │ │ ├── checkertexture.rs │ │ ├── cubemap.rs │ │ ├── imagetexture.rs │ │ └── uvtexture.rs ├── my_scene │ ├── bunny.rs │ ├── cornell.rs │ ├── cow.rs │ ├── easing.rs │ ├── fresnel.rs │ ├── heptoroid.rs │ ├── lucy.rs │ ├── mod.rs │ ├── sibenik.rs │ ├── sphere.rs │ ├── sponza.rs │ ├── tachikoma.rs │ └── teapot.rs ├── raytracer │ ├── animator │ │ ├── animator.rs │ │ ├── camerakeyframe.rs │ │ ├── easing.rs │ │ └── mod.rs │ ├── compositor │ │ ├── colorrgba.rs │ │ ├── mod.rs │ │ ├── surface.rs │ │ ├── surfacefactory.rs │ │ └── surfaceiterator.rs │ ├── intersection.rs │ ├── mod.rs │ ├── octree.rs │ ├── ray.rs │ └── renderer.rs ├── scene │ ├── camera.rs │ ├── mod.rs │ └── scene.rs ├── util │ ├── export.rs │ ├── import.rs │ └── mod.rs └── vec3.rs ├── test └── res │ ├── cube.obj │ └── png24.png └── tools ├── bench.sh ├── cbenchdec.py └── conf ├── box.json ├── bunny.json ├── cow.json └── teapot.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .cargo 3 | /config.stamp 4 | /Makefile 5 | /config.mk 6 | 7 | /main 8 | /main.exe 9 | /*.ppm 10 | /*.webm 11 | /*.mp4 12 | /*.mpg 13 | /*.gif 14 | /*.png 15 | /*.avi 16 | /*.mkv 17 | /*.exe 18 | 19 | # Windows image file caches 20 | Thumbs.db 21 | ehthumbs.db 22 | 23 | # Folder config file 24 | Desktop.ini 25 | 26 | # Recycle Bin used on file shares 27 | $RECYCLE.BIN/ 28 | 29 | # Windows Installer files 30 | *.cab 31 | *.msi 32 | *.msm 33 | *.msp 34 | 35 | # ========================= 36 | # Operating System Files 37 | # ========================= 38 | 39 | # OSX 40 | # ========================= 41 | 42 | .DS_Store 43 | .AppleDouble 44 | .LSOverride 45 | 46 | # Icon must ends with two \r. 47 | Icon 48 | # Thumbnails 49 | ._* 50 | 51 | # Files that might appear on external disk 52 | .Spotlight-V100 53 | .Trashes 54 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/assets"] 2 | path = docs/assets 3 | url = https://github.com/gyng/raytracer-assets.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: rust 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | matrix: 8 | allow_failures: 9 | - rust: nightly 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-raytracer" 3 | version = "0.1.0" 4 | authors = [ 5 | "ng.guoyou@gmail.com", 6 | "stacey.ell@gmail.com" 7 | ] 8 | 9 | [dependencies] 10 | image = "*" 11 | num = "*" 12 | num_cpus = "*" 13 | rand = "*" 14 | rustc-serialize = "*" 15 | threadpool = "*" 16 | time = "*" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2018 Ng Guoyou, Stacey Ell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rust-raytracer 2 | ============== 3 | [](https://travis-ci.org/gyng/rust-raytracer) 4 | 5 |  6 | 7 | A raytracer in Rust. Compiles on Rust stable > 1.5. 8 | 9 | [Gallery](http://gyng.github.io/rust-raytracer-gallery/) 10 | [Gallery repository](https://github.com/gyng/rust-raytracer-gallery) 11 | [Assets repository](https://github.com/gyng/raytracer-assets) 12 | 13 | 14 | ## Usage 15 | 16 | 1. Clone the project. `--recursive` clones most sample models and textures into the project directory as well. 17 | 18 | git clone --recursive https://github.com/gyng/rust-raytracer.git 19 | 20 | 2. Compile 21 | 22 | cargo build --release 23 | 24 | 3. Edit `sample-config.json` if you wish to render a scene besides the default, 25 | or if you wish to tweak the renderer parameters 26 | 27 | 4. Run the compiled program, passing the render configuration as an argument. 28 | If rendering a provided scene, run the binary in the project root so it can find the models and textures. 29 | 30 | ./main sample-config.json 31 | 32 | or alternatively to compile and run in one single command 33 | 34 | cargo run --release sample-config.json 35 | 36 | 37 | ### Useful commands 38 | 39 | * To update (assets) submodules only: `git submodule foreach git pull` 40 | * To convert frames into a video `ffmpeg -i test%06d.ppm -b 2000k out.webm` 41 | * Scenes are created in `./myscene/`. To hook up a scene, add it to `./myscene/mod.rs` and `get_camera_and_scene(&SceneConfig)` in `main.rs`. 42 | 43 | 44 | ## Available Scenes 45 | 46 | These should use 30deg fov for squares and 45deg fov for 16:9. 47 | 48 | * box 49 | * bunny 50 | * cow 51 | * easing (0s-10s animation) 52 | * fresnel (0s-10s animation) 53 | * lucy 54 | * sibenik (0s-7s animation) 55 | * sphere (0s-10s animation) 56 | * sponza (45deg fov for a square; 67.5deg for 16:9) 57 | * teapot 58 | * heptoroid-white 59 | * heptoroid-shiny 60 | * heptoroid-refractive 61 | * tachikoma 62 | 63 | 64 | ## Features 65 | 66 | * Reflections 67 | * Refractions 68 | * Multi-threading 69 | * Soft shadows 70 | * Supersampling 71 | * Cook-Torrance, Phong materials 72 | * Sphere, plane, triangle primitives 73 | * Point, sphere lights 74 | * Unoptimised glossy reflections 75 | * Limited OBJ model and mesh support 76 | * Mesh transformations (4x4 matrices) 77 | * Basic spatial partitioning (octree) 78 | * Basic textures (checker, uv, image) 79 | * Skybox (cubemap) 80 | * Camera animation with Bézier easing 81 | 82 | 83 | ## Missing/potential features 84 | 85 | * Scene description 86 | * Caustics/global illumination (progress stalled on `photon-trace` branch) 87 | -------------------------------------------------------------------------------- /docs/sample_render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gyng/rust-raytracer/855f2d053ac936a5fb2962d278d629f6269d6bf4/docs/sample_render.png -------------------------------------------------------------------------------- /sample-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cornell", 3 | "size": [512, 512], 4 | "fov": 30.0, 5 | "reflect_depth": 3, 6 | "refract_depth": 6, 7 | "shadow_samples": 64, 8 | "gloss_samples": 8, 9 | "pixel_samples": 2, 10 | "output_file": "test", 11 | "animating": false, 12 | "fps": 25.0, 13 | "time_slice": [0.0, 10.0], 14 | "starting_frame_number": 0 15 | } 16 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | main.exe 3 | *.ppm 4 | *.webm 5 | *.mp4 6 | *.mpg 7 | *.gif 8 | *.png 9 | *.avi 10 | *.mkv 11 | *.dmp 12 | 13 | # Windows image file caches 14 | Thumbs.db 15 | ehthumbs.db 16 | 17 | # Folder config file 18 | Desktop.ini 19 | 20 | # Recycle Bin used on file shares 21 | $RECYCLE.BIN/ 22 | 23 | # Windows Installer files 24 | *.cab 25 | *.msi 26 | *.msm 27 | *.msp 28 | 29 | # ========================= 30 | # Operating System Files 31 | # ========================= 32 | 33 | # OSX 34 | # ========================= 35 | 36 | .DS_Store 37 | .AppleDouble 38 | .LSOverride 39 | 40 | # Icon must ends with two \r. 41 | Icon 42 | # Thumbnails 43 | ._* 44 | 45 | # Files that might appear on external disk 46 | .Spotlight-V100 47 | .Trashes 48 | -------------------------------------------------------------------------------- /src/geometry/bbox.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use raytracer::Ray; 3 | use vec3::Vec3; 4 | 5 | #[derive(Clone, Copy, PartialEq)] 6 | pub struct BBox { 7 | pub min: Vec3, 8 | pub max: Vec3 9 | } 10 | 11 | pub trait PartialBoundingBox { 12 | fn partial_bounding_box(&self) -> Option; 13 | } 14 | 15 | impl PartialBoundingBox for BBox { 16 | fn partial_bounding_box(&self) -> Option { 17 | Some(*self) 18 | } 19 | } 20 | 21 | impl PartialBoundingBox for Option { 22 | fn partial_bounding_box(&self) -> Option { 23 | *self 24 | } 25 | } 26 | 27 | /// Given a bounding box and a point, compute and return a new `BBox` that 28 | /// encompasses the point and the space the original box encompassed. 29 | pub fn union_point(b: &BBox, p: &Vec3) -> BBox { 30 | BBox { 31 | min: Vec3 { 32 | x: b.min.x.min(p.x), 33 | y: b.min.y.min(p.y), 34 | z: b.min.z.min(p.z) 35 | }, 36 | max: Vec3 { 37 | x: b.max.x.max(p.x), 38 | y: b.max.y.max(p.y), 39 | z: b.max.z.max(p.z) 40 | } 41 | } 42 | } 43 | 44 | /// Given two points, compute and return a new `BBox` that encompasses both points 45 | pub fn union_points(p1: &Vec3, p2: &Vec3) -> BBox { 46 | BBox { 47 | min: Vec3 { 48 | x: p1.x.min(p2.x), 49 | y: p1.y.min(p2.y), 50 | z: p1.z.min(p2.z) 51 | }, 52 | max: Vec3 { 53 | x: p1.x.max(p2.x), 54 | y: p1.y.max(p2.y), 55 | z: p1.z.max(p2.z) 56 | } 57 | } 58 | } 59 | 60 | /// Given two bounding boxes, compute and return a new `BBox` that encompasses 61 | /// both spaces the original two boxes encompassed. 62 | pub fn union_bbox(b1: &BBox, b2: &BBox) -> BBox { 63 | BBox { 64 | min: Vec3 { 65 | x: b1.min.x.min(b2.min.x), 66 | y: b1.min.y.min(b2.min.y), 67 | z: b1.min.z.min(b2.min.z) 68 | }, 69 | max: Vec3 { 70 | x: b1.max.x.max(b2.max.x), 71 | y: b1.max.y.max(b2.max.y), 72 | z: b1.max.z.max(b2.max.z) 73 | } 74 | } 75 | } 76 | 77 | impl BBox { 78 | pub fn zero() -> Self { 79 | BBox { 80 | min: Vec3::zero(), 81 | max: Vec3::zero(), 82 | } 83 | } 84 | 85 | pub fn from_union(obj_iter: I) -> Option 86 | where 87 | P: PartialBoundingBox, 88 | I: Iterator { 89 | 90 | obj_iter 91 | .filter_map(|item| item.partial_bounding_box()) 92 | .fold(None, |acc, item| { 93 | Some(acc 94 | .map(|a| union_bbox(&a, &item)) 95 | .unwrap_or(item)) 96 | }) 97 | } 98 | 99 | pub fn intersects(&self, ray: &Ray) -> bool { 100 | // Using ray.inverse_dir is an optimisation. Normally, for simplicity we would do 101 | // 102 | // let d = -ray.direction; 103 | // tx1 = (self.min.x - o.x) / d.x; 104 | // ty1 = (self.min.y - o.y) / d.y; 105 | // ... 106 | // 107 | // but: 108 | // 109 | // 1. div is usually more expensive than mul 110 | // 2. we are recomputing the inverse of d each time we do an intersection check 111 | // 112 | // By caching 1.0 / -ray.direction inside the ray itself we do not need 113 | // to waste CPU cycles recomputing that every intersection check. 114 | // 115 | // See: https://truesculpt.googlecode.com/hg-history/Release%25200.8/Doc/ray_box_intersect.pdf 116 | 117 | let o = ray.origin; 118 | 119 | let (min_bound, max_bound) = if ray.signs[0] { 120 | (self.min, self.max) 121 | } else { 122 | (self.max, self.min) 123 | }; 124 | let mut t_min = (min_bound.x - o.x) * ray.inverse_dir.x; 125 | let mut t_max = (max_bound.x - o.x) * ray.inverse_dir.x; 126 | 127 | let (min_y_bound, max_y_bound) = if ray.signs[1] { 128 | (self.min, self.max) 129 | } else { 130 | (self.max, self.min) 131 | }; 132 | let ty_min = (min_y_bound.y - o.y) * ray.inverse_dir.y; 133 | let ty_max = (max_y_bound.y - o.y) * ray.inverse_dir.y; 134 | 135 | if t_min > ty_max || ty_min > t_max { 136 | return false 137 | } 138 | if ty_min > t_min { 139 | t_min = ty_min; 140 | } 141 | if ty_max < t_max { 142 | t_max = ty_max; 143 | } 144 | 145 | let (min_z_bound, max_z_bound) = if ray.signs[2] { 146 | (self.min, self.max) 147 | } else { 148 | (self.max, self.min) 149 | }; 150 | let tz_min = (min_z_bound.z - o.z) * ray.inverse_dir.z; 151 | let tz_max = (max_z_bound.z - o.z) * ray.inverse_dir.z; 152 | 153 | if t_min > tz_max || tz_min > t_max { 154 | return false 155 | } 156 | if tz_min > t_min { 157 | t_min = tz_min; 158 | } 159 | if tz_max < t_max { 160 | t_max = tz_max; 161 | } 162 | 163 | // tmin < t1 && tmax > t0 164 | t_min < ::std::f64::INFINITY && t_max > 0.0 165 | } 166 | 167 | pub fn overlaps(&self, other: &BBox) -> bool { 168 | let x = self.max.x >= other.min.x && self.min.x <= other.max.x; 169 | let y = self.max.y >= other.min.y && self.min.y <= other.max.y; 170 | let z = self.max.z >= other.min.z && self.min.z <= other.max.z; 171 | 172 | x && y && z 173 | } 174 | 175 | pub fn inside(&self, p: &Vec3) -> bool { 176 | p.x >= self.min.x && p.x <= self.max.x && 177 | p.y >= self.min.y && p.y <= self.max.y && 178 | p.z >= self.min.z && p.z <= self.max.z 179 | } 180 | 181 | pub fn contains(&self, other: &BBox) -> bool { 182 | other.min.x >= self.min.x && 183 | other.min.y >= self.min.y && 184 | other.min.z >= self.min.z && 185 | other.max.x <= self.max.x && 186 | other.max.y <= self.max.y && 187 | other.max.z <= self.max.z 188 | } 189 | 190 | /// Pad bounding box by a constant factor. 191 | pub fn expand(&self, delta: f64) -> BBox { 192 | let delta_vec3 = Vec3 { x: delta, y: delta, z: delta }; 193 | 194 | BBox { 195 | min: self.min - delta_vec3, 196 | max: self.max + delta_vec3 197 | } 198 | } 199 | 200 | /// Returns which axis is the widest. 0: x, 1: y, 2: z 201 | pub fn max_extent(&self) -> u8 { 202 | let diag = self.max - self.min; 203 | if diag.x > diag.y && diag.x > diag.z { 204 | 0 205 | } else if diag.y > diag.z { 206 | 1 207 | } else { 208 | 2 209 | } 210 | } 211 | 212 | /// Interpolate between corners of the box. 213 | pub fn lerp(&self, t_x: f64, t_y: f64, t_z: f64) -> Vec3 { 214 | let diag = self.max - self.min; 215 | Vec3 { 216 | x: self.min.x + diag.x * t_x, 217 | y: self.min.y + diag.y * t_y, 218 | z: self.min.z + diag.z * t_z 219 | } 220 | } 221 | 222 | /// Offset from minimum corner point 223 | pub fn offset(&self, offset: &Vec3) -> Vec3 { 224 | let diag = self.max - self.min; 225 | Vec3 { 226 | x: (offset.x - self.min.x) / diag.x, 227 | y: (offset.y - self.min.y) / diag.y, 228 | z: (offset.z - self.min.z) / diag.z 229 | } 230 | } 231 | 232 | pub fn x_len(&self) -> f64 { 233 | self.max.x - self.min.x 234 | } 235 | 236 | pub fn y_len(&self) -> f64 { 237 | self.max.y - self.min.y 238 | } 239 | 240 | pub fn z_len(&self) -> f64 { 241 | self.max.z - self.min.z 242 | } 243 | 244 | pub fn len(&self) -> Vec3 { 245 | self.max - self.min 246 | } 247 | } 248 | 249 | #[test] 250 | fn it_intersects_with_a_ray() { 251 | let bbox = BBox { 252 | min: Vec3::zero(), 253 | max: Vec3::one() 254 | }; 255 | 256 | // Out of the box 257 | let mut intersecting_ray = Ray::new(Vec3 { x: 0.5, y: 1.5, z: 0.5 }, Vec3 { x: 0.0, y: -1.0, z: 0.0 }); 258 | assert!(bbox.intersects(&intersecting_ray)); 259 | 260 | // In the box 261 | intersecting_ray = Ray::new(Vec3 { x: 0.5, y: 0.5, z: 0.5 }, Vec3 { x: 0.0, y: -1.0, z: 0.0 }); 262 | assert!(bbox.intersects(&intersecting_ray)); 263 | 264 | // Away from box 265 | let mut non_intersecting_ray = Ray::new(Vec3 { x: 0.5, y: 1.5, z: 0.5 }, Vec3 { x: 0.0, y: 1.0, z: 0.0 }); 266 | assert_eq!(false, bbox.intersects(&non_intersecting_ray)); 267 | 268 | // To the side 269 | non_intersecting_ray = Ray::new(Vec3 { x: 0.5, y: 1.5, z: 0.5 }, Vec3 { x: 1000.0, y: -1.0, z: 1000.0 }); 270 | assert_eq!(false, bbox.intersects(&non_intersecting_ray)); 271 | } 272 | 273 | #[test] 274 | fn it_unions_a_bbox_with_a_point() { 275 | let original_bbox = BBox { 276 | min: Vec3::zero(), 277 | max: Vec3::one() 278 | }; 279 | 280 | let smaller_point = Vec3 { x: -1.0, y: -1.0, z: -1.0 }; 281 | let unioned_bbox = union_point(&original_bbox, &smaller_point); 282 | assert_eq!(unioned_bbox.min, smaller_point); 283 | assert_eq!(unioned_bbox.max, Vec3::one()); 284 | 285 | let larger_point = Vec3 { x: 2.0, y: 2.0, z: 2.0 }; 286 | let unioned_bbox2 = union_point(&unioned_bbox, &larger_point); 287 | assert_eq!(unioned_bbox2.min, smaller_point); 288 | assert_eq!(unioned_bbox2.max, larger_point); 289 | } 290 | 291 | #[test] 292 | fn it_unions_two_points() { 293 | // Larger to smaller 294 | let unioned_bbox = union_points(&Vec3::one(), &Vec3::zero()); 295 | assert_eq!(unioned_bbox.min, Vec3::zero()); 296 | assert_eq!(unioned_bbox.max, Vec3::one()); 297 | 298 | // Smaller to larger 299 | let unioned_bbox2 = union_points(&-Vec3::one(), &Vec3::zero()); 300 | assert_eq!(unioned_bbox2.min, -Vec3::one()); 301 | assert_eq!(unioned_bbox2.max, Vec3::zero()); 302 | } 303 | 304 | #[test] 305 | fn it_unions_two_bboxes() { 306 | let bbox_one = BBox { 307 | min: Vec3::zero(), 308 | max: Vec3::one() 309 | }; 310 | 311 | let bbox_two = BBox { 312 | min: -Vec3::one(), 313 | max: Vec3::zero() 314 | }; 315 | 316 | let unioned_bbox = union_bbox(&bbox_one, &bbox_two); 317 | assert_eq!(unioned_bbox.min, -Vec3::one()); 318 | assert_eq!(unioned_bbox.max, Vec3::one()); 319 | } 320 | 321 | #[test] 322 | fn it_checks_for_bbox_overlap() { 323 | let bbox = BBox { 324 | min: Vec3::zero(), 325 | max: Vec3::one() 326 | }; 327 | 328 | let overlapping = BBox { 329 | min: Vec3 { x: 0.5, y: 0.5, z: 0.5 }, 330 | max: Vec3 { x: 1.5, y: 1.5, z: 1.5 } 331 | }; 332 | 333 | let not_overlapping = BBox { 334 | min: Vec3 { x: 1.5, y: 1.5, z: 1.5 }, 335 | max: Vec3 { x: 2.5, y: 2.5, z: 2.5 } 336 | }; 337 | 338 | assert!(bbox.overlaps(&overlapping)); 339 | assert_eq!(false, bbox.overlaps(¬_overlapping)); 340 | } 341 | 342 | #[test] 343 | fn it_checks_for_point_inside() { 344 | let bbox = BBox { 345 | min: Vec3::zero(), 346 | max: Vec3::one() 347 | }; 348 | 349 | let inside = Vec3 { x: 0.5, y: 0.5, z: 0.5 }; 350 | assert!(bbox.inside(&inside)); 351 | 352 | let outside_1 = Vec3 { x: 1.5, y: 1.5, z: 1.5 }; 353 | let outside_2 = Vec3 { x: 0.5, y: 1.5, z: 0.5 }; 354 | let outside_3 = Vec3 { x: -0.5, y: 0.5, z: 0.5 }; 355 | 356 | assert_eq!(false, bbox.inside(&outside_1)); 357 | assert_eq!(false, bbox.inside(&outside_2)); 358 | assert_eq!(false, bbox.inside(&outside_3)); 359 | } 360 | 361 | #[test] 362 | fn it_checks_for_contains_another_bbox() { 363 | let bbox = BBox { 364 | min: Vec3::zero(), 365 | max: Vec3::one() 366 | }; 367 | 368 | let overlapping = BBox { 369 | min: Vec3 { x: 0.5, y: 0.5, z: 0.5 }, 370 | max: Vec3 { x: 1.5, y: 1.5, z: 1.5 } 371 | }; 372 | 373 | let not_overlapping = BBox { 374 | min: Vec3 { x: 1.5, y: 1.5, z: 1.5 }, 375 | max: Vec3 { x: 2.5, y: 2.5, z: 2.5 } 376 | }; 377 | 378 | let inside = BBox { 379 | min: Vec3 { x: 0.25, y: 0.25, z: 0.25 }, 380 | max: Vec3 { x: 0.75, y: 0.75, z: 0.75 } 381 | }; 382 | 383 | assert_eq!(false, bbox.contains(&overlapping)); 384 | assert_eq!(false, bbox.contains(¬_overlapping)); 385 | assert!(bbox.contains(&inside)); 386 | } 387 | 388 | #[test] 389 | fn it_expands_by_a_factor() { 390 | let bbox = BBox { 391 | min: Vec3::zero(), 392 | max: Vec3::one() 393 | }; 394 | 395 | let expanded = bbox.expand(1.0); 396 | assert_eq!(-Vec3::one(), expanded.min); 397 | assert_eq!(Vec3::one().scale(2.0), expanded.max); 398 | 399 | let shrunken = bbox.expand(-0.25); 400 | assert_eq!(Vec3 { x: 0.25, y: 0.25, z: 0.25 }, shrunken.min); 401 | assert_eq!(Vec3 { x: 0.75, y: 0.75, z: 0.75 }, shrunken.max); 402 | } 403 | 404 | #[test] 405 | fn it_returns_max_extent() { 406 | let x = BBox { 407 | min: Vec3::zero(), 408 | max: Vec3 { x: 2.0, y: 1.0, z: 1.0 } 409 | }; 410 | 411 | let y = BBox { 412 | min: Vec3::zero(), 413 | max: Vec3 { x: 1.0, y: 2.0, z: 1.0 } 414 | }; 415 | 416 | let z = BBox { 417 | min: Vec3::zero(), 418 | max: Vec3 { x: 1.0, y: 1.0, z: 2.0 } 419 | }; 420 | 421 | assert_eq!(0u8, x.max_extent()); 422 | assert_eq!(1u8, y.max_extent()); 423 | assert_eq!(2u8, z.max_extent()); 424 | } 425 | 426 | #[test] 427 | fn it_returns_offset_length_from_min_corner() { 428 | let bbox = BBox { 429 | min: -Vec3::one(), 430 | max: Vec3::one() 431 | }; 432 | 433 | let offset_point = bbox.offset(&Vec3::one()); 434 | assert_eq!(Vec3::one(), offset_point); 435 | } 436 | 437 | #[test] 438 | fn it_returns_side_lengths() { 439 | let bbox = BBox { 440 | min: Vec3::zero(), 441 | max: Vec3 { x: 1.0, y: 2.0, z: 3.0 } 442 | }; 443 | 444 | assert_eq!(1.0, bbox.x_len()); 445 | assert_eq!(2.0, bbox.y_len()); 446 | assert_eq!(3.0, bbox.z_len()); 447 | } 448 | -------------------------------------------------------------------------------- /src/geometry/mesh.rs: -------------------------------------------------------------------------------- 1 | use geometry::Prim; 2 | use mat4::Transform; 3 | 4 | #[allow(dead_code)] 5 | pub struct Mesh { 6 | pub triangles: Vec> 7 | } 8 | 9 | impl Mesh { 10 | pub fn mut_transform(&mut self, transform: &Transform) { 11 | for triangle in &mut self.triangles { 12 | triangle.mut_transform(transform); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/geometry/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::prim::Prim; 2 | pub use self::mesh::Mesh; 3 | pub use self::bbox::{BBox, PartialBoundingBox}; 4 | 5 | pub mod bbox; 6 | pub mod prim; 7 | pub mod mesh; 8 | 9 | pub mod prims { 10 | pub use self::plane::Plane; 11 | pub use self::sphere::Sphere; 12 | pub use self::triangle::{Triangle, TriangleOptions}; 13 | 14 | mod plane; 15 | mod sphere; 16 | mod triangle; 17 | } 18 | -------------------------------------------------------------------------------- /src/geometry/prim.rs: -------------------------------------------------------------------------------- 1 | use geometry::{BBox, PartialBoundingBox}; 2 | use raytracer::{Ray, Intersection}; 3 | use mat4::Transform; 4 | 5 | pub trait Prim: PartialBoundingBox { 6 | fn intersects<'a>(&'a self, ray: &Ray, t_min: f64, t_max: f64) -> Option>; 7 | 8 | // fn transform(&self, transform: &Transform) -> Box; 9 | fn mut_transform(&mut self, transform: &Transform); 10 | } 11 | 12 | impl<'a> PartialBoundingBox for Box { 13 | fn partial_bounding_box(&self) -> Option { 14 | (**self).partial_bounding_box() 15 | } 16 | } -------------------------------------------------------------------------------- /src/geometry/prims/plane.rs: -------------------------------------------------------------------------------- 1 | use geometry::{BBox, PartialBoundingBox, Prim}; 2 | use material::Material; 3 | use mat4::{Mat4, Transform}; 4 | use raytracer::{Ray, Intersection}; 5 | use vec3::Vec3; 6 | 7 | #[cfg(test)] 8 | use material::materials::FlatMaterial; 9 | 10 | #[allow(dead_code)] 11 | pub struct Plane { 12 | pub a: f64, // normal.x 13 | pub b: f64, // normal.y 14 | pub c: f64, // normal.z 15 | pub d: f64, 16 | pub material: Box 17 | } 18 | 19 | impl PartialBoundingBox for Plane { 20 | fn partial_bounding_box(&self) -> Option { 21 | None // more infinite than infinityb 22 | } 23 | } 24 | 25 | impl Prim for Plane { 26 | fn intersects<'a>(&'a self, ray: &Ray, t_min: f64, t_max: f64) -> Option> { 27 | let n = Vec3 { x: self.a, y: self.b, z: self.c }; 28 | let nrd = n.dot(&ray.direction); 29 | let nro = n.dot(&ray.origin); 30 | let t = (-self.d - nro) / nrd; 31 | 32 | if t < t_min || t > t_max { 33 | None 34 | } else { 35 | let intersection_point = ray.origin + ray.direction.scale(t); 36 | let u_axis = Vec3 { x: n.y, y: n.z, z: -n.x }; 37 | let v_axis = u_axis.cross(&n); 38 | let u = intersection_point.dot(&u_axis); 39 | let v = intersection_point.dot(&v_axis); 40 | 41 | Some(Intersection { 42 | n: n, 43 | t: t, 44 | u: u, 45 | v: v, 46 | position: intersection_point, 47 | material: &self.material 48 | }) 49 | } 50 | } 51 | 52 | /// This transformation is entirely ad-hoc, do not trust this 53 | fn mut_transform(&mut self, transform: &Transform) { 54 | let new_v = Mat4::mult_v(&transform.m, &Vec3 { x: self.a, y: self.b, z: self.c }); 55 | 56 | self.a = new_v.x; 57 | self.b = new_v.y; 58 | self.c = new_v.z; 59 | 60 | let trans = Vec3 { 61 | x: transform.m.m[0][3], 62 | y: transform.m.m[1][3], 63 | z: transform.m.m[2][3] 64 | }; 65 | 66 | let t_x = transform.m.m[0][3].powf(2.0) * if trans.x < 0.0 { -1.0 } else { 1.0 }; 67 | let t_y = transform.m.m[1][3].powf(2.0) * if trans.y < 0.0 { -1.0 } else { 1.0 }; 68 | let t_z = transform.m.m[2][3].powf(2.0) * if trans.z < 0.0 { -1.0 } else { 1.0 }; 69 | let add_sub = if t_x + t_y + t_z < 0.0 { -1.0 } else { 1.0 }; 70 | 71 | self.d += trans.len() * add_sub; 72 | } 73 | } 74 | 75 | #[test] 76 | fn it_intersects() { 77 | let plane = Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(FlatMaterial { color: Vec3::one() }) }; 78 | 79 | // Tests actual intersection 80 | let intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 1.0, z: 0.0 }, Vec3 { x: 0.0, y: -1.0, z: 0.0 }); 81 | let intersection = plane.intersects(&intersecting_ray, 0.0, 10.0).unwrap(); 82 | assert_eq!(intersection.position.x, 0.0); 83 | assert_eq!(intersection.position.y, 0.0); 84 | assert_eq!(intersection.position.z, 0.0); 85 | assert_eq!(intersection.n.x, 0.0); 86 | assert_eq!(intersection.n.y, 1.0); 87 | assert_eq!(intersection.n.z, 0.0); 88 | 89 | // Parallel ray 90 | let mut non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 1.0, z: 0.0 }, Vec3 { x: 1.0, y: 0.0, z: 1.0 }); 91 | let mut non_intersection = plane.intersects(&non_intersecting_ray, 0.0, 10000.0); 92 | assert!(non_intersection.is_none()); 93 | 94 | // Ray in opposite direction 95 | non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 1.0, z: 0.0 }, Vec3 { x: 0.0, y: 1.0, z: 0.0 }); 96 | non_intersection = plane.intersects(&non_intersecting_ray, 0.0, 10.0); 97 | assert!(non_intersection.is_none()); 98 | } 99 | 100 | #[test] 101 | fn it_intersects_only_in_tmin_tmax() { 102 | let plane = Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(FlatMaterial { color: Vec3::one() }) }; 103 | 104 | // Tests tmin 105 | let intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 1.0, z: 0.0 }, Vec3 { x: 0.0, y: -1.0, z: 0.0 }); 106 | let mut non_intersection = plane.intersects(&intersecting_ray, 1000.0, 10000.0); 107 | assert!(non_intersection.is_none()); 108 | 109 | // Tests tmax 110 | non_intersection = plane.intersects(&intersecting_ray, 0.0, 0.0001); 111 | assert!(non_intersection.is_none()); 112 | } 113 | -------------------------------------------------------------------------------- /src/geometry/prims/sphere.rs: -------------------------------------------------------------------------------- 1 | use geometry::bbox::{BBox, PartialBoundingBox}; 2 | use geometry::prim::Prim; 3 | use material::Material; 4 | use mat4::{Mat4, Transform}; 5 | use raytracer::{Ray, Intersection}; 6 | use vec3::Vec3; 7 | 8 | #[cfg(test)] 9 | use material::materials::FlatMaterial; 10 | 11 | #[allow(dead_code)] 12 | pub struct Sphere { 13 | pub center: Vec3, 14 | pub radius: f64, 15 | pub material: Box 16 | } 17 | 18 | impl PartialBoundingBox for Sphere { 19 | fn partial_bounding_box(&self) -> Option { 20 | Some(BBox { 21 | min: Vec3 { 22 | x: self.center.x - self.radius, 23 | y: self.center.y - self.radius, 24 | z: self.center.z - self.radius 25 | }, 26 | max: Vec3 { 27 | x: self.center.x + self.radius, 28 | y: self.center.y + self.radius, 29 | z: self.center.z + self.radius 30 | } 31 | }) 32 | } 33 | } 34 | 35 | impl Prim for Sphere { 36 | fn intersects<'a>(&'a self, ray: &Ray, t_min: f64, t_max: f64) -> Option> { 37 | let i = ray.origin - self.center; 38 | let a = 1.0; 39 | let b = 2.0 * ray.direction.dot(&i); 40 | let c = i.dot(&i) - self.radius * self.radius; 41 | let discriminant = b * b - 4.0 * a * c; 42 | 43 | if discriminant <= 0.0 { 44 | None 45 | } else { 46 | // Up to two intersections 47 | let disc_sqrt = discriminant.sqrt(); 48 | let t1 = (-b + disc_sqrt) / 2.0 * a; 49 | let t2 = (-b - disc_sqrt) / 2.0 * a; 50 | 51 | if t1 >= t_min && t1 <= t_max || 52 | t2 >= t_min && t2 <= t_max { 53 | // Valid intersection(s): get nearer intersection 54 | let t = if t1.abs() < t2.abs() { t1 } else { t2 }; 55 | let intersection_point = ray.origin + ray.direction.scale(t); 56 | let n = (intersection_point - self.center).unit(); 57 | 58 | let u = 0.5 + n.z.atan2(n.x) / (::std::f64::consts::PI * 2.0); 59 | let v = 0.5 - n.y.asin() / ::std::f64::consts::PI; 60 | 61 | Some(Intersection { 62 | n: n, 63 | t: t, 64 | u: u, 65 | v: v, 66 | position: intersection_point, 67 | material: &self.material 68 | }) 69 | } else { 70 | None 71 | } 72 | } 73 | } 74 | 75 | fn mut_transform(&mut self, transform: &Transform) { 76 | let new_center = Mat4::mult_p(&transform.m, &self.center); 77 | 78 | let new_radius = if transform.m.has_scale() { 79 | self.radius * transform.m.scale() 80 | } else { 81 | self.radius 82 | }; 83 | 84 | self.center = new_center; 85 | self.radius = new_radius; 86 | } 87 | } 88 | 89 | #[test] 90 | fn it_intersects() { 91 | let sphere = Sphere { 92 | center: Vec3::zero(), 93 | radius: 1.0, 94 | material: Box::new(FlatMaterial { color: Vec3::one() }) 95 | }; 96 | 97 | // Tests actual intersection 98 | let intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.0, z: -2.0 }, Vec3 { x: 0.0, y: 0.0, z: 1.0 }); 99 | let intersection = sphere.intersects(&intersecting_ray, 0.0, 10.0).unwrap(); 100 | assert_eq!(intersection.position.x, 0.0); 101 | assert_eq!(intersection.position.y, 0.0); 102 | assert_eq!(intersection.position.z, -1.0); 103 | assert_eq!(intersection.n.x, 0.0); 104 | assert_eq!(intersection.n.y, 0.0); 105 | assert_eq!(intersection.n.z, -1.0); 106 | 107 | // Ray off to the sides 108 | let mut non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.0, z: -2.0 }, Vec3 { x: 100.0, y: 100.0, z: 0.1 }); 109 | let mut non_intersection = sphere.intersects(&non_intersecting_ray, 0.0, 10.0); 110 | assert!(non_intersection.is_none()); 111 | 112 | non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.0, z: -2.0 }, Vec3 { x: -100.0, y: -100.0, z: 0.1 }); 113 | non_intersection = sphere.intersects(&non_intersecting_ray, 0.0, 10.0); 114 | assert!(non_intersection.is_none()); 115 | 116 | // Ray in opposite direction 117 | non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.0, z: -2.0 }, Vec3 {x: 0.0, y: 0.0, z: -1.0 }); 118 | non_intersection = sphere.intersects(&non_intersecting_ray, 0.0, 10.0); 119 | assert!(non_intersection.is_none()); 120 | } 121 | 122 | #[test] 123 | fn it_intersects_only_in_tmin_tmax() { 124 | let sphere = Sphere { 125 | center: Vec3::zero(), 126 | radius: 1.0, 127 | material: Box::new(FlatMaterial { color: Vec3::one() }) 128 | }; 129 | 130 | // Tests tmin 131 | let intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.0, z: -2.0 }, Vec3 { x: 0.0, y: 0.0, z: 1.0 }); 132 | let mut non_intersection = sphere.intersects(&intersecting_ray, 1000.0, 10000.0); 133 | assert!(non_intersection.is_none()); 134 | 135 | // Tests tmax 136 | non_intersection = sphere.intersects(&intersecting_ray, 0.0, 0.0001); 137 | assert!(non_intersection.is_none()); 138 | } 139 | -------------------------------------------------------------------------------- /src/geometry/prims/triangle.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use geometry::bbox::{union_point, union_points, BBox, PartialBoundingBox}; 4 | use geometry::prim::Prim; 5 | use material::Material; 6 | use mat4::{Mat4, Transform}; 7 | use raytracer::{Ray, Intersection}; 8 | use vec3::Vec3; 9 | 10 | use material::materials::FlatMaterial; 11 | 12 | 13 | struct UvValue { 14 | u: f64, 15 | v: f64 16 | } 17 | 18 | impl UvValue { 19 | pub fn from_tuple(uv: (f64, f64)) -> UvValue { 20 | UvValue { u: uv.0, v: uv.1 } 21 | } 22 | 23 | fn default3() -> [UvValue; 3] { 24 | [ 25 | UvValue { u: 0.5, v: 1.0 }, 26 | UvValue { u: 0.0, v: 0.0 }, 27 | UvValue { u: 1.0, v: 0.0 }, 28 | ] 29 | } 30 | } 31 | 32 | pub struct TriangleOptions { 33 | vertices: [Vec3; 3], 34 | normals: Option<[Vec3; 3]>, 35 | texinfo: Option<[UvValue; 3]>, 36 | material: Option>, 37 | } 38 | 39 | fn get_auto_normals(v: [Vec3; 3]) -> [Vec3; 3] { 40 | let n = (v[1] - v[0]).cross(&(v[2] - v[0])); 41 | [n, n, n] 42 | } 43 | 44 | impl TriangleOptions { 45 | pub fn new(v0: Vec3, v1: Vec3, v2: Vec3) -> TriangleOptions { 46 | TriangleOptions { 47 | vertices: [v0, v1, v2], 48 | normals: None, 49 | texinfo: None, 50 | material: None, 51 | } 52 | } 53 | 54 | /// In the default case, all three normals at vertices are perpendicular 55 | /// to the triangle plane. 56 | pub fn normals(&mut self, normals: [Vec3; 3]) -> &mut Self { 57 | self.normals = Some(normals); 58 | self 59 | } 60 | 61 | pub fn texinfo(&mut self, texinfo: [(f64, f64); 3]) -> &mut Self { 62 | self.texinfo = Some([ 63 | UvValue::from_tuple(texinfo[0]), 64 | UvValue::from_tuple(texinfo[1]), 65 | UvValue::from_tuple(texinfo[2]), 66 | ]); 67 | self 68 | } 69 | 70 | pub fn material(&mut self, material: Box) -> &mut Self { 71 | self.material = Some(material); 72 | self 73 | } 74 | 75 | pub fn build(self) -> Triangle { 76 | let normals = self.normals.unwrap_or_else(|| get_auto_normals(self.vertices)); 77 | let texinfo = self.texinfo.unwrap_or_else(UvValue::default3); 78 | let material = self.material.unwrap_or_else(|| Box::new(FlatMaterial { color: Vec3::one() })); 79 | 80 | Triangle { 81 | vertices: self.vertices, 82 | normals: normals, 83 | texinfo: texinfo, 84 | material: material, 85 | } 86 | } 87 | } 88 | 89 | pub struct Triangle { 90 | vertices: [Vec3; 3], 91 | 92 | // All the same if our triangle is ``flat''. 93 | // Values differ when we want interpolation. e.g. round things like teapot. 94 | normals: [Vec3; 3], 95 | 96 | // Used in textured triangles, can be [UvValue; 3]::default() otherwise. 97 | texinfo: [UvValue; 3], 98 | 99 | material: Box 100 | } 101 | 102 | impl PartialBoundingBox for Triangle { 103 | fn partial_bounding_box(&self) -> Option { 104 | Some(union_point(&union_points(&self.vertices[0], &self.vertices[1]), &self.vertices[2])) 105 | } 106 | } 107 | 108 | impl Prim for Triangle { 109 | /// http://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm 110 | /// Barycentric coordinates. 111 | fn intersects<'a>(&'a self, ray: &Ray, t_min: f64, t_max: f64) -> Option> { 112 | let e1 = self.vertices[1] - self.vertices[0]; 113 | let e2 = self.vertices[2] - self.vertices[0]; 114 | let p = ray.direction.cross(&e2); 115 | let det = e1.dot(&p); 116 | 117 | // if determinant is near zero, ray lies in plane of triangle 118 | if det > -::std::f64::EPSILON && det < ::std::f64::EPSILON { 119 | return None 120 | } 121 | 122 | let inv_det = 1.0 / det; 123 | let s = ray.origin - self.vertices[0]; 124 | let beta = inv_det * s.dot(&p); 125 | if beta < 0.0 || beta > 1.0 { return None } 126 | 127 | let q = s.cross(&e1); 128 | let gamma = inv_det * ray.direction.dot(&q); 129 | if gamma < 0.0 || beta + gamma > 1.0 { return None } 130 | 131 | let t = inv_det * e2.dot(&q); 132 | 133 | if t < t_min || t > t_max { 134 | None 135 | } else { 136 | let intersection_point = ray.origin + ray.direction.scale(t); 137 | 138 | let alpha = 1.0 - beta - gamma; 139 | 140 | // Interpolate normals at vertices to get normal 141 | let n = self.normals[0].scale(alpha) + self.normals[1].scale(beta) + self.normals[2].scale(gamma); 142 | 143 | // Interpolate UVs at vertices to get UV 144 | let u = self.texinfo[0].u * alpha + self.texinfo[1].u * beta + self.texinfo[2].u * gamma; 145 | let v = self.texinfo[0].v * alpha + self.texinfo[1].v * beta + self.texinfo[2].v * gamma; 146 | 147 | Some(Intersection { 148 | n: n, 149 | t: t, 150 | u: u, 151 | v: v, 152 | position: intersection_point, 153 | material: &self.material 154 | }) 155 | } 156 | } 157 | 158 | fn mut_transform(&mut self, transform: &Transform) { 159 | let v0_t = Mat4::mult_p(&transform.m, &self.vertices[0]); 160 | let v1_t = Mat4::mult_p(&transform.m, &self.vertices[1]); 161 | let v2_t = Mat4::mult_p(&transform.m, &self.vertices[2]); 162 | 163 | let n0_t = Mat4::transform_normal(&self.normals[0], &transform.m); 164 | let n1_t = Mat4::transform_normal(&self.normals[1], &transform.m); 165 | let n2_t = Mat4::transform_normal(&self.normals[2], &transform.m); 166 | 167 | self.vertices[0] = v0_t; 168 | self.vertices[1] = v1_t; 169 | self.vertices[2] = v2_t; 170 | 171 | self.normals[0] = n0_t; 172 | self.normals[1] = n1_t; 173 | self.normals[2] = n2_t; 174 | } 175 | } 176 | 177 | #[test] 178 | fn it_intersects_and_interpolates() { 179 | let mut triopts = TriangleOptions::new( 180 | Vec3 { x: -1.0, y: 0.0, z: 0.0 }, 181 | Vec3 { x: 1.0, y: 0.0, z: 0.0 }, 182 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }); 183 | triopts.normals([ 184 | Vec3 { x: -1.0, y: 0.0, z: 0.0 }, 185 | Vec3 { x: 1.0, y: 0.0, z: 0.0 }, 186 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }]); 187 | triopts.texinfo([(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)]); 188 | 189 | let triangle = triopts.build(); 190 | 191 | // Tests actual intersection 192 | let intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.5, z: -1.0 }, Vec3 { x: 0.0, y: 0.0, z: 1.0 }); 193 | let intersection = triangle.intersects(&intersecting_ray, 0.0, 10.0).unwrap(); 194 | assert_eq!(intersection.position.x, 0.0); 195 | assert_eq!(intersection.position.y, 0.5); 196 | assert_eq!(intersection.position.z, 0.0); 197 | assert_eq!(intersection.u, 0.25); 198 | assert_eq!(intersection.v, 0.5); 199 | assert_eq!(intersection.n.x, 0.0); 200 | assert_eq!(intersection.n.y, 0.5); 201 | assert_eq!(intersection.n.z, 0.0); 202 | 203 | // Ray off to the sides 204 | let mut non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.5, z: -1.0 }, Vec3 { x: 100.0, y: 100.0, z: 1.0 }); 205 | let mut non_intersection = triangle.intersects(&non_intersecting_ray, 0.0, 10.0); 206 | assert!(non_intersection.is_none()); 207 | 208 | non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.5, z: -1.0 }, Vec3 { x: -100.0, y: -100.0, z: 1.0 }); 209 | non_intersection = triangle.intersects(&non_intersecting_ray, 0.0, 10.0); 210 | assert!(non_intersection.is_none()); 211 | 212 | // Ray in opposite direction 213 | non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.5, z: -1.0 }, Vec3 { x: 0.0, y: 0.0, z: -1.0 }); 214 | non_intersection = triangle.intersects(&non_intersecting_ray, 0.0, 10.0); 215 | assert!(non_intersection.is_none()); 216 | } 217 | 218 | #[test] 219 | fn it_intersects_only_in_tmin_tmax() { 220 | let mut triopts = TriangleOptions::new( 221 | Vec3 { x: -1.0, y: 0.0, z: 0.0 }, 222 | Vec3 { x: 1.0, y: 0.0, z: 0.0 }, 223 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }); 224 | triopts.normals([Vec3::zero(), Vec3::zero(), Vec3::one()]); 225 | triopts.texinfo([(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)]); 226 | 227 | let triangle = triopts.build(); 228 | 229 | // Tests tmin 230 | let intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.5, z: -1.0 }, Vec3 { x: 0.0, y: 0.0, z: 1.0 }); 231 | let mut non_intersection = triangle.intersects(&intersecting_ray, 1000.0, 10000.0); 232 | assert!(non_intersection.is_none()); 233 | 234 | // Tests tmax 235 | non_intersection = triangle.intersects(&intersecting_ray, 0.0, 0.0001); 236 | assert!(non_intersection.is_none()); 237 | } 238 | -------------------------------------------------------------------------------- /src/light/light.rs: -------------------------------------------------------------------------------- 1 | use vec3::Vec3; 2 | 3 | pub trait Light { 4 | fn position(&self) -> Vec3; 5 | fn color(&self) -> Vec3; 6 | fn center(&self) -> Vec3; 7 | fn is_point(&self) -> bool; 8 | } 9 | -------------------------------------------------------------------------------- /src/light/lights/pointlight.rs: -------------------------------------------------------------------------------- 1 | use light::light::Light; 2 | use vec3::Vec3; 3 | 4 | #[allow(dead_code)] 5 | pub struct PointLight { 6 | pub position: Vec3, 7 | pub color: Vec3 8 | } 9 | 10 | impl Light for PointLight { 11 | fn position(&self) -> Vec3 { 12 | self.position 13 | } 14 | 15 | fn color(&self) -> Vec3 { 16 | self.color 17 | } 18 | 19 | fn center(&self) -> Vec3 { 20 | self.position 21 | } 22 | 23 | fn is_point(&self) -> bool { 24 | true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/light/lights/spherelight.rs: -------------------------------------------------------------------------------- 1 | use rand::{thread_rng, Rng}; 2 | use light::light::Light; 3 | use vec3::Vec3; 4 | 5 | #[allow(dead_code)] 6 | pub struct SphereLight { 7 | pub position: Vec3, 8 | pub color: Vec3, 9 | pub radius: f64 10 | } 11 | 12 | impl Light for SphereLight { 13 | fn position(&self) -> Vec3 { 14 | let mut rng = thread_rng(); 15 | 16 | let jitter = Vec3 { 17 | x: self.radius * (rng.gen::() - 0.5), 18 | y: self.radius * (rng.gen::() - 0.5), 19 | z: self.radius * (rng.gen::() - 0.5) 20 | }; 21 | 22 | self.position + jitter 23 | } 24 | 25 | fn color(&self) -> Vec3 { 26 | self.color 27 | } 28 | 29 | fn center(&self) -> Vec3 { 30 | self.position 31 | } 32 | 33 | fn is_point(&self) -> bool { 34 | false 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/light/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::light::Light; 2 | pub mod light; 3 | 4 | pub mod lights { 5 | pub use self::pointlight::PointLight; 6 | pub use self::spherelight::SphereLight; 7 | 8 | mod pointlight; 9 | mod spherelight; 10 | } 11 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_imports)] 2 | 3 | extern crate image; 4 | extern crate num; 5 | extern crate num_cpus; 6 | extern crate rand; 7 | extern crate rustc_serialize; 8 | extern crate threadpool; 9 | extern crate time; 10 | 11 | use std::fs::File; 12 | use std::io::{self, Read, Write}; 13 | use std::env; 14 | use std::process; 15 | use std::sync::Arc; 16 | use rustc_serialize::json; 17 | use rustc_serialize::json::DecoderError::MissingFieldError; 18 | 19 | mod geometry; 20 | mod light; 21 | mod material; 22 | mod my_scene; 23 | mod raytracer; 24 | mod scene; 25 | mod util; 26 | mod vec3; 27 | mod mat4; 28 | 29 | // Replace this with argparse eventually 30 | struct ProgramArgs { 31 | config_file: String 32 | } 33 | 34 | #[derive(RustcDecodable, RustcEncodable)] 35 | struct SceneConfig { 36 | name: String, 37 | size: (u32, u32), 38 | fov: f64, 39 | reflect_depth: u32, 40 | refract_depth: u32, 41 | shadow_samples: u32, 42 | gloss_samples: u32, 43 | pixel_samples: u32, 44 | output_file: String, 45 | animating: bool, 46 | fps: f64, 47 | time_slice: (f64, f64), 48 | starting_frame_number: u32 49 | } 50 | 51 | fn parse_args(args: env::Args) -> Result { 52 | let args = args.collect::>(); 53 | 54 | let program_name = &args[0]; 55 | match args.len() { 56 | // I wouldn't expect this in the wild 57 | 0 => panic!("Args do not even include a program name"), 58 | 2 => Ok(ProgramArgs { config_file: args[1].clone() }), 59 | _ => Err(format!("Usage: {} scene_config.json", program_name)), 60 | } 61 | } 62 | 63 | fn main() { 64 | let start_time = ::time::get_time().sec; 65 | 66 | let program_args = match parse_args(env::args()) { 67 | Ok(program_args) => program_args, 68 | Err(error_str) => { 69 | write!(&mut io::stderr(), "{}\n", error_str).unwrap(); 70 | process::exit(1) 71 | } 72 | }; 73 | let mut file_handle = match File::open(&program_args.config_file) { 74 | Ok(file) => file, 75 | Err(err) => { 76 | write!(&mut io::stderr(), "{}\n", err).unwrap(); 77 | process::exit(1) 78 | } 79 | }; 80 | 81 | let mut json_data = String::new(); 82 | if let Err(ref err) = file_handle.read_to_string(&mut json_data) { 83 | write!(&mut io::stderr(), "{}\n", err).unwrap(); 84 | process::exit(1); 85 | } 86 | 87 | let config: SceneConfig = match json::decode(&json_data) { 88 | Ok(data) => data, 89 | Err(err) => { 90 | let msg = match err { 91 | MissingFieldError(field_name) => { 92 | format!("parse failure, missing field ``{}''\n", field_name) 93 | }, 94 | _ => { 95 | format!("parse failure: {:?}", err) 96 | } 97 | }; 98 | write!(&mut io::stderr(), "{}\n", msg).unwrap(); 99 | process::exit(1) 100 | } 101 | }; 102 | 103 | println!("Job started at {}...\nLoading scene...", start_time); 104 | 105 | let scene_config = match my_scene::scene_by_name(&config.name) { 106 | Some(scene_config) => scene_config, 107 | None => { 108 | write!(&mut io::stderr(), "unknown scene ``{}''\n", config.name).unwrap(); 109 | process::exit(1) 110 | } 111 | }; 112 | 113 | let (image_width, image_height) = config.size; 114 | let fov = config.fov; 115 | 116 | // Hackish solution for animator 117 | let shared_scene = Arc::new(scene_config.get_scene()); 118 | 119 | let camera = if config.animating { 120 | scene_config.get_animation_camera(image_width, image_height, fov) 121 | } else { 122 | scene_config.get_camera(image_width, image_height, fov) 123 | }; 124 | 125 | let scene_time = ::time::get_time().sec; 126 | println!("Scene loaded at {} ({}s)...", scene_time, scene_time - start_time); 127 | 128 | let render_options = raytracer::RenderOptions { 129 | reflect_depth: config.reflect_depth, 130 | refract_depth: config.refract_depth, 131 | shadow_samples: config.shadow_samples, 132 | gloss_samples: config.gloss_samples, 133 | pixel_samples: config.pixel_samples, 134 | }; 135 | 136 | let renderer = raytracer::Renderer { 137 | options: render_options, 138 | tasks: ::num_cpus::get(), // Number of tasks to spawn. Will use up max available cores. 139 | }; 140 | 141 | if config.animating { 142 | let (animate_from, animate_to) = config.time_slice; 143 | 144 | let animator = raytracer::animator::Animator { 145 | fps: config.fps, 146 | animate_from: animate_from, 147 | animate_to: animate_to, 148 | starting_frame_number: config.starting_frame_number, 149 | renderer: renderer 150 | }; 151 | 152 | println!("Animating - tasks: {}, FPS: {}, start: {}s, end:{}s, starting frame: {}", 153 | ::num_cpus::get(), animator.fps, animator.animate_from, animator.animate_to, 154 | animator.starting_frame_number); 155 | animator.animate(camera, shared_scene, &config.output_file); 156 | let render_time = ::time::get_time().sec; 157 | println!("Render done at {} ({}s)", 158 | render_time, render_time - scene_time); 159 | } else { 160 | // Still frame 161 | println!("Rendering with {} tasks...", ::num_cpus::get()); 162 | let image_data = renderer.render(camera, shared_scene); 163 | let render_time = ::time::get_time().sec; 164 | println!("Render done at {} ({}s)...\nWriting file...", 165 | render_time, render_time - scene_time); 166 | 167 | let out_file = format!("{}{}", config.output_file, ".ppm"); 168 | util::export::to_ppm(&image_data, &out_file).expect("ppm write failure"); 169 | let export_time = ::time::get_time().sec; 170 | 171 | println!("Write done: {} ({}s). Written to {}\nTotal: {}s", 172 | export_time, export_time - render_time, 173 | config.output_file, export_time - start_time); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/mat4.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use geometry::bbox::BBox; 4 | use raytracer::Ray; 5 | use std::cmp; 6 | use std::f64; 7 | use std::f64::consts::PI; 8 | use std::fmt; 9 | use std::ops::{Add, Mul, Sub}; 10 | use vec3::Vec3; 11 | 12 | /// Stored in row-major, M_(i, j) = i-th row and j-th column 13 | /// 0-indexed 14 | #[derive(Clone, Copy)] 15 | pub struct Mat4 { 16 | pub m: [[f64; 4]; 4] 17 | } 18 | 19 | /// We store the inverse matrix for convenience as per pbrt's recommendation 20 | pub struct Transform { 21 | pub m: Mat4, 22 | pub inv: Mat4 23 | } 24 | 25 | impl Transform { 26 | pub fn new(mat: Mat4) -> Transform { 27 | Transform { 28 | m: mat, 29 | inv: mat.inverse() 30 | } 31 | } 32 | } 33 | 34 | fn are_equal_rel(a: f64, b: f64) -> bool { 35 | let mut max = a; 36 | if max < b { 37 | max = b; 38 | } 39 | (a - b).abs() <= f64::EPSILON * max 40 | } 41 | 42 | /// Most implementations adapted from pbrt 43 | impl Mat4 { 44 | pub fn new(t00: f64, t01: f64, t02: f64, t03: f64, 45 | t10: f64, t11: f64, t12: f64, t13: f64, 46 | t20: f64, t21: f64, t22: f64, t23: f64, 47 | t30: f64, t31: f64, t32: f64, t33: f64) 48 | -> Mat4 { 49 | 50 | let mut m = [[0.0, 0.0, 0.0, 0.0], 51 | [0.0, 0.0, 0.0, 0.0], 52 | [0.0, 0.0, 0.0, 0.0], 53 | [0.0, 0.0, 0.0, 0.0]]; 54 | 55 | m[0][0] = t00; 56 | m[0][1] = t01; 57 | m[0][2] = t02; 58 | m[0][3] = t03; 59 | 60 | m[1][0] = t10; 61 | m[1][1] = t11; 62 | m[1][2] = t12; 63 | m[1][3] = t13; 64 | 65 | m[2][0] = t20; 66 | m[2][1] = t21; 67 | m[2][2] = t22; 68 | m[2][3] = t23; 69 | 70 | m[3][0] = t30; 71 | m[3][1] = t31; 72 | m[3][2] = t32; 73 | m[3][3] = t33; 74 | 75 | Mat4 { m: m } 76 | } 77 | 78 | pub fn get(&self, row: usize, column: usize) -> f64 { 79 | self.m[row][column] 80 | } 81 | 82 | pub fn identity() -> Mat4 { 83 | Mat4::new( 84 | 1.0, 0.0, 0.0, 0.0, 85 | 0.0, 1.0, 0.0, 0.0, 86 | 0.0, 0.0, 1.0, 0.0, 87 | 0.0, 0.0, 0.0, 1.0 88 | ) 89 | } 90 | 91 | pub fn zero() -> Mat4 { 92 | Mat4::new( 93 | 0.0, 0.0, 0.0, 0.0, 94 | 0.0, 0.0, 0.0, 0.0, 95 | 0.0, 0.0, 0.0, 0.0, 96 | 0.0, 0.0, 0.0, 0.0 97 | ) 98 | } 99 | 100 | pub fn translate_matrix(v: &Vec3) -> Mat4 { 101 | Mat4::new( 102 | 1.0, 0.0, 0.0, v.x, 103 | 0.0, 1.0, 0.0, v.y, 104 | 0.0, 0.0, 1.0, v.z, 105 | 0.0, 0.0, 0.0, 1.0 106 | ) 107 | } 108 | 109 | pub fn scale_matrix(v: &Vec3) -> Mat4 { 110 | Mat4::new( 111 | v.x, 0.0, 0.0, 0.0, 112 | 0.0, v.y, 0.0, 0.0, 113 | 0.0, 0.0, v.z, 0.0, 114 | 0.0, 0.0, 0.0, 1.0 115 | ) 116 | } 117 | 118 | pub fn has_scale(&self) -> bool { 119 | Mat4::approx_eq(self.get(0, 0), self.get(1, 1)) && 120 | Mat4::approx_eq(self.get(0, 0), self.get(2, 2)) 121 | } 122 | 123 | /// This assumes the matrix is scalar; check has_scale(&self) -> bool before use 124 | pub fn scale(&self) -> f64 { 125 | self.m[0][0] 126 | } 127 | 128 | pub fn rotate_x_deg_matrix(angle: f64) -> Mat4 { 129 | let sin_t = Mat4::deg_to_rad(angle).sin(); 130 | let cos_t = Mat4::deg_to_rad(angle).cos(); 131 | 132 | Mat4::new( 133 | 1.0, 0.0, 0.0, 0.0, 134 | 0.0, cos_t, -sin_t, 0.0, 135 | 0.0, sin_t, cos_t, 0.0, 136 | 0.0, 0.0, 0.0, 1.0 137 | ) 138 | } 139 | 140 | pub fn rotate_y_deg_matrix(angle: f64) -> Mat4 { 141 | let sin_t = Mat4::deg_to_rad(angle).sin(); 142 | let cos_t = Mat4::deg_to_rad(angle).cos(); 143 | 144 | Mat4::new( 145 | cos_t, 0.0, sin_t, 0.0, 146 | 0.0, 1.0, 0.0, 0.0, 147 | -sin_t, 0.0, cos_t, 0.0, 148 | 0.0, 0.0, 0.0, 1.0 149 | ) 150 | } 151 | 152 | pub fn rotate_z_deg_matrix(angle: f64) -> Mat4 { 153 | let sin_t = Mat4::deg_to_rad(angle).sin(); 154 | let cos_t = Mat4::deg_to_rad(angle).cos(); 155 | 156 | Mat4::new( 157 | cos_t, -sin_t, 0.0, 0.0, 158 | sin_t, cos_t, 0.0, 0.0, 159 | 0.0, 0.0, 1.0, 0.0, 160 | 0.0, 0.0, 0.0, 1.0 161 | ) 162 | } 163 | 164 | pub fn rotate_axis_deg_matrix(angle: f64, axis: &Vec3) -> Mat4 { 165 | let a = axis.unit(); 166 | let s = Mat4::deg_to_rad(angle).sin(); 167 | let c = Mat4::deg_to_rad(angle).cos(); 168 | 169 | let mut m = [[0.0, 0.0, 0.0, 0.0], 170 | [0.0, 0.0, 0.0, 0.0], 171 | [0.0, 0.0, 0.0, 0.0], 172 | [0.0, 0.0, 0.0, 0.0]]; 173 | 174 | m[0][0] = a.x * a.x + (1.0 - a.x * a.x) * c; 175 | m[0][1] = a.x * a.y * (1.0 - c) - a.z * s; 176 | m[0][2] = a.x * a.z * (1.0 - c) + a.y * s; 177 | m[0][3] = 0.0; 178 | 179 | m[1][0] = a.x * a.y * (1.0 - c) + a.z * s; 180 | m[1][1] = a.y * a.y + (1.0 - a.y * a.y) * c; 181 | m[1][2] = a.y * a.z * (1.0 - c) - a.x * s; 182 | m[1][3] = 0.0; 183 | 184 | m[2][0] = a.x * a.z * (1.0 - c) - a.y * s; 185 | m[2][1] = a.y * a.z * (1.0 - c) + a.x * s; 186 | m[2][2] = a.z * a.z + (1.0 - a.z * a.z) * c; 187 | m[2][3] = 0.0; 188 | 189 | m[3][0] = 0.0; 190 | m[3][1] = 0.0; 191 | m[3][2] = 0.0; 192 | m[3][3] = 1.0; 193 | 194 | Mat4 { m: m } 195 | } 196 | 197 | /// This matrix translates between world-space and camera-space 198 | pub fn look_at_matrix(pos: &Vec3, up: &Vec3, look_at: &Vec3) -> Mat4 { 199 | let dir = (*look_at - *pos).unit(); 200 | let left = (up.unit().cross(&dir)).unit(); 201 | let new_up = dir.cross(&left); 202 | 203 | Mat4::new( 204 | left.x, new_up.x, dir.x, pos.x, 205 | left.y, new_up.y, dir.y, pos.y, 206 | left.z, new_up.z, dir.z, pos.z, 207 | 0.0, 0.0, 0.0, 1.0 208 | ) 209 | } 210 | 211 | pub fn transpose(&self) -> Mat4 { 212 | Mat4::new( 213 | self.m[0][0], self.m[1][0], self.m[2][0], self.m[3][0], 214 | self.m[0][1], self.m[1][1], self.m[2][1], self.m[3][1], 215 | self.m[0][2], self.m[1][2], self.m[2][2], self.m[3][2], 216 | self.m[0][3], self.m[1][3], self.m[2][3], self.m[3][3] 217 | ) 218 | } 219 | 220 | /// Normals cannot have the transformation matrix directly applied to them 221 | pub fn transform_normal(n: &Vec3, transform: &Mat4) -> Vec3 { 222 | let inv = transform.inverse(); 223 | 224 | Vec3 { 225 | x: inv.m[0][0] * n.x + inv.m[1][0] * n.y + inv.m[2][0] * n.z, 226 | y: inv.m[0][1] * n.x + inv.m[1][1] * n.y + inv.m[2][1] * n.z, 227 | z: inv.m[0][2] * n.x + inv.m[1][2] * n.y + inv.m[2][2] * n.z 228 | } 229 | } 230 | 231 | pub fn transform_ray(_r: &Ray) -> Ray { 232 | panic!("Ray transform not implemented"); 233 | } 234 | 235 | pub fn transform_bbox(_bbox: &BBox) -> BBox { 236 | panic!("BBox transform not implemented"); 237 | } 238 | 239 | pub fn inverse(&self) -> Mat4 { 240 | let s0 = self.m[0][0] * self.m[1][1] - self.m[1][0] * self.m[0][1]; 241 | let s1 = self.m[0][0] * self.m[1][2] - self.m[1][0] * self.m[0][2]; 242 | let s2 = self.m[0][0] * self.m[1][3] - self.m[1][0] * self.m[0][3]; 243 | let s3 = self.m[0][1] * self.m[1][2] - self.m[1][1] * self.m[0][2]; 244 | let s4 = self.m[0][1] * self.m[1][3] - self.m[1][1] * self.m[0][3]; 245 | let s5 = self.m[0][2] * self.m[1][3] - self.m[1][2] * self.m[0][3]; 246 | 247 | let c5 = self.m[2][2] * self.m[3][3] - self.m[3][2] * self.m[2][3]; 248 | let c4 = self.m[2][1] * self.m[3][3] - self.m[3][1] * self.m[2][3]; 249 | let c3 = self.m[2][1] * self.m[3][2] - self.m[3][1] * self.m[2][2]; 250 | let c2 = self.m[2][0] * self.m[3][3] - self.m[3][0] * self.m[2][3]; 251 | let c1 = self.m[2][0] * self.m[3][2] - self.m[3][0] * self.m[2][2]; 252 | let c0 = self.m[2][0] * self.m[3][1] - self.m[3][0] * self.m[2][1]; 253 | 254 | let invdet = 1.0 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0); 255 | 256 | let mut m = [[0.0, 0.0, 0.0, 0.0], 257 | [0.0, 0.0, 0.0, 0.0], 258 | [0.0, 0.0, 0.0, 0.0], 259 | [0.0, 0.0, 0.0, 0.0]]; 260 | 261 | let a = self.m; 262 | 263 | m[0][0] = ( a[1][1] * c5 - a[1][2] * c4 + a[1][3] * c3) * invdet; 264 | m[0][1] = (-a[0][1] * c5 + a[0][2] * c4 - a[0][3] * c3) * invdet; 265 | m[0][2] = ( a[3][1] * s5 - a[3][2] * s4 + a[3][3] * s3) * invdet; 266 | m[0][3] = (-a[2][1] * s5 + a[2][2] * s4 - a[2][3] * s3) * invdet; 267 | 268 | m[1][0] = (-a[1][0] * c5 + a[1][2] * c2 - a[1][3] * c1) * invdet; 269 | m[1][1] = ( a[0][0] * c5 - a[0][2] * c2 + a[0][3] * c1) * invdet; 270 | m[1][2] = (-a[3][0] * s5 + a[3][2] * s2 - a[3][3] * s1) * invdet; 271 | m[1][3] = ( a[2][0] * s5 - a[2][2] * s2 + a[2][3] * s1) * invdet; 272 | 273 | m[2][0] = ( a[1][0] * c4 - a[1][1] * c2 + a[1][3] * c0) * invdet; 274 | m[2][1] = (-a[0][0] * c4 + a[0][1] * c2 - a[0][3] * c0) * invdet; 275 | m[2][2] = ( a[3][0] * s4 - a[3][1] * s2 + a[3][3] * s0) * invdet; 276 | m[2][3] = (-a[2][0] * s4 + a[2][1] * s2 - a[2][3] * s0) * invdet; 277 | 278 | m[3][0] = (-a[1][0] * c3 + a[1][1] * c1 - a[1][2] * c0) * invdet; 279 | m[3][1] = ( a[0][0] * c3 - a[0][1] * c1 + a[0][2] * c0) * invdet; 280 | m[3][2] = (-a[3][0] * s3 + a[3][1] * s1 - a[3][2] * s0) * invdet; 281 | m[3][3] = ( a[2][0] * s3 - a[2][1] * s1 + a[2][2] * s0) * invdet; 282 | 283 | Mat4 { m: m } 284 | } 285 | 286 | /// http://csherratt.github.io/csherratt/blog/2013/11/24/matrix-multiply-in-rust/ 287 | /// Note: This is the slow, unoptimised version! 288 | pub fn mult_m(a: &Mat4, b: &Mat4) -> Mat4 { 289 | let mut out = Mat4 { 290 | m: [[0.0, 0.0, 0.0, 0.0], 291 | [0.0, 0.0, 0.0, 0.0], 292 | [0.0, 0.0, 0.0, 0.0], 293 | [0.0, 0.0, 0.0, 0.0]] 294 | }; 295 | 296 | for i in 0usize..4 { 297 | for j in 0usize..4 { 298 | for k in 0usize..4 { 299 | out.m[i][j] += a.m[i][k] * b.m[k][j]; 300 | } 301 | } 302 | } 303 | 304 | out 305 | } 306 | 307 | pub fn mult_v(m: &Mat4, v: &Vec3) -> Vec3 { 308 | Vec3 { 309 | x: m.m[0][0] * v.x + m.m[0][1] * v.y + m.m[0][2] * v.z, 310 | y: m.m[1][0] * v.x + m.m[1][1] * v.y + m.m[1][2] * v.z, 311 | z: m.m[2][0] * v.x + m.m[2][1] * v.y + m.m[2][2] * v.z 312 | } 313 | } 314 | 315 | pub fn mult_p(m: &Mat4, p: &Vec3) -> Vec3 { 316 | let xp = m.m[0][0] * p.x + m.m[0][1] * p.y + m.m[0][2] * p.z + m.m[0][3]; 317 | let yp = m.m[1][0] * p.x + m.m[1][1] * p.y + m.m[1][2] * p.z + m.m[1][3]; 318 | let zp = m.m[2][0] * p.x + m.m[2][1] * p.y + m.m[2][2] * p.z + m.m[2][3]; 319 | let wp = m.m[3][0] * p.x + m.m[3][1] * p.y + m.m[3][2] * p.z + m.m[3][3]; 320 | 321 | if are_equal_rel(wp, 1.0) { 322 | // Optimisation, wp == 1.0 is common 323 | Vec3 { 324 | x: xp, 325 | y: yp, 326 | z: zp 327 | } 328 | } else { 329 | // Perspective division 330 | Vec3 { 331 | x: xp / wp, 332 | y: yp / wp, 333 | z: zp / wp 334 | } 335 | } 336 | } 337 | 338 | fn approx_eq(f1: f64, f2: f64) -> bool { 339 | (f1 - f2).abs() < ::std::f64::EPSILON 340 | } 341 | 342 | fn deg_to_rad(deg: f64) -> f64 { 343 | deg * PI / 180.0 344 | } 345 | } 346 | 347 | impl cmp::PartialEq for Mat4 { 348 | fn eq(&self, other: &Mat4) -> bool { 349 | self.m[0][0] == other.m[0][0] && 350 | self.m[0][1] == other.m[0][1] && 351 | self.m[0][2] == other.m[0][2] && 352 | self.m[0][3] == other.m[0][3] && 353 | 354 | self.m[1][0] == other.m[1][0] && 355 | self.m[1][1] == other.m[1][1] && 356 | self.m[1][2] == other.m[1][2] && 357 | self.m[1][3] == other.m[1][3] && 358 | 359 | self.m[2][0] == other.m[2][0] && 360 | self.m[2][1] == other.m[2][1] && 361 | self.m[2][2] == other.m[2][2] && 362 | self.m[2][3] == other.m[2][3] && 363 | 364 | self.m[3][0] == other.m[3][0] && 365 | self.m[3][1] == other.m[3][1] && 366 | self.m[3][2] == other.m[3][2] && 367 | self.m[3][3] == other.m[3][3] 368 | } 369 | } 370 | 371 | impl Add for Mat4 { 372 | type Output = Mat4; 373 | 374 | fn add(self, other: Mat4) -> Mat4 { 375 | let mut out = Mat4 { 376 | m: [[0.0, 0.0, 0.0, 0.0], 377 | [0.0, 0.0, 0.0, 0.0], 378 | [0.0, 0.0, 0.0, 0.0], 379 | [0.0, 0.0, 0.0, 0.0]] 380 | }; 381 | 382 | out.m[0][0] = self.m[0][0] + other.m[0][0]; 383 | out.m[0][1] = self.m[0][1] + other.m[0][1]; 384 | out.m[0][2] = self.m[0][2] + other.m[0][2]; 385 | out.m[0][3] = self.m[0][3] + other.m[0][3]; 386 | 387 | out.m[1][0] = self.m[1][0] + other.m[1][0]; 388 | out.m[1][1] = self.m[1][1] + other.m[1][1]; 389 | out.m[1][2] = self.m[1][2] + other.m[1][2]; 390 | out.m[1][3] = self.m[1][3] + other.m[1][3]; 391 | 392 | out.m[2][0] = self.m[2][0] + other.m[2][0]; 393 | out.m[2][1] = self.m[2][1] + other.m[2][1]; 394 | out.m[2][2] = self.m[2][2] + other.m[2][2]; 395 | out.m[2][3] = self.m[2][3] + other.m[2][3]; 396 | 397 | out.m[3][0] = self.m[3][0] + other.m[3][0]; 398 | out.m[3][1] = self.m[3][1] + other.m[3][1]; 399 | out.m[3][2] = self.m[3][2] + other.m[3][2]; 400 | out.m[3][3] = self.m[3][3] + other.m[3][3]; 401 | 402 | out 403 | } 404 | } 405 | 406 | impl Sub for Mat4 { 407 | type Output = Mat4; 408 | 409 | fn sub(self, other: Mat4) -> Mat4 { 410 | let mut out = Mat4 { 411 | m: [[0.0, 0.0, 0.0, 0.0], 412 | [0.0, 0.0, 0.0, 0.0], 413 | [0.0, 0.0, 0.0, 0.0], 414 | [0.0, 0.0, 0.0, 0.0]] 415 | }; 416 | 417 | out.m[0][0] = self.m[0][0] - other.m[0][0]; 418 | out.m[0][1] = self.m[0][1] - other.m[0][1]; 419 | out.m[0][2] = self.m[0][2] - other.m[0][2]; 420 | out.m[0][3] = self.m[0][3] - other.m[0][3]; 421 | 422 | out.m[1][0] = self.m[1][0] - other.m[1][0]; 423 | out.m[1][1] = self.m[1][1] - other.m[1][1]; 424 | out.m[1][2] = self.m[1][2] - other.m[1][2]; 425 | out.m[1][3] = self.m[1][3] - other.m[1][3]; 426 | 427 | out.m[2][0] = self.m[2][0] - other.m[2][0]; 428 | out.m[2][1] = self.m[2][1] - other.m[2][1]; 429 | out.m[2][2] = self.m[2][2] - other.m[2][2]; 430 | out.m[2][3] = self.m[2][3] - other.m[2][3]; 431 | 432 | out.m[3][0] = self.m[3][0] - other.m[3][0]; 433 | out.m[3][1] = self.m[3][1] - other.m[3][1]; 434 | out.m[3][2] = self.m[3][2] - other.m[3][2]; 435 | out.m[3][3] = self.m[3][3] - other.m[3][3]; 436 | 437 | out 438 | } 439 | } 440 | 441 | impl Mul for Mat4 { 442 | type Output = Mat4; 443 | 444 | fn mul(self, other: Mat4) -> Mat4 { 445 | Mat4::mult_m(&self, &other) 446 | } 447 | } 448 | 449 | impl fmt::Debug for Mat4 { 450 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 451 | // 46 spaces in between 452 | write!(f, 453 | "\n┌ ┐\n\ 454 | │{: >10.3}, {: >10.3}, {: >10.3}, {: >10.3}│\n\ 455 | │{: >10.3}, {: >10.3}, {: >10.3}, {: >10.3}│\n\ 456 | │{: >10.3}, {: >10.3}, {: >10.3}, {: >10.3}│\n\ 457 | │{: >10.3}, {: >10.3}, {: >10.3}, {: >10.3}│\n\ 458 | └ ┘\n", 459 | self.m[0][0], self.m[0][1], self.m[0][2], self.m[0][3], 460 | self.m[1][0], self.m[1][1], self.m[1][2], self.m[1][3], 461 | self.m[2][0], self.m[2][1], self.m[2][2], self.m[2][3], 462 | self.m[3][0], self.m[3][1], self.m[3][2], self.m[3][3] 463 | ) 464 | } 465 | } 466 | 467 | #[test] 468 | fn test_add() { 469 | let m = Mat4::new( 470 | 1.0, 2.0, 3.0, 4.0, 471 | 5.0, 6.0, 7.0, 8.0, 472 | 9.0, 10.0, 11.0, 12.0, 473 | 13.0, 14.0, 15.0, 16.0 474 | ); 475 | 476 | let expected = Mat4::new( 477 | 2.0, 4.0, 6.0, 8.0, 478 | 10.0, 12.0, 14.0, 16.0, 479 | 18.0, 20.0, 22.0, 24.0, 480 | 26.0, 28.0, 30.0, 32.0 481 | ); 482 | 483 | assert_eq!(m + m, expected); 484 | } 485 | 486 | #[test] 487 | fn test_sub() { 488 | let m = Mat4::new( 489 | 1.0, 2.0, 3.0, 4.0, 490 | 5.0, 6.0, 7.0, 8.0, 491 | 9.0, 10.0, 11.0, 12.0, 492 | 13.0, 14.0, 15.0, 16.0 493 | ); 494 | 495 | assert_eq!(m - m, Mat4::zero()); 496 | } 497 | 498 | #[test] 499 | fn test_mul() { 500 | let a = Mat4::new( 501 | 1.0, 3.0, 5.0, 7.0, 502 | 11.0, 13.0, 17.0, 23.0, 503 | 29.0, 31.0, 37.0, 41.0, 504 | 43.0, 47.0, 53.0, 59.0 505 | ); 506 | 507 | let b = Mat4::new( 508 | 1.0, 2.0, 3.0, 4.0, 509 | 5.0, 6.0, 7.0, 8.0, 510 | 9.0, 10.0, 11.0, 12.0, 511 | 13.0, 14.0, 15.0, 16.0 512 | ); 513 | 514 | let expected = Mat4::new( 515 | 152.0, 168.0, 184.0, 200.0, 516 | 528.0, 592.0, 656.0, 720.0, 517 | 1050.0, 1188.0, 1326.0, 1464.0, 518 | 1522.0, 1724.0, 1926.0, 2128.0 519 | ); 520 | 521 | let out = Mat4::mult_m(&a, &b); 522 | assert_eq!(out, expected); 523 | } 524 | 525 | #[test] 526 | fn test_equality() { 527 | let i1 = Mat4::identity(); 528 | let i2 = Mat4::identity(); 529 | let zero = Mat4::zero(); 530 | 531 | assert!(i1 == i2); 532 | assert!(i1 != zero); 533 | } 534 | 535 | #[test] 536 | fn test_inverse() { 537 | let i = Mat4::identity(); 538 | assert_eq!(i, i.inverse()); 539 | 540 | let m = Mat4::new( 541 | 1.0, 0.0, 1.0, 1.0, 542 | 2.0, 0.0, 1.0, 0.0, 543 | 2.0, 1.0, 1.0, 0.0, 544 | 0.0, 0.0, 1.0, 3.0 545 | ); 546 | 547 | let m_inverse = Mat4::new( 548 | -3.0, 2.0, 0.0, 1.0, 549 | 0.0, -1.0, 1.0, 0.0, 550 | 6.0, -3.0, 0.0, -2.0, 551 | -2.0, 1.0, 0.0, 1.0 552 | ); 553 | 554 | assert_eq!(m.inverse(), m_inverse); 555 | } 556 | 557 | #[test] 558 | fn test_transpose() { 559 | let m = Mat4::new( 560 | 1.0, 2.0, 3.0, 4.0, 561 | 5.0, 6.0, 7.0, 8.0, 562 | 9.0, 10.0, 11.0, 12.0, 563 | 13.0, 14.0, 15.0, 16.0 564 | ); 565 | 566 | let mt = Mat4::new( 567 | 1.0, 5.0, 9.0, 13.0, 568 | 2.0, 6.0, 10.0, 14.0, 569 | 3.0, 7.0, 11.0, 15.0, 570 | 4.0, 8.0, 12.0, 16.0 571 | ); 572 | 573 | assert!(m.transpose() == mt); 574 | } 575 | 576 | #[test] 577 | fn test_mul_with_vec() { 578 | let m = Mat4::new( 579 | 1.0, 2.0, 3.0, 4.0, 580 | 5.0, 6.0, 7.0, 8.0, 581 | 9.0, 10.0, 11.0, 12.0, 582 | 13.0, 14.0, 15.0, 16.0 583 | ); 584 | 585 | let v = Vec3 { 586 | x: 1.0, 587 | y: 2.0, 588 | z: 3.0 589 | }; 590 | 591 | let expected_w0 = Vec3 { 592 | x: 1.0 * 1.0 + 2.0 * 2.0 + 3.0 * 3.0, 593 | y: 5.0 * 1.0 + 6.0 * 2.0 + 7.0 * 3.0, 594 | z: 9.0 * 1.0 + 10.0 * 2.0 + 11.0 * 3.0 595 | }; 596 | 597 | let multiplied_w0 = Mat4::mult_v(&m, &v); 598 | assert_eq!(multiplied_w0, expected_w0); 599 | } 600 | -------------------------------------------------------------------------------- /src/material/material.rs: -------------------------------------------------------------------------------- 1 | use vec3::Vec3; 2 | 3 | /// TODO: Move specular/transmissive properties into traits 4 | pub trait Material { 5 | fn sample(&self, n: Vec3, i: Vec3, l: Vec3, u: f64, v: f64) -> Vec3; 6 | fn is_reflective(&self) -> bool; 7 | fn is_refractive(&self) -> bool; 8 | fn global_specular(&self, color: &Vec3) -> Vec3; 9 | fn global_transmissive(&self, color: &Vec3) -> Vec3; 10 | fn transmission(&self) -> Vec3; 11 | fn ior(&self) -> f64; 12 | fn is_glossy(&self) -> bool; 13 | fn glossiness(&self) -> f64; 14 | } 15 | -------------------------------------------------------------------------------- /src/material/materials/cooktorrancematerial.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts::PI; 2 | use material::{Material, Texture}; 3 | use raytracer::compositor::ColorRGBA; 4 | use vec3::Vec3; 5 | 6 | #[allow(dead_code)] 7 | #[derive(Clone)] 8 | pub struct CookTorranceMaterial { 9 | pub k_a: f64, // Ambient coefficient 10 | pub k_d: f64, // Diffuse coefficient 11 | pub k_s: f64, // Local specular coefficient 12 | pub k_sg: f64, // Global specular coefficient (mirror reflection) 13 | pub k_tg: f64, // Global transmissive coefficient (refraction) 14 | pub ambient: Vec3, // Ambient color 15 | pub diffuse: Vec3, // Diffuse color 16 | pub transmission: Vec3, // Transmissive color 17 | pub specular: Vec3, // Specular color 18 | pub roughness: f64, // Smaller = shininer => smaller highlight spot on surface 19 | pub glossiness: f64, // How glossy reflections are. 0 for non-glossy surfaces. 20 | pub gauss_constant: f64, // Controls curve of distribution of microfacets 21 | pub ior: f64, // Index of refraction, also used for specular highlights 22 | pub diffuse_texture: Option> 23 | } 24 | 25 | impl Material for CookTorranceMaterial { 26 | fn sample(&self, n: Vec3, i: Vec3, l: Vec3, u: f64, v: f64) -> Vec3 { 27 | let ambient = self.ambient.scale(self.k_a); 28 | let diffuse = self.diffuse.scale(self.k_d).scale(n.dot(&l)) * match self.diffuse_texture { 29 | Some(ref x) => x.color(u, v), 30 | None => ColorRGBA::white() 31 | }.to_vec3(); 32 | 33 | // Specular calculations 34 | let h = (l + i).unit(); 35 | let n_dot_h = n.dot(&h); 36 | let n_dot_l = n.dot(&l); 37 | let v_dot_h = i.dot(&h); 38 | let n_dot_v = n.dot(&i); 39 | 40 | // Fresnel term (Schlick's approximation) 41 | let n1 = 1.0; 42 | let n2 = self.ior; 43 | let f0 = ((n1 - n2) / (n1 + n2)).powf(2.0); 44 | let f = (1.0 - v_dot_h).powf(5.0) * (1.0 - f0) + f0; 45 | 46 | // Microfacet distribution 47 | let alpha = n_dot_h.acos(); 48 | let d = self.gauss_constant * (-alpha / self.roughness.sqrt()).exp(); 49 | 50 | // Geometric attenuation factor 51 | let g1 = (2.0 * n_dot_h * n_dot_v) / v_dot_h; 52 | let g2 = (2.0 * n_dot_h * n_dot_l) / v_dot_h; 53 | let g = g1.min(g2); 54 | 55 | let brdf = f * d * g / (n_dot_v * n_dot_l * PI); 56 | 57 | self.specular.scale(self.k_s * brdf) + diffuse + ambient 58 | } 59 | 60 | fn is_reflective(&self) -> bool { 61 | self.k_sg > 0.0 62 | } 63 | 64 | fn is_refractive(&self) -> bool { 65 | self.k_tg > 0.0 66 | } 67 | 68 | fn global_specular(&self, color: &Vec3) -> Vec3 { 69 | color.scale(self.k_sg) 70 | } 71 | 72 | fn global_transmissive(&self, color: &Vec3) -> Vec3 { 73 | color.scale(self.k_tg) 74 | } 75 | 76 | fn transmission(&self) -> Vec3 { 77 | self.transmission 78 | } 79 | 80 | fn ior(&self) -> f64 { 81 | self.ior 82 | } 83 | 84 | fn is_glossy(&self) -> bool { 85 | self.glossiness > ::std::f64::EPSILON 86 | } 87 | 88 | fn glossiness(&self) -> f64 { 89 | self.glossiness 90 | } 91 | } 92 | 93 | impl Default for CookTorranceMaterial { 94 | fn default() -> CookTorranceMaterial { 95 | CookTorranceMaterial { 96 | k_a: 0.0, 97 | k_d: 1.0, 98 | k_s: 1.0, 99 | k_sg: 0.0, 100 | k_tg: 0.0, 101 | gauss_constant: 1.0, 102 | roughness: 0.15, 103 | glossiness: 0.0, 104 | ior: 1.5, 105 | ambient: Vec3::one(), 106 | diffuse: Vec3 { x: 0.5, y: 0.5, z: 0.5 }, 107 | specular: Vec3::one(), 108 | transmission: Vec3::zero(), 109 | diffuse_texture: None 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/material/materials/flatmaterial.rs: -------------------------------------------------------------------------------- 1 | use material::Material; 2 | use vec3::Vec3; 3 | 4 | #[allow(dead_code)] 5 | #[derive(Clone)] 6 | pub struct FlatMaterial { 7 | pub color: Vec3 8 | } 9 | 10 | impl Material for FlatMaterial { 11 | fn sample(&self, _n: Vec3, _i: Vec3, _l: Vec3, _u: f64, _v: f64) -> Vec3 { 12 | self.color 13 | } 14 | 15 | fn is_reflective(&self) -> bool { 16 | false 17 | } 18 | 19 | fn is_refractive(&self) -> bool { 20 | false 21 | } 22 | 23 | fn global_specular(&self, _color: &Vec3) -> Vec3 { 24 | Vec3::zero() 25 | } 26 | 27 | fn global_transmissive(&self, _color: &Vec3) -> Vec3 { 28 | Vec3::zero() 29 | } 30 | 31 | fn transmission(&self) -> Vec3 { 32 | Vec3::zero() 33 | } 34 | 35 | fn ior(&self) -> f64 { 36 | 1.0 37 | } 38 | 39 | fn is_glossy(&self) -> bool { 40 | false 41 | } 42 | 43 | fn glossiness(&self) -> f64 { 44 | 0.0 45 | } 46 | } 47 | 48 | impl Default for FlatMaterial { 49 | fn default() -> FlatMaterial { 50 | FlatMaterial { color: Vec3 { x: 0.5, y: 0.5, z: 0.5 } } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/material/materials/phongmaterial.rs: -------------------------------------------------------------------------------- 1 | use material::{Material, Texture}; 2 | use raytracer::compositor::ColorRGBA; 3 | use vec3::Vec3; 4 | 5 | #[allow(dead_code)] 6 | #[derive(Clone)] 7 | pub struct PhongMaterial { 8 | pub k_a: f64, // Ambient coefficient 9 | pub k_d: f64, // Diffuse coefficient 10 | pub k_s: f64, // Local specular coefficient 11 | pub k_sg: f64, // Global specular coefficient (mirror reflection) 12 | pub k_tg: f64, // Global transmissive coefficient (refraction) 13 | pub ambient: Vec3, // Ambient color 14 | pub diffuse: Vec3, // Diffuse color 15 | pub transmission: Vec3, // Transmissive color 16 | pub specular: Vec3, // Specular color 17 | pub shininess: f64, // Size of Phong specular highlight 18 | pub glossiness: f64, // How glossy reflections are. 0 for non-glossy surfaces. 19 | pub ior: f64, // Index of refraction 20 | pub diffuse_texture: Option> 21 | } 22 | 23 | impl Material for PhongMaterial { 24 | fn sample(&self, n: Vec3, i: Vec3, l: Vec3, u: f64, v: f64) -> Vec3 { 25 | let h = (l + i).unit(); 26 | 27 | // Blinn-Phong approximation 28 | let ambient = self.ambient.scale(self.k_a); 29 | let diffuse = self.diffuse.scale(self.k_d).scale(n.dot(&l)) * match self.diffuse_texture { 30 | Some(ref x) => x.color(u, v), 31 | None => ColorRGBA::white() 32 | }.to_vec3(); 33 | let specular = self.specular.scale(self.k_s).scale(n.dot(&h).powf(self.shininess)); 34 | 35 | ambient + diffuse + specular 36 | } 37 | 38 | fn is_reflective(&self) -> bool { 39 | self.k_sg > 0.0 40 | } 41 | 42 | fn is_refractive(&self) -> bool { 43 | self.k_tg > 0.0 44 | } 45 | 46 | fn global_specular(&self, color: &Vec3) -> Vec3 { 47 | color.scale(self.k_sg) 48 | } 49 | 50 | fn global_transmissive(&self, color: &Vec3) -> Vec3 { 51 | color.scale(self.k_tg) 52 | } 53 | 54 | fn transmission(&self) -> Vec3 { 55 | self.transmission 56 | } 57 | 58 | fn ior(&self) -> f64 { 59 | self.ior 60 | } 61 | 62 | fn is_glossy(&self) -> bool { 63 | self.glossiness > ::std::f64::EPSILON 64 | } 65 | 66 | fn glossiness(&self) -> f64 { 67 | self.glossiness 68 | } 69 | } 70 | 71 | impl Default for PhongMaterial { 72 | fn default() -> PhongMaterial { 73 | PhongMaterial { 74 | k_a: 0.0, 75 | k_d: 1.0, 76 | k_s: 1.0, 77 | k_sg: 0.0, 78 | k_tg: 0.0, 79 | shininess: 10.0, 80 | glossiness: 0.0, 81 | ior: 1.0, 82 | ambient: Vec3::one(), 83 | diffuse: Vec3 { x: 0.5, y: 0.5, z: 0.5 }, 84 | specular: Vec3::one(), 85 | transmission: Vec3::zero(), 86 | diffuse_texture: None 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/material/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::material::Material; 2 | pub use self::texture::Texture; 3 | pub mod material; 4 | pub mod texture; 5 | 6 | pub mod materials { 7 | pub use self::cooktorrancematerial::CookTorranceMaterial; 8 | pub use self::flatmaterial::FlatMaterial; 9 | pub use self::phongmaterial::PhongMaterial; 10 | 11 | mod cooktorrancematerial; 12 | mod flatmaterial; 13 | mod phongmaterial; 14 | } 15 | 16 | pub mod textures { 17 | pub use self::checkertexture::CheckerTexture; 18 | pub use self::uvtexture::UVTexture; 19 | pub use self::imagetexture::ImageTexture; 20 | pub use self::cubemap::CubeMap; 21 | 22 | mod checkertexture; 23 | mod uvtexture; 24 | mod imagetexture; 25 | mod cubemap; 26 | } 27 | -------------------------------------------------------------------------------- /src/material/texture.rs: -------------------------------------------------------------------------------- 1 | use raytracer::compositor::ColorRGBA; 2 | 3 | pub trait Texture { 4 | fn color(&self, u: f64, v: f64) -> ColorRGBA; 5 | fn clone_self(&self) -> Box; 6 | } 7 | 8 | impl Clone for Box { 9 | fn clone(&self) -> Box { 10 | self.clone_self() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/material/textures/checkertexture.rs: -------------------------------------------------------------------------------- 1 | use material::Texture; 2 | use raytracer::compositor::ColorRGBA; 3 | 4 | 5 | #[derive(Clone)] 6 | pub struct CheckerTexture { 7 | pub color1: ColorRGBA, 8 | pub color2: ColorRGBA, 9 | pub scale: f64 // Controls how large the squares are. 10 | } 11 | 12 | impl Texture for CheckerTexture { 13 | fn color(&self, u: f64, v: f64) -> ColorRGBA { 14 | let s = (u % self.scale).abs(); 15 | let t = (v % self.scale).abs(); 16 | let half = self.scale / 2.0; 17 | 18 | if s > half && t < half || s < half && t > half { 19 | self.color1 20 | } else { 21 | self.color2 22 | } 23 | } 24 | 25 | fn clone_self(&self) -> Box { 26 | Box::new(CheckerTexture { 27 | color1: self.color1, 28 | color2: self.color2, 29 | scale: self.scale 30 | }) as Box 31 | } 32 | } 33 | 34 | impl CheckerTexture { 35 | #[allow(dead_code)] 36 | pub fn black_and_white(scale: f64) -> CheckerTexture { 37 | CheckerTexture { 38 | color1: ColorRGBA::::black(), 39 | color2: ColorRGBA::::white(), 40 | scale: scale 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/material/textures/cubemap.rs: -------------------------------------------------------------------------------- 1 | use material::textures::ImageTexture; 2 | use std::sync::mpsc::channel; 3 | use std::thread; 4 | use vec3::Vec3; 5 | 6 | #[allow(dead_code)] 7 | pub struct CubeMap { 8 | pub faces: Vec 9 | } 10 | 11 | impl CubeMap { 12 | /// For y-axis as up, load: left, right, down, up, front, back 13 | #[allow(dead_code)] 14 | pub fn load(x: &str, x_neg: &str, y: &str, y_neg: &str, z: &str, z_neg: &str) -> CubeMap { 15 | let filenames = vec![ 16 | x, x_neg, 17 | y, y_neg, 18 | z, z_neg, 19 | ]; 20 | 21 | let mut faces: Vec = Vec::with_capacity(6); 22 | unsafe { faces.set_len(6); } 23 | 24 | let (tx, rx) = channel(); 25 | 26 | for (i, filename) in filenames.iter().take(6).enumerate() { 27 | let task_tx = tx.clone(); 28 | let filename = filename.to_string(); 29 | 30 | thread::spawn(move || { 31 | task_tx.send((i, ImageTexture::load(&filename))).unwrap(); 32 | }); 33 | } 34 | drop(tx); 35 | 36 | for (i, tex) in rx { 37 | let p = faces.as_mut_ptr(); 38 | unsafe { ::std::ptr::write::(p.offset(i as isize), tex); } 39 | } 40 | 41 | CubeMap { faces: faces } 42 | } 43 | 44 | #[allow(dead_code)] 45 | pub fn color(&self, dir: Vec3) -> Vec3 { 46 | let x_mag = dir.x.abs(); 47 | let y_mag = dir.y.abs(); 48 | let z_mag = dir.z.abs(); 49 | 50 | let mut face = !0; 51 | let mut s = 0.0; 52 | let mut t = 0.0; 53 | 54 | if x_mag >= y_mag && x_mag >= z_mag { 55 | // +x -x direction 56 | face = if dir.x <= 0.0 { 0 } else { 1 }; 57 | let scale = if dir.x < 0.0 { 1.0 } else { -1.0 }; 58 | s = scale * dir.z / dir.x.abs(); 59 | t = dir.y / dir.x.abs(); 60 | } else if y_mag >= x_mag && y_mag >= z_mag { 61 | // +y -y direction 62 | face = if dir.y <= 0.0 { 2 } else { 3 }; 63 | let scale = if dir.y < 0.0 { 1.0 } else { -1.0 }; 64 | s = scale * dir.x / dir.y.abs(); 65 | t = dir.z / dir.y.abs(); 66 | } else if z_mag >= y_mag && z_mag >= x_mag { 67 | // +z -z direction 68 | face = if dir.z <= 0.0 { 4 } else { 5 }; 69 | let scale = if dir.z < 0.0 { -1.0 } else { 1.0 }; 70 | s = scale * dir.x / dir.z.abs(); 71 | t = dir.y / dir.z.abs(); 72 | } 73 | 74 | // [-1..1] -> [0..1] 75 | let seam_delta = 0.0001; 76 | s = (1.0 - (s * 0.5 + 0.5)).max(seam_delta).min(1.0 - seam_delta); 77 | t = (1.0 - (t * 0.5 + 0.5)).max(seam_delta).min(1.0 - seam_delta); 78 | 79 | if face == !0 { 80 | panic!("CubeMap could not get a cube face for direction {} {} {}", dir.x, dir.y, dir.z); 81 | } 82 | 83 | self.faces[face].sample(s, t) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/material/textures/imagetexture.rs: -------------------------------------------------------------------------------- 1 | use vec3::Vec3; 2 | use material::Texture; 3 | use raytracer::compositor::{Surface, ColorRGBA}; 4 | 5 | /// Maps the supplied (u, v) coordinate to the image (s, t). 6 | #[derive(Clone)] 7 | pub struct ImageTexture { 8 | pub image: Surface 9 | } 10 | 11 | impl ImageTexture { 12 | #[allow(dead_code)] 13 | pub fn load(filename: &str) -> ImageTexture { 14 | ImageTexture { image: ::util::import::from_image(filename).unwrap() } 15 | } 16 | 17 | // Alias, used by skybox sampling. This is needed because we aren't storing the skybox 18 | // ImageTextures as a more generic Texture (vec of objects with the Texture trait). 19 | // An ImageTexture-specific function needs to exist to be called. 20 | pub fn sample(&self, u: f64, v: f64) -> Vec3 { 21 | self.color(u, v).to_vec3() 22 | } 23 | } 24 | 25 | impl Texture for ImageTexture { 26 | fn color(&self, u: f64, v: f64) -> ColorRGBA { 27 | // Avoid out-of-bounds during bilinear filtering 28 | let s = u % 1.0 * (self.image.width as f64 - 1.0); 29 | let t = v % 1.0 * (self.image.height as f64 - 1.0); 30 | 31 | let x = s.floor() as usize; 32 | let y = t.floor() as usize; 33 | let u_ratio = s - x as f64; 34 | let v_ratio = t - y as f64; 35 | let u_opposite = 1.0 - u_ratio; 36 | let v_opposite = 1.0 - v_ratio; 37 | 38 | ( 39 | ( 40 | self.image[(x , y )].channel_f64() * u_opposite 41 | + self.image[(x + 1, y )].channel_f64() * u_ratio 42 | ) * v_opposite + ( 43 | self.image[(x , y + 1)].channel_f64() * u_opposite 44 | + self.image[(x + 1, y + 1)].channel_f64() * u_ratio 45 | ) * v_ratio 46 | ) 47 | } 48 | 49 | fn clone_self(&self) -> Box { 50 | let tex: Box = Box::new(ImageTexture { 51 | image: self.image.clone() 52 | }); 53 | tex 54 | } 55 | } 56 | 57 | #[test] 58 | fn it_bilinearly_filters() { 59 | let background = ColorRGBA::new_rgb(0, 0, 0); 60 | let mut surface = Surface::new(2, 2, background); 61 | 62 | surface[(0, 0)] = ColorRGBA::new_rgb(255, 0, 0); 63 | surface[(0, 1)] = ColorRGBA::new_rgb(0, 255, 0); 64 | surface[(1, 0)] = ColorRGBA::new_rgb(0, 0, 255); 65 | surface[(1, 1)] = ColorRGBA::new_rgb(0, 0, 0); 66 | 67 | let texture = ImageTexture { image: surface }; 68 | 69 | let left = texture.color(0.0, 0.5); 70 | assert_eq!(left.r, 0.5); 71 | assert_eq!(left.g, 0.5); 72 | assert_eq!(left.b, 0.0); 73 | 74 | let center = texture.color(0.5, 0.5); 75 | assert_eq!(center.r, 0.25); 76 | assert_eq!(center.g, 0.25); 77 | assert_eq!(center.b, 0.25); 78 | } 79 | -------------------------------------------------------------------------------- /src/material/textures/uvtexture.rs: -------------------------------------------------------------------------------- 1 | use material::Texture; 2 | use raytracer::compositor::{ColorRGBA, Channel}; 3 | 4 | 5 | /// Maps the supplied (u, v) coordinate to the (red, green) color channels. 6 | #[derive(Clone)] 7 | pub struct UVTexture; 8 | 9 | impl Texture for UVTexture { 10 | fn color(&self, u: f64, v: f64) -> ColorRGBA { 11 | let min_value = ::min_value(); 12 | let range = ::max_value() - min_value; 13 | ColorRGBA::new_rgb(u % range + min_value, v % range + min_value, min_value) 14 | } 15 | 16 | fn clone_self(&self) -> Box { 17 | Box::new(UVTexture) as Box 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/my_scene/bunny.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use scene::{Camera, Scene}; 12 | use vec3::Vec3; 13 | 14 | // 300 polys, octree is slightly slower than no octree 15 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 16 | Camera::new( 17 | Vec3 { x: 0.0, y: -150.0, z: 30.0 }, 18 | Vec3 { x: 0.0, y: 60.0, z: 50.0 }, 19 | Vec3 { x: 0.0, y: 0.0, z: 1.0 }, 20 | fov, 21 | image_width, 22 | image_height 23 | ) 24 | } 25 | 26 | pub fn get_scene() -> Scene { 27 | let mut lights: Vec> = Vec::new(); 28 | lights.push(Box::new(SphereLight { position: Vec3 { x: 200.0, y: -200.0, z: 100.0 }, color: Vec3::one(), radius: 40.0 })); 29 | lights.push(Box::new(SphereLight { position: Vec3 { x: -95.0, y: 20.0, z: 170.0 }, color: Vec3 { x: 0.5, y: 0.5, z: 0.3 }, radius: 15.0 })); 30 | 31 | let red = CookTorranceMaterial { k_a: 0.1, k_d: 0.4, k_s: 0.5, k_sg: 0.5, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.05, glossiness: 0.0, ior: 0.98, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 0.25, z: 0.1 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None}; 32 | let green = CookTorranceMaterial { k_a: 0.0, k_d: 0.4, k_s: 0.6, k_sg: 0.7, k_tg: 0.0, gauss_constant: 50.0, roughness: 0.3, glossiness: 0.0, ior: 1.5, ambient: Vec3::one(), diffuse: Vec3 { x: 0.2, y: 0.7, z: 0.2 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None}; 33 | let shiny = CookTorranceMaterial { k_a: 0.0, k_d: 0.2, k_s: 0.7, k_sg: 1.0, k_tg: 0.0, gauss_constant: 25.0, roughness: 0.01, glossiness: 0.0, ior: 0.2, ambient: Vec3::one(), diffuse: Vec3 { x: 0.9, y: 0.9, z: 0.1 }, specular: Vec3 {x: 0.9, y: 0.9, z: 0.1}, transmission: Vec3::zero(), diffuse_texture: None}; 34 | 35 | let mut prims: Vec> = Vec::new(); 36 | prims.push(Box::new(Plane { a: 0.0, b: 0.0, c: 1.0, d: -10.0, material: Box::new(green)})); 37 | prims.push(Box::new(Sphere { center: Vec3 { x: -75.0, y: 60.0, z: 50.0 }, radius: 40.0, material: Box::new(shiny.clone()) })); 38 | prims.push(Box::new(Sphere { center: Vec3 { x: -75.0, y: 60.0, z: 140.0 }, radius: 40.0, material: Box::new(shiny.clone()) })); 39 | let bunny = ::util::import::from_obj(red, false, "./docs/assets/models/bunny.obj").expect("failed to load obj model"); 40 | for triangle in bunny.triangles { prims.push(triangle); } 41 | 42 | println!("Generating octree..."); 43 | let octree = prims.into_iter().collect(); 44 | println!("Octree generated..."); 45 | 46 | Scene { 47 | lights: lights, 48 | octree: octree, 49 | background: Vec3 { x: 0.3, y: 0.5, z: 0.8 }, 50 | skybox: Some(CubeMap::load( 51 | "./docs/assets/textures/skyboxes/storm_y_up/left.png", 52 | "./docs/assets/textures/skyboxes/storm_y_up/right.png", 53 | "./docs/assets/textures/skyboxes/storm_y_up/down.png", 54 | "./docs/assets/textures/skyboxes/storm_y_up/up.png", 55 | "./docs/assets/textures/skyboxes/storm_y_up/front.png", 56 | "./docs/assets/textures/skyboxes/storm_y_up/back.png" 57 | )) 58 | } 59 | } 60 | 61 | pub struct BunnyConfig; 62 | 63 | impl super::SceneConfig for BunnyConfig { 64 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 65 | get_camera(image_width, image_height, fov) 66 | } 67 | 68 | fn get_scene(&self) -> Scene { 69 | get_scene() 70 | } 71 | } -------------------------------------------------------------------------------- /src/my_scene/cornell.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle, TriangleOptions}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use raytracer::compositor::ColorRGBA; 12 | use scene::{Camera, Scene}; 13 | use vec3::Vec3; 14 | 15 | // 10 primitives, octree is super inefficient for this scene 16 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 17 | Camera::new( 18 | Vec3 { x: 50.0, y: 25.0, z: 150.0 }, 19 | Vec3 { x: 50.0, y: 50.0, z: 50.0 }, 20 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 21 | fov, 22 | image_width, 23 | image_height 24 | ) 25 | } 26 | 27 | pub fn get_scene() -> Scene { 28 | let mut lights: Vec> = Vec::new(); 29 | lights.push(Box::new(SphereLight {position: Vec3 { x: 50.0, y: 80.0, z: 50.0 }, color: Vec3::one(), radius: 10.0 })); 30 | 31 | // Example of a textured material 32 | let checker: Box = Box::new(CheckerTexture { color1: ColorRGBA::white(), color2: ColorRGBA::new_rgb(0.8, 0.1, 0.1), scale: 16.0 }); 33 | let checker_grey = CookTorranceMaterial { k_a: 0.0, k_d: 1.0, k_s: 0.0, k_sg: 0.0, k_tg: 0.0, gauss_constant: 1.0, roughness: 0.15, glossiness: 0.0, ior: 0.7, ambient: Vec3::one(), diffuse: Vec3 { x: 0.6, y: 0.6, z: 0.6 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: Some(checker.clone()) }; 34 | 35 | // Example of a short-form material definition using defaults 36 | // let grey = CookTorranceMaterial { k_a: 0.0, k_d: 1.0, k_s: 1.0, k_sg: 0.0, k_tg: 0.0, gauss_constant: 1.0, roughness: 0.15, glossiness: 0.0, ior: 1.5, ambient: Vec3::one(), diffuse: Vec3 { x: 0.6, y: 0.6, z: 0.6 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 37 | let grey = CookTorranceMaterial { diffuse: Vec3 { x: 0.6, y: 0.6, z: 0.6 }, ..Default::default() }; 38 | 39 | let blue = CookTorranceMaterial { k_a: 0.0, k_d: 0.3, k_s: 0.7, k_sg: 0.0, k_tg: 0.0, gauss_constant: 50.0, roughness: 0.1, glossiness: 0.0, ior: 1.3, ambient: Vec3::one(), diffuse: Vec3 { x: 0.1, y: 0.1, z: 1.0 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 40 | let red = PhongMaterial { k_a: 0.0, k_d: 0.6, k_s: 0.4, k_sg: 0.8, k_tg: 0.0, shininess: 10.0, glossiness: 0.0, ior: 0.5, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 0.0, z: 0.0 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 41 | let green = PhongMaterial { k_a: 0.0, k_d: 0.9, k_s: 0.1, k_sg: 0.5, k_tg: 0.0, shininess: 10.0, glossiness: 0.0, ior: 0.7, ambient: Vec3::one(), diffuse: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 42 | let shiny = CookTorranceMaterial { k_a: 0.0, k_d: 0.2, k_s: 1.0, k_sg: 0.8, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 0.25, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None }; 43 | let shiny_glossy = CookTorranceMaterial { k_a: 0.0, k_d: 0.7, k_s: 1.0, k_sg: 0.4, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.2, ior: 0.25, ambient: Vec3::one(), diffuse: Vec3 { x: 0.3, y: 0.3, z: 1.0 }, specular: Vec3 { x: 0.3, y: 0.3, z: 1.0 }, transmission: Vec3::zero(), diffuse_texture: None }; 44 | let refract = CookTorranceMaterial { k_a: 0.0, k_d: 0.0, k_s: 1.0, k_sg: 1.0, k_tg: 1.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 3.0, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3 { x: 0.8, y: 0.8, z: 0.8 }, diffuse_texture: None }; 45 | 46 | let mut prims: Vec> = Vec::new(); 47 | prims.push(Box::new(Plane { a: 0.0, b: 0.0, c: 1.0, d: 0.0, material: Box::new(grey.clone()) })); // Ahead 48 | prims.push(Box::new(Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(checker_grey.clone()) })); // Bottom 49 | prims.push(Box::new(Plane { a: 0.0, b: -1.0, c: 0.0, d: 100.0, material: Box::new(grey.clone()) })); // Top 50 | prims.push(Box::new(Plane { a: 1.0, b: 0.0, c: 0.0, d: 0.0, material: Box::new(red.clone()) })); // Left 51 | prims.push(Box::new(Plane { a: -1.0, b: 0.0, c: 0.0, d: 100.0, material: Box::new(green.clone()) })); // Right 52 | prims.push(Box::new(Sphere { center: Vec3 { x: 30.0, y: 15.0, z: 20.0 }, radius: 15.0, material: Box::new(shiny.clone())})); 53 | prims.push(Box::new(Sphere { center: Vec3 { x: 70.0, y: 17.0, z: 60.0 }, radius: 17.0, material: Box::new(refract.clone())})); 54 | prims.push(Box::new(Sphere { center: Vec3 { x: 50.0, y: 50.0, z: 20.0 }, radius: 10.0, material: Box::new(shiny_glossy.clone())})); 55 | prims.push(Box::new(Sphere { center: Vec3 { x: 20.0, y: 13.0, z: 90.0 }, radius: 13.0, material: Box::new(blue.clone())})); 56 | 57 | let mut triopts = TriangleOptions::new( 58 | Vec3 { x: 20.0, y: 95.0, z: 20.0 }, 59 | Vec3 { x: 15.0, y: 50.0, z: 40.0 }, 60 | Vec3 { x: 35.0, y: 50.0, z: 35.0 }); 61 | triopts.texinfo([(0.5, 1.0), (0.0, 0.0), (1.0, 0.0)]); 62 | triopts.material(Box::new(blue)); 63 | prims.push(Box::new(triopts.build())); 64 | 65 | println!("Generating octree..."); 66 | let octree = prims.into_iter().collect(); 67 | println!("Octree generated..."); 68 | 69 | Scene { 70 | lights: lights, 71 | octree: octree, 72 | background: Vec3::one(), 73 | skybox: None 74 | } 75 | } 76 | 77 | pub struct CornelConfig; 78 | 79 | impl super::SceneConfig for CornelConfig { 80 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 81 | get_camera(image_width, image_height, fov) 82 | } 83 | 84 | fn get_scene(&self) -> Scene { 85 | get_scene() 86 | } 87 | } -------------------------------------------------------------------------------- /src/my_scene/cow.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use scene::{Camera, Scene}; 12 | use vec3::Vec3; 13 | 14 | // 5000 polys, cow. Octree helps. 15 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 16 | Camera::new( 17 | Vec3 { x: -2.0, y: 4.0, z: 10.0 }, 18 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 19 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 20 | fov, 21 | image_width, 22 | image_height 23 | ) 24 | } 25 | 26 | pub fn get_scene() -> Scene { 27 | let mut lights: Vec> = Vec::new(); 28 | lights.push(Box::new(SphereLight { position: Vec3 {x: 3.0, y: 10.0, z: 6.0}, color: Vec3::one(), radius: 5.0 })); 29 | 30 | let red = CookTorranceMaterial { k_a: 0.0, k_d: 0.6, k_s: 1.0, k_sg: 0.2, k_tg: 0.0, gauss_constant: 30.0, roughness: 0.1, glossiness: 0.0, ior: 0.8, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 0.25, z: 0.1 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 31 | let green = CookTorranceMaterial { k_a: 0.0, k_d: 0.5, k_s: 0.4, k_sg: 0.1, k_tg: 0.0, gauss_constant: 25.0, roughness: 0.4, glossiness: 0.0, ior: 0.95, ambient: Vec3::one(), diffuse: Vec3 { x: 0.2, y: 0.7, z: 0.2 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 32 | 33 | let mut prims: Vec> = Vec::new(); 34 | prims.push(Box::new(Plane { a: 0.0, b: 1.0, c: 0.0, d: 3.6, material: Box::new(green) })); 35 | let cow = ::util::import::from_obj(red, true, "./docs/assets/models/cow.obj").expect("failed to load obj model");; 36 | for triangle in cow.triangles { prims.push(triangle); } 37 | 38 | println!("Generating octree..."); 39 | let octree = prims.into_iter().collect(); 40 | println!("Octree generated..."); 41 | 42 | Scene { 43 | lights: lights, 44 | octree: octree, 45 | background: Vec3 { x: 0.3, y: 0.5, z: 0.8 }, 46 | skybox: None 47 | } 48 | } 49 | 50 | pub struct CowConfig; 51 | 52 | impl super::SceneConfig for CowConfig { 53 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 54 | get_camera(image_width, image_height, fov) 55 | } 56 | 57 | fn get_scene(&self) -> Scene { 58 | get_scene() 59 | } 60 | } -------------------------------------------------------------------------------- /src/my_scene/easing.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use raytracer::compositor::ColorRGBA; 12 | use raytracer::animator::easing::Easing; 13 | use scene::{Camera, Scene}; 14 | use vec3::Vec3; 15 | 16 | // Easing test scene 17 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 18 | Camera::new( 19 | Vec3 { x: 0.0, y: 0.0, z: 150.0 }, 20 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 21 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 22 | fov, 23 | image_width, 24 | image_height 25 | ) 26 | } 27 | 28 | pub fn get_animation_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 29 | Camera::new_with_keyframes( 30 | Vec3 { x: 0.0, y: 0.0, z: 150.0 }, 31 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 32 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 33 | fov, 34 | image_width, 35 | image_height, 36 | vec![ 37 | CameraKeyframe { 38 | time: 10.0, 39 | position: Vec3 { x: 0.0, y: 1000.0, z: 150.0 }, 40 | look_at: Vec3 { x: 0.0, y: 1000.0, z: 0.0 }, 41 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 42 | easing: Easing { a: 0.0, b: 0.05, c: 0.1, d: 1.0 } 43 | }, 44 | ] 45 | ) 46 | } 47 | 48 | pub fn get_scene() -> Scene { 49 | let mut lights: Vec> = Vec::new(); 50 | lights.push(Box::new(SphereLight { 51 | position: Vec3 { x: 0.0, y: 0.0, z: 150.0 }, 52 | color: Vec3::one(), 53 | radius: 10.0 54 | })); 55 | 56 | lights.push(Box::new(SphereLight { 57 | position: Vec3 { x: 0.0, y: 1000.0, z: 150.0 }, 58 | color: Vec3::one(), 59 | radius: 10.0 60 | })); 61 | 62 | let checker: Box = Box::new(CheckerTexture { 63 | color1: ColorRGBA::white(), 64 | color2: ColorRGBA::new_rgb(0.1, 0.1, 0.1), 65 | scale: 32.0 66 | }); 67 | let checker_mat = CookTorranceMaterial { 68 | k_a: 0.0, 69 | k_d: 1.0, 70 | k_s: 0.0, 71 | k_sg: 0.0, 72 | k_tg: 0.0, 73 | gauss_constant: 1.0, 74 | roughness: 0.15, 75 | glossiness: 0.0, 76 | ior: 0.7, 77 | ambient: Vec3::one(), 78 | diffuse: Vec3 { x: 0.6, y: 0.6, z: 0.6 }, 79 | specular: Vec3::one(), 80 | transmission: Vec3::zero(), 81 | diffuse_texture: Some(checker) 82 | }; 83 | 84 | let mut prims: Vec> = Vec::new(); 85 | prims.push(Box::new(Plane { 86 | a: 0.0, 87 | b: 0.0, 88 | c: 1.0, 89 | d: 0.0, 90 | material: Box::new(checker_mat) 91 | })); 92 | 93 | let octree = prims.into_iter().collect(); 94 | 95 | Scene { 96 | lights: lights, 97 | octree: octree, 98 | background: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, 99 | skybox: None 100 | } 101 | } 102 | 103 | pub struct EasingConfig; 104 | 105 | impl super::SceneConfig for EasingConfig { 106 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 107 | get_camera(image_width, image_height, fov) 108 | } 109 | 110 | fn get_animation_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 111 | get_animation_camera(image_width, image_height, fov) 112 | } 113 | 114 | fn get_scene(&self) -> Scene { 115 | get_scene() 116 | } 117 | } -------------------------------------------------------------------------------- /src/my_scene/fresnel.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use raytracer::compositor::ColorRGBA; 12 | use raytracer::animator::easing::Easing; 13 | use scene::{Camera, Scene}; 14 | use vec3::Vec3; 15 | 16 | // Fresnel test scene 17 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 18 | let height = 50.0; 19 | 20 | Camera::new( 21 | Vec3 { x: 50.0, y: height, z: 250.0 }, 22 | Vec3 { x: 50.0, y: 50.0, z: 50.0 }, 23 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 24 | fov, 25 | image_width, 26 | image_height 27 | ) 28 | } 29 | 30 | pub fn get_animation_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 31 | // State at time t=0 32 | // A keyframe at time t=0 is automatically created when insert_keyframes is called 33 | Camera::new_with_keyframes( 34 | Vec3 { x: 0.0, y: 1.0, z: 250.0 }, 35 | Vec3 { x: 0.0, y: 1.0, z: 50.0 }, 36 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 37 | fov, 38 | image_width, 39 | image_height, 40 | vec![ 41 | CameraKeyframe { 42 | time: 2.5, 43 | position: Vec3 { x: 50.0, y: 100.0, z: 250.0 }, 44 | look_at: Vec3 { x: 0.0, y: 1.0, z: 50.0 }, 45 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 46 | easing: Easing::linear() 47 | }, 48 | CameraKeyframe { 49 | time: 5.0, 50 | position: Vec3 { x: 0.0, y: 200.0, z: 250.0 }, 51 | look_at: Vec3 { x: 0.0, y: 1.0, z: 50.0 }, 52 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 53 | easing: Easing::linear() 54 | }, 55 | CameraKeyframe { 56 | time: 7.5, 57 | position: Vec3 { x: -50.0, y: 100.0, z: 250.0 }, 58 | look_at: Vec3 { x: 0.0, y: 1.0, z: 50.0 }, 59 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 60 | easing: Easing::linear() 61 | }, 62 | CameraKeyframe { 63 | time: 10.0, 64 | position: Vec3 { x: 0.0, y: 1.0, z: 250.0 }, 65 | look_at: Vec3 { x: 0.0, y: 1.0, z: 50.0 }, 66 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 67 | easing: Easing::linear() 68 | }, 69 | ] 70 | ) 71 | } 72 | 73 | pub fn get_scene() -> Scene { 74 | let mut lights: Vec> = Vec::new(); 75 | lights.push(Box::new(SphereLight { position: Vec3 { x: 50.0, y: 80.0, z: 50.0 }, color: Vec3::one(), radius: 10.0 })); 76 | 77 | 78 | let checker: Box = Box::new(CheckerTexture { color1: ColorRGBA::white(), color2: ColorRGBA::new_rgb(0.1, 0.1, 0.1), scale: 32.0 }); 79 | let checker_red = CookTorranceMaterial { k_a: 0.0, k_d: 1.0, k_s: 0.0, k_sg: 0.0, k_tg: 0.0, gauss_constant: 1.0, roughness: 0.15, glossiness: 0.0, ior: 1.5, ambient: Vec3::one(), diffuse: Vec3 { x: 0.6, y: 0.6, z: 0.6 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: Some(checker.clone()) }; 80 | let shiny = CookTorranceMaterial { k_a: 0.0, k_d: 0.2, k_s: 1.0, k_sg: 1.0, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 0.15, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None }; 81 | let global_specular_only = CookTorranceMaterial { k_a: 0.0, k_d: 0.0, k_s: 0.0, k_sg: 1.0, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 1.5, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None }; 82 | let refract = CookTorranceMaterial { k_a: 0.0, k_d: 0.0, k_s: 1.0, k_sg: 1.0, k_tg: 1.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 3.0, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None }; 83 | 84 | let mut prims: Vec> = Vec::new(); 85 | prims.push(Box::new(Plane { a: 0.0, b: 0.0, c: 1.0, d: 0.0, material: Box::new(checker_red.clone()) })); // Ahead 86 | prims.push(Box::new(Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(global_specular_only.clone()) })); // Bottom 87 | prims.push(Box::new(Sphere { center: Vec3 {x: 30.0, y: 15.0, z: 20.0 }, radius: 15.0, material: Box::new(shiny.clone()) })); 88 | prims.push(Box::new(Sphere { center: Vec3 {x: 70.0, y: 17.0, z: 60.0 }, radius: 17.0, material: Box::new(refract.clone()) })); 89 | 90 | println!("Generating octree..."); 91 | let octree = prims.into_iter().collect(); 92 | println!("Octree generated..."); 93 | 94 | Scene { 95 | lights: lights, 96 | octree: octree, 97 | background: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, 98 | skybox: None 99 | } 100 | } 101 | 102 | 103 | pub struct FresnelConfig; 104 | 105 | impl super::SceneConfig for FresnelConfig { 106 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 107 | get_camera(image_width, image_height, fov) 108 | } 109 | 110 | fn get_animation_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 111 | get_animation_camera(image_width, image_height, fov) 112 | } 113 | 114 | fn get_scene(&self) -> Scene { 115 | get_scene() 116 | } 117 | } -------------------------------------------------------------------------------- /src/my_scene/heptoroid.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use scene::{Camera, Scene}; 12 | use vec3::Vec3; 13 | 14 | // 114688 tris, 57302 verts 15 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 16 | Camera::new( 17 | Vec3 { x: 7.0, y: 2.0, z: -6.0 }, 18 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 19 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 20 | fov, 21 | image_width, 22 | image_height 23 | ) 24 | } 25 | 26 | pub fn get_scene(material_option: HeptoroidMaterial) -> Scene { 27 | let mut lights: Vec> = Vec::new(); 28 | lights.push(Box::new(SphereLight { position: Vec3 { x: 2.0, y: 3.0, z: -2.0 }, color: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, radius: 1.0 })); 29 | 30 | let heptoroid_material = match material_option { 31 | HeptoroidMaterial::Shiny => { 32 | CookTorranceMaterial { k_a: 0.0, k_d: 0.2, k_s: 1.0, k_sg: 0.55, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 0.25, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None } 33 | } 34 | HeptoroidMaterial::Refractive => { 35 | CookTorranceMaterial { k_a: 0.0, k_d: 0.0, k_s: 1.0, k_sg: 1.0, k_tg: 1.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 1.50, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3 { x: 0.8, y: 0.8, z: 0.8 }, diffuse_texture: None } 36 | } 37 | HeptoroidMaterial::White => { 38 | CookTorranceMaterial { k_a: 0.0, k_d: 0.9, k_s: 1.0, k_sg: 0.15, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.1, ior: 0.5, glossiness: 0.0, ambient: Vec3::one(), diffuse: Vec3 { x: 0.9, y: 0.85, z: 0.7 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None } 39 | } 40 | }; 41 | 42 | let mut prims: Vec> = Vec::new(); 43 | let heptoroid = ::util::import::from_obj(heptoroid_material, false, "./docs/assets/models/heptoroid.obj").ok().expect("failed to load obj model");; 44 | for triangle in heptoroid.triangles.into_iter() { prims.push(triangle); } 45 | 46 | println!("Generating octree..."); 47 | let octree = prims.into_iter().collect(); 48 | println!("Octree generated..."); 49 | 50 | Scene { 51 | lights: lights, 52 | octree: octree, 53 | background: Vec3 { x: 0.84, y: 0.34, z: 0.0 }, 54 | skybox: Some(CubeMap::load( 55 | "./docs/assets/textures/skyboxes/miramar_y_up/left.png", 56 | "./docs/assets/textures/skyboxes/miramar_y_up/right.png", 57 | "./docs/assets/textures/skyboxes/miramar_y_up/down.png", 58 | "./docs/assets/textures/skyboxes/miramar_y_up/up.png", 59 | "./docs/assets/textures/skyboxes/miramar_y_up/front.png", 60 | "./docs/assets/textures/skyboxes/miramar_y_up/back.png" 61 | )) 62 | } 63 | } 64 | 65 | #[derive(Copy, Clone)] 66 | pub enum HeptoroidMaterial { 67 | Shiny, 68 | Refractive, 69 | White, 70 | } 71 | 72 | pub struct HeptoroidConfig { 73 | material: HeptoroidMaterial, 74 | } 75 | 76 | impl HeptoroidConfig { 77 | pub fn shiny() -> HeptoroidConfig { 78 | HeptoroidConfig { material: HeptoroidMaterial::Shiny } 79 | } 80 | 81 | pub fn white() -> HeptoroidConfig { 82 | HeptoroidConfig { material: HeptoroidMaterial::White } 83 | } 84 | 85 | pub fn refractive() -> HeptoroidConfig { 86 | HeptoroidConfig { material: HeptoroidMaterial::Refractive } 87 | } 88 | } 89 | 90 | impl super::SceneConfig for HeptoroidConfig { 91 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 92 | get_camera(image_width, image_height, fov) 93 | } 94 | 95 | fn get_scene(&self) -> Scene { 96 | get_scene(self.material) 97 | } 98 | } -------------------------------------------------------------------------------- /src/my_scene/lucy.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use scene::{Camera, Scene}; 12 | use vec3::Vec3; 13 | 14 | // 50000 polys, model not included! 15 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 16 | Camera::new( 17 | Vec3 { x: -1500.0, y: 300.0, z: 600.0 }, 18 | Vec3 { x: 0.0, y: 400.0, z: -200.0 }, 19 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 20 | fov, 21 | image_width, 22 | image_height 23 | ) 24 | } 25 | 26 | pub fn get_scene() -> Scene { 27 | let mut lights: Vec> = Vec::new(); 28 | lights.push(Box::new(SphereLight { position: Vec3 { x: -1400.0, y: 200.0, z: 100.0 }, color: Vec3 { x: 1.0, y: 0.80, z: 0.40 }, radius: 50.0 })); 29 | 30 | let grey = CookTorranceMaterial { k_a: 0.0, k_d: 0.5, k_s: 0.8, k_sg: 0.5, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.1, glossiness: 0.0, ior: 0.4, ambient: Vec3::one(), diffuse: Vec3 { x: 0.6, y: 0.6, z: 0.65 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 31 | 32 | let mut prims: Vec> = Vec::new(); 33 | let lucy = ::util::import::from_obj(grey, true, "./docs/assets/models/lucy.obj").ok().expect("failed to load obj model");; 34 | for triangle in lucy.triangles.into_iter() { prims.push(triangle); } 35 | 36 | println!("Generating octree..."); 37 | let octree = prims.into_iter().collect(); 38 | println!("Octree generated..."); 39 | 40 | Scene { 41 | lights: lights, 42 | octree: octree, 43 | background: Vec3 { x: 0.84, y: 0.34, z: 0.0 }, 44 | skybox: Some(CubeMap::load( 45 | "./docs/assets/textures/skyboxes/storm_y_up/left.png", 46 | "./docs/assets/textures/skyboxes/storm_y_up/right.png", 47 | "./docs/assets/textures/skyboxes/storm_y_up/down.png", 48 | "./docs/assets/textures/skyboxes/storm_y_up/up.png", 49 | "./docs/assets/textures/skyboxes/storm_y_up/front.png", 50 | "./docs/assets/textures/skyboxes/storm_y_up/back.png" 51 | )) 52 | } 53 | } 54 | 55 | pub struct LucyConfig; 56 | 57 | impl super::SceneConfig for LucyConfig { 58 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 59 | get_camera(image_width, image_height, fov) 60 | } 61 | 62 | fn get_scene(&self) -> Scene { 63 | get_scene() 64 | } 65 | } -------------------------------------------------------------------------------- /src/my_scene/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(test, allow(dead_code))] 2 | use ::scene::{Camera, Scene}; 3 | 4 | pub mod bunny; 5 | pub mod cornell; 6 | pub mod cow; 7 | pub mod easing; 8 | pub mod fresnel; 9 | pub mod heptoroid; 10 | pub mod lucy; 11 | pub mod sibenik; 12 | pub mod sphere; 13 | pub mod sponza; 14 | pub mod tachikoma; 15 | pub mod teapot; 16 | 17 | pub trait SceneConfig { 18 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera; 19 | 20 | fn get_animation_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 21 | self.get_camera(image_width, image_height, fov) 22 | } 23 | 24 | fn get_scene(&self) -> Scene; 25 | } 26 | 27 | pub fn scene_by_name(name: &str) -> Option> { 28 | Some(match name { 29 | "bunny" => Box::new(bunny::BunnyConfig), 30 | "cornell" => Box::new(cornell::CornelConfig), 31 | "cow" => Box::new(cow::CowConfig), 32 | "easing" => Box::new(easing::EasingConfig), 33 | "fresnel" => Box::new(fresnel::FresnelConfig), 34 | "heptoroid-shiny" => Box::new(heptoroid::HeptoroidConfig::shiny()), 35 | "heptoroid-white" => Box::new(heptoroid::HeptoroidConfig::white()), 36 | "heptoroid-refractive" => Box::new(heptoroid::HeptoroidConfig::refractive()), 37 | "lucy" => Box::new(lucy::LucyConfig), 38 | "sibenik" => Box::new(sibenik::SibenikConfig), 39 | "sphere" => Box::new(sphere::SphereConfig), 40 | "sponza" => Box::new(sponza::SponzaConfig), 41 | "tachikoma" => Box::new(tachikoma::TachikomaConfig), 42 | "teapot" => Box::new(teapot::TeapotConfig), 43 | _ => return None, 44 | }) 45 | } -------------------------------------------------------------------------------- /src/my_scene/sibenik.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use raytracer::animator::easing::Easing; 12 | use raytracer::compositor::ColorRGBA; 13 | use scene::{Camera, Scene}; 14 | use vec3::Vec3; 15 | 16 | // ~70K triangles, no textures yet 17 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 18 | Camera::new( 19 | Vec3 { x: -16.0, y: -14.5, z: -2.0 }, 20 | Vec3 { x: 8.0, y: -3.0, z: 2.0 }, 21 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 22 | fov, 23 | image_width, 24 | image_height 25 | ) 26 | } 27 | // 7s target length 28 | pub fn get_animation_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 29 | Camera::new_with_keyframes( 30 | Vec3 { x: -16.0, y: -14.5, z: -2.0 }, 31 | Vec3 { x: 8.0, y: -3.0, z: 2.0 }, 32 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 33 | fov, 34 | image_width, 35 | image_height, 36 | vec![ 37 | CameraKeyframe { 38 | time: 7.0, 39 | position: Vec3 { x: 8.0, y: -13.5, z: 0.2 }, 40 | look_at: Vec3 { x: 8.5, y: 8.0, z: 2.0 }, 41 | up: Vec3 { x: -0.9, y: 0.0, z: -0.7 }, 42 | easing: Easing::linear() 43 | } 44 | ] 45 | ) 46 | } 47 | 48 | pub fn get_scene() -> Scene { 49 | let mut lights: Vec> = Vec::new(); 50 | lights.push(Box::new(SphereLight { position: Vec3 { x: 8.0, y: 8.0, z: 0.0 }, color: Vec3 { x: 1.0, y: 0.8, z: 0.4}, radius: 0.5 })); 51 | lights.push(Box::new(SphereLight { position: Vec3 { x: 8.0, y: -5.0, z: 0.0 }, color: Vec3 { x: 0.5, y: 0.4, z: 0.2}, radius: 1.0 })); 52 | lights.push(Box::new(PointLight { position: Vec3 { x: -16.0, y: -14.5, z: -2.0 }, color: Vec3 { x: 0.15, y: 0.07, z: 0.05 } })); 53 | 54 | 55 | let checker: Box = Box::new(CheckerTexture { color1: ColorRGBA::white(), color2: ColorRGBA::new_rgb(0.15, 0.11, 0.1), scale: 1.0 }); 56 | 57 | let stone = CookTorranceMaterial { k_a: 0.1, k_d: 0.8, k_s: 0.2, k_sg: 0.0, k_tg: 0.0, gauss_constant: 25.0, roughness: 1.0, glossiness: 0.0, ior: 1.5, ambient: Vec3 { x: 0.88, y: 0.83, z: 0.77 }, diffuse: Vec3 { x: 0.88, y: 0.83, z: 0.77 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 58 | let ground = CookTorranceMaterial { k_a: 0.03, k_d: 0.9, k_s: 0.3, k_sg: 0.5, k_tg: 0.0, gauss_constant: 25.0, roughness: 0.1, glossiness: 0.0, ior: 0.5, ambient: Vec3::one(), diffuse: Vec3 { x: 0.38, y: 0.38, z: 0.5 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: Some(checker.clone()) }; 59 | 60 | let mut prims: Vec> = Vec::new(); 61 | prims.push(Box::new(Plane { a: 0.0, b: -1.0, c: 0.0, d: -14.9, material: Box::new(ground.clone()) })); 62 | 63 | let sibenik = ::util::import::from_obj(stone, false, "./docs/assets/models/sibenik.obj").ok().expect("failed to load obj model");; 64 | for triangle in sibenik.triangles.into_iter() { prims.push(triangle); } 65 | 66 | println!("Generating octree..."); 67 | let octree = prims.into_iter().collect(); 68 | println!("Octree generated..."); 69 | 70 | Scene { 71 | lights: lights, 72 | octree: octree, 73 | background: Vec3 { x: 0.5, y: 0.5, z: 0.5 }, 74 | skybox: None 75 | } 76 | } 77 | 78 | 79 | pub struct SibenikConfig; 80 | 81 | impl super::SceneConfig for SibenikConfig { 82 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 83 | get_camera(image_width, image_height, fov) 84 | } 85 | 86 | fn get_animation_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 87 | get_animation_camera(image_width, image_height, fov) 88 | } 89 | 90 | fn get_scene(&self) -> Scene { 91 | get_scene() 92 | } 93 | } -------------------------------------------------------------------------------- /src/my_scene/sphere.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use raytracer::animator::easing::Easing; 12 | use scene::{Camera, Scene}; 13 | use vec3::Vec3; 14 | 15 | // Skybox test scene 16 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 17 | let up = Vec3 { x: 0.0, y: 1.0, z: 0.0 }; // y-up 18 | Camera::new( 19 | Vec3 { x: 0.0, y: 0.0, z: 10.0 }, 20 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 21 | up, 22 | fov, 23 | image_width, 24 | image_height 25 | ) 26 | 27 | // let up = Vec3 { x: 0.0, y: 0.0, z: 1.0 }; // z-up 28 | // Camera::new( 29 | // Vec3 { x: 0.0, y: 10.0, z: 0.0 }, 30 | // Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 31 | // up, 32 | // fov, 33 | // image_width, 34 | // image_height 35 | // ) 36 | } 37 | 38 | pub fn get_animation_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 39 | // State at time t=0 40 | // A keyframe at time t=0 is automatically created when insert_keyframes is called 41 | let camera = Camera::new_with_keyframes( 42 | Vec3 { x: 0.0, y: 0.0, z: 10.0 }, 43 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 44 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 45 | fov, 46 | image_width, 47 | image_height, 48 | vec![ 49 | CameraKeyframe { 50 | time: 2.5, 51 | position: Vec3 { x: 10.0, y: 0.0, z: 0.0 }, 52 | look_at: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 53 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 54 | easing: Easing::linear() 55 | }, 56 | CameraKeyframe { 57 | time: 5.0, 58 | position: Vec3 { x: 0.0, y: 0.0, z: -10.0 }, 59 | look_at: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 60 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 61 | easing: Easing::linear() 62 | }, 63 | CameraKeyframe { 64 | time: 7.5, 65 | position: Vec3 { x: -10.0, y: 0.0, z: 0.0 }, 66 | look_at: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 67 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 68 | easing: Easing::linear() 69 | }, 70 | CameraKeyframe { 71 | time: 10.0, 72 | position: Vec3 { x: 0.0, y: 0.0, z: 10.0 }, 73 | look_at: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 74 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 75 | easing: Easing::linear() 76 | }, 77 | ] 78 | ); 79 | 80 | camera 81 | } 82 | 83 | pub fn get_scene() -> Scene { 84 | let mut lights: Vec> = Vec::new(); 85 | lights.push(Box::new(SphereLight { position: Vec3 { x: 3.0, y: 10.0, z: 6.0 }, color: Vec3::one(), radius: 5.0 })); 86 | 87 | let mut prims: Vec> = Vec::new(); 88 | let shiny = CookTorranceMaterial { k_a: 0.0, k_d: 0.2, k_s: 1.0, k_sg: 1.0, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 0.05, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None }; 89 | prims.push(Box::new(Sphere { center: Vec3::zero(), radius: 2.0, material: Box::new(shiny) })); 90 | 91 | println!("Generating octree..."); 92 | let octree = prims.into_iter().collect(); 93 | println!("Octree generated..."); 94 | 95 | // For y as up 96 | Scene { 97 | lights: lights, 98 | background: Vec3 { x: 0.3, y: 0.5, z: 0.8 }, 99 | octree: octree, 100 | skybox: Some(CubeMap::load( 101 | "./docs/assets/textures/skyboxes/storm_y_up/left.png", 102 | "./docs/assets/textures/skyboxes/storm_y_up/right.png", 103 | "./docs/assets/textures/skyboxes/storm_y_up/down.png", 104 | "./docs/assets/textures/skyboxes/storm_y_up/up.png", 105 | "./docs/assets/textures/skyboxes/storm_y_up/front.png", 106 | "./docs/assets/textures/skyboxes/storm_y_up/back.png" 107 | )) 108 | } 109 | } 110 | 111 | 112 | pub struct SphereConfig; 113 | 114 | impl super::SceneConfig for SphereConfig { 115 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 116 | get_camera(image_width, image_height, fov) 117 | } 118 | 119 | fn get_animation_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 120 | get_animation_camera(image_width, image_height, fov) 121 | } 122 | 123 | fn get_scene(&self) -> Scene { 124 | get_scene() 125 | } 126 | } -------------------------------------------------------------------------------- /src/my_scene/sponza.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use raytracer::compositor::ColorRGBA; 12 | use scene::{Camera, Scene}; 13 | use vec3::Vec3; 14 | 15 | // ~28000 triangles, complex scene with 2 lights 16 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 17 | Camera::new( 18 | Vec3 { x: 800.0, y: 30.0, z: 90.0 }, 19 | Vec3 { x: -500.0, y: 1000.0, z: -100.0 }, 20 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 21 | fov, 22 | image_width, 23 | image_height 24 | ) 25 | } 26 | 27 | pub fn get_scene() -> Scene { 28 | let mut lights: Vec> = Vec::new(); 29 | lights.push(Box::new(SphereLight { position: Vec3 { x: 0.0, y: 3000.0, z: 1000.0 }, color: Vec3 { x: 1.0, y: 0.8, z: 0.4 }, radius: 50.0 })); 30 | lights.push(Box::new(SphereLight { position: Vec3 { x: 300.0, y: 300.0, z: 60.0 }, color: Vec3 { x: 0.38, y: 0.32, z: 0.28 }, radius: 20.0 })); 31 | 32 | let checker: Box = Box::new(CheckerTexture { color1: ColorRGBA::white(), color2: ColorRGBA::new_rgb(0.15, 0.11, 0.1), scale: 32.0 }); 33 | 34 | let stone = CookTorranceMaterial { k_a: 0.1, k_d: 0.8, k_s: 0.2, k_sg: 0.2, k_tg: 0.0, gauss_constant: 50.0, roughness: 1.0, glossiness: 0.0, ior: 1.5, ambient: Vec3 { x: 0.88, y: 0.83, z: 0.77 }, diffuse: Vec3 { x: 0.88, y: 0.83, z: 0.77 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 35 | let ground = CookTorranceMaterial { k_a: 0.03, k_d: 0.9, k_s: 0.3, k_sg: 0.5, k_tg: 0.0, gauss_constant: 25.0, roughness: 0.1, glossiness: 0.0, ior: 0.5, ambient: Vec3::one(), diffuse: Vec3 { x: 0.38, y: 0.38, z: 0.5 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: Some(checker.clone()) }; 36 | let cloth = CookTorranceMaterial { k_a: 0.03, k_d: 0.8, k_s: 0.1, k_sg: 0.05, k_tg: 0.0, gauss_constant: 40.0, roughness: 0.8, glossiness: 0.0, ior: 1.3, ambient: Vec3::one(), diffuse: Vec3 { x: 0.85, y: 0.05, z: 0.05 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 37 | let shrubbery = CookTorranceMaterial { k_a: 0.03, k_d: 0.8, k_s: 0.2, k_sg: 0.05, k_tg: 0.0, gauss_constant: 50.0, roughness: 0.2, glossiness: 0.0, ior: 1.2, ambient: Vec3::one(), diffuse: Vec3 { x: 0.16, y: 0.47, z: 0.11 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 38 | 39 | let mut prims: Vec> = Vec::new(); 40 | prims.push(Box::new(Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(ground) })); 41 | 42 | let sponza_other = ::util::import::from_obj(stone, false, "./docs/assets/models/sponza_other.obj").ok().expect("failed to load obj model");; 43 | for triangle in sponza_other.triangles.into_iter() { prims.push(triangle); } 44 | 45 | let sponza_column_shrubbery = ::util::import::from_obj(shrubbery, false, "./docs/assets/models/sponza_column_shrubbery.obj").ok().expect("failed to load obj model");; 46 | for triangle in sponza_column_shrubbery.triangles.into_iter() { prims.push(triangle); } 47 | 48 | let sponza_cloth = ::util::import::from_obj(cloth, false, "./docs/assets/models/sponza_cloth.obj").ok().expect("failed to load obj model");; 49 | for triangle in sponza_cloth.triangles.into_iter() { prims.push(triangle); } 50 | 51 | println!("Generating octree..."); 52 | let octree = prims.into_iter().collect(); 53 | println!("Octree generated..."); 54 | 55 | Scene { 56 | lights: lights, 57 | octree: octree, 58 | background: Vec3 { x: 0.84, y: 0.34, z: 0.0 }, 59 | skybox: Some(CubeMap::load( 60 | "./docs/assets/textures/skyboxes/storm_y_up/left.png", 61 | "./docs/assets/textures/skyboxes/storm_y_up/right.png", 62 | "./docs/assets/textures/skyboxes/storm_y_up/down.png", 63 | "./docs/assets/textures/skyboxes/storm_y_up/up.png", 64 | "./docs/assets/textures/skyboxes/storm_y_up/front.png", 65 | "./docs/assets/textures/skyboxes/storm_y_up/back.png" 66 | )) 67 | } 68 | } 69 | 70 | pub struct SponzaConfig; 71 | 72 | impl super::SceneConfig for SponzaConfig { 73 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 74 | get_camera(image_width, image_height, fov) 75 | } 76 | 77 | fn get_scene(&self) -> Scene { 78 | get_scene() 79 | } 80 | } -------------------------------------------------------------------------------- /src/my_scene/tachikoma.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use scene::{Camera, Scene}; 12 | use vec3::Vec3; 13 | 14 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 15 | Camera::new( 16 | Vec3 { x: 100.0, y: 60.0, z: -150.0 }, 17 | Vec3 { x: 0.0, y: 50.0, z: 0.0 }, 18 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 19 | fov, 20 | image_width, 21 | image_height 22 | ) 23 | } 24 | 25 | pub fn get_scene() -> Scene { 26 | let mut lights: Vec> = Vec::new(); 27 | lights.push(Box::new(SphereLight { position: Vec3 { x: 0.0, y: 100.0, z: 0.0 }, color: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, radius: 25.0 })); 28 | 29 | let blue = CookTorranceMaterial { k_a: 0.0, k_d: 0.9, k_s: 1.0, k_sg: 0.4, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 0.25, ambient: Vec3::one(), diffuse: Vec3 { x: 0.16, y: 0.29, z: 0.44 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 30 | let floor = CookTorranceMaterial { k_a: 0.0, k_d: 0.9, k_s: 1.0, k_sg: 1.0, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.3, glossiness: 0.0, ior: 1.0, ambient: Vec3::one(), diffuse: Vec3 { x: 0.58, y: 0.63, z: 0.44 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None }; 31 | 32 | let mut prims: Vec> = Vec::new(); 33 | prims.push(Box::new(Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(floor.clone()) })); // Bottom 34 | 35 | let tachikoma = ::util::import::from_obj(blue, false, "./docs/assets/models/tachikoma.obj").ok().expect("failed to load obj model");; 36 | for triangle in tachikoma.triangles.into_iter() { prims.push(triangle); } 37 | 38 | println!("Generating octree..."); 39 | let octree = prims.into_iter().collect(); 40 | println!("Octree generated..."); 41 | 42 | Scene { 43 | lights: lights, 44 | octree: octree, 45 | background: Vec3 { x: 0.2, y: 0.2, z: 0.2 }, 46 | // skybox: None 47 | skybox: Some(CubeMap::load( 48 | "./docs/assets/textures/skyboxes/city_y_up/left.png", 49 | "./docs/assets/textures/skyboxes/city_y_up/right.png", 50 | "./docs/assets/textures/skyboxes/city_y_up/down.png", 51 | "./docs/assets/textures/skyboxes/city_y_up/up.png", 52 | "./docs/assets/textures/skyboxes/city_y_up/front.png", 53 | "./docs/assets/textures/skyboxes/city_y_up/back.png" 54 | )) 55 | } 56 | } 57 | 58 | pub struct TachikomaConfig; 59 | 60 | impl super::SceneConfig for TachikomaConfig { 61 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 62 | get_camera(image_width, image_height, fov) 63 | } 64 | 65 | fn get_scene(&self) -> Scene { 66 | get_scene() 67 | } 68 | } -------------------------------------------------------------------------------- /src/my_scene/teapot.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use mat4::{Mat4, Transform}; 11 | use raytracer::animator::CameraKeyframe; 12 | use scene::{Camera, Scene}; 13 | use vec3::Vec3; 14 | 15 | // When using Fresnel, set k_sg and k_tg (if applicable) to 1.0 for easier material definition. 16 | // You can still manually tweak it if you wish (try reducing k_sg for metals) 17 | 18 | // 2500 polys, marginal improvement from an octree 19 | pub fn get_teapot_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 20 | Camera::new( 21 | Vec3 { x: -0.2, y: 1.0, z: 2.0 }, 22 | Vec3 { x: 0.0, y: 0.6, z: 0.0 }, 23 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 24 | fov, 25 | image_width, 26 | image_height 27 | ) 28 | } 29 | 30 | pub fn get_teapot_scene() -> Scene { 31 | let mut lights: Vec> = Vec::new(); 32 | lights.push(Box::new(SphereLight { position: Vec3 { x: 0.6, y: 2.0, z: 1.2 }, color: Vec3::one(), radius: 1.0 })); 33 | 34 | let porcelain = CookTorranceMaterial { k_a: 0.0, k_d: 0.9, k_s: 1.0, k_sg: 1.0, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.1, glossiness: 0.0, ior: 1.1, ambient: Vec3::one(), diffuse: Vec3 { x: 0.9, y: 0.85, z: 0.7 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 35 | 36 | let mut prims: Vec> = Vec::new(); 37 | // prims.push(Box::new(Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(green) })); 38 | let mut teapot = ::util::import::from_obj(porcelain, false, "./docs/assets/models/teapot.obj").ok().expect("failed to load obj model");; 39 | let rotate = Transform::new(Mat4::rotate_x_deg_matrix(1.0)); 40 | teapot.mut_transform(&rotate); 41 | for triangle in teapot.triangles.into_iter() { prims.push(triangle); } 42 | 43 | println!("Generating octree..."); 44 | let octree = prims.into_iter().collect(); 45 | println!("Octree generated..."); 46 | 47 | Scene { 48 | lights: lights, 49 | octree: octree, 50 | background: Vec3 { x: 0.3, y: 0.5, z: 0.8 }, 51 | skybox: Some(CubeMap::load( 52 | "./docs/assets/textures/skyboxes/miramar_y_up/left.png", 53 | "./docs/assets/textures/skyboxes/miramar_y_up/right.png", 54 | "./docs/assets/textures/skyboxes/miramar_y_up/down.png", 55 | "./docs/assets/textures/skyboxes/miramar_y_up/up.png", 56 | "./docs/assets/textures/skyboxes/miramar_y_up/front.png", 57 | "./docs/assets/textures/skyboxes/miramar_y_up/back.png" 58 | )) 59 | } 60 | } 61 | 62 | 63 | pub struct TeapotConfig; 64 | 65 | impl super::SceneConfig for TeapotConfig { 66 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 67 | get_teapot_camera(image_width, image_height, fov) 68 | } 69 | 70 | fn get_scene(&self) -> Scene { 71 | get_teapot_scene() 72 | } 73 | } -------------------------------------------------------------------------------- /src/raytracer/animator/animator.rs: -------------------------------------------------------------------------------- 1 | use raytracer::animator::CameraKeyframe; 2 | use raytracer::Renderer; 3 | use scene::{Camera, Scene}; 4 | use std::sync::mpsc::sync_channel; 5 | use std::sync::Arc; 6 | use std::thread; 7 | use vec3::Vec3; 8 | 9 | pub struct Animator { 10 | pub fps: f64, 11 | pub animate_from: f64, // Number of frames is rounded down to nearest frame 12 | pub animate_to: f64, 13 | pub starting_frame_number: u32, // For filename 14 | pub renderer: Renderer 15 | } 16 | 17 | // TODO: Non-linear interpolation 18 | impl Animator { 19 | // TODO: make this a Surface iterator so both single frame and animation 20 | // process flows are similar 21 | pub fn animate(&self, camera: Camera, shared_scene: Arc, filename: &str) { 22 | let animate_start = ::time::get_time(); 23 | let length = self.animate_to - self.animate_from; 24 | let total_frames = (self.fps * length).floor() as u32; 25 | 26 | // Allow one frame to be renderered while the previous one is being written 27 | let (frame_tx, frame_rx) = sync_channel(0); 28 | let (exit_tx, exit_rx) = sync_channel(0); 29 | 30 | let starting_frame_number = self.starting_frame_number; 31 | 32 | let filename = filename.to_string(); 33 | thread::spawn(move || { 34 | for (frame_num, frame_data) in frame_rx.iter().enumerate() { 35 | let file_frame_number = starting_frame_number as usize + frame_num; 36 | 37 | let shared_name = format!("{}{:06}.ppm", filename, file_frame_number); 38 | ::util::export::to_ppm(&frame_data, &shared_name).expect("ppm write failure"); 39 | } 40 | 41 | exit_tx.send(()).unwrap(); 42 | }); 43 | 44 | for frame_number in 0..total_frames { 45 | let time = self.animate_from + f64::from(frame_number) / self.fps; 46 | let lerped_camera = Animator::lerp_camera(&camera, time); 47 | let frame_data = self.renderer.render(lerped_camera, Arc::clone(&shared_scene)); 48 | frame_tx.send(frame_data).unwrap(); 49 | 50 | ::util::print_progress("*** Frame", animate_start, frame_number as usize + 1usize, total_frames as usize); 51 | println!(""); 52 | } 53 | drop(frame_tx); 54 | 55 | exit_rx.recv().unwrap(); 56 | } 57 | 58 | fn get_neighbour_keyframes(keyframes: Vec, time: f64) 59 | -> (CameraKeyframe, CameraKeyframe, f64) { 60 | 61 | if keyframes.len() <= 1 { 62 | panic!("Not enough keyframes to interpolate: got: {} expected: >= 2", keyframes.len()); 63 | } 64 | 65 | // Get the two keyframes inbetween current time 66 | let mut first = &keyframes[0]; 67 | let mut second = &keyframes[1]; 68 | 69 | for keyframe in &keyframes { 70 | if keyframe.time <= time && time - keyframe.time >= first.time - time { 71 | first = keyframe; 72 | } 73 | 74 | if keyframe.time > time && 75 | (keyframe.time - time < second.time - time || second.time < time) { 76 | second = keyframe; 77 | } 78 | } 79 | 80 | let keyframe_length = second.time - first.time; 81 | 82 | let alpha = if keyframe_length == 0.0 { 83 | 0.0 84 | } else { 85 | second.easing.t((time - first.time) / keyframe_length) 86 | }; 87 | 88 | (first.clone(), second.clone(), alpha) 89 | } 90 | 91 | fn lerp_camera(camera: &Camera, time: f64) -> Camera { 92 | let keyframes = match camera.keyframes.clone() { 93 | Some(k) => k, 94 | None => panic!("Cannot lerp a camera with no keyframes!") 95 | }; 96 | 97 | let (first, second, alpha) = Animator::get_neighbour_keyframes(keyframes, time); 98 | 99 | let lerped_position = Vec3::lerp(&first.position, &second.position, alpha); 100 | let lerped_look_at = Vec3::lerp(&first.look_at, &second.look_at, alpha); 101 | let lerped_up = Vec3::lerp(&first.up, &second.up, alpha); 102 | 103 | let mut lerped_camera = Camera::new( 104 | lerped_position, 105 | lerped_look_at, 106 | lerped_up, 107 | camera.fov_deg, 108 | camera.image_width, 109 | camera.image_height, 110 | ); 111 | 112 | lerped_camera.keyframes = camera.keyframes.clone(); 113 | lerped_camera 114 | } 115 | } 116 | 117 | #[cfg(test)] 118 | use raytracer::animator::Easing; 119 | 120 | #[test] 121 | fn test_lerp_camera_position() { 122 | // Camera rotates 180 degrees 123 | let camera = Camera::new_with_keyframes( 124 | Vec3 { x: -1.0, y: -1.0, z: -1.0 }, 125 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 126 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 127 | 45.0, 128 | 10, 129 | 10, 130 | vec![ 131 | CameraKeyframe { 132 | time: 5.0, 133 | position: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 134 | look_at: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 135 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 136 | easing: Easing::linear() 137 | }, 138 | CameraKeyframe { 139 | time: 10.0, 140 | position: Vec3 { x: 10.0, y: 0.0, z: 0.0 }, 141 | look_at: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 142 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 143 | easing: Easing::linear() 144 | }, 145 | ] 146 | ); 147 | 148 | let expected_position_0 = Vec3 { x: -1.0, y: -1.0, z: -1.0 }; 149 | assert_eq!(Animator::lerp_camera(&camera, 0.0).position, expected_position_0); 150 | 151 | let expected_position_5 = Vec3 { x: 0.0, y: 0.0, z: 0.0 }; 152 | assert_eq!(Animator::lerp_camera(&camera, 5.0).position, expected_position_5); 153 | 154 | let expected_position_7_5 = Vec3 { x: 5.0, y: 0.0, z: 0.0 }; 155 | assert_eq!(Animator::lerp_camera(&camera, 7.5).position, expected_position_7_5); 156 | 157 | let expected_position_10 = Vec3 { x: 10.0, y: 0.0, z: 0.0 }; 158 | assert_eq!(Animator::lerp_camera(&camera, 10.0).position, expected_position_10); 159 | } 160 | -------------------------------------------------------------------------------- /src/raytracer/animator/camerakeyframe.rs: -------------------------------------------------------------------------------- 1 | use vec3::Vec3; 2 | use raytracer::animator::Easing; 3 | 4 | #[derive(Clone)] 5 | pub struct CameraKeyframe { 6 | pub time: f64, 7 | pub position: Vec3, 8 | pub look_at: Vec3, 9 | pub up: Vec3, 10 | pub easing: Easing 11 | } 12 | -------------------------------------------------------------------------------- /src/raytracer/animator/easing.rs: -------------------------------------------------------------------------------- 1 | /// Tries to fit a curve where t is in the range [0, 1] and 2 | /// a is t=0, b is t=0.33.., c is t=0.66.., and d is t=1.0 3 | #[derive(Clone)] 4 | pub struct Easing { 5 | pub a: f64, 6 | pub b: f64, 7 | pub c: f64, 8 | pub d: f64 9 | } 10 | 11 | impl Easing { 12 | pub fn linear() -> Easing { 13 | Easing { 14 | a: 0.0, 15 | b: 1.0 / 3.0, 16 | c: 2.0 / 3.0, 17 | d: 1.0 18 | } 19 | } 20 | 21 | pub fn t(&self, t: f64) -> f64 { 22 | Easing::interpolate_cubic(self.a, self.b, self.c, self.d, t) 23 | } 24 | 25 | fn interpolate_cubic(a: f64, b: f64, c: f64, d: f64, t: f64) -> f64 { 26 | let abc = Easing::interpolate_quadratic(a, b, c, t); 27 | let bcd = Easing::interpolate_quadratic(b, c, d, t); 28 | 29 | Easing::interpolate_linear(abc, bcd, t) 30 | } 31 | 32 | fn interpolate_quadratic(a: f64, b: f64, c: f64, t: f64) -> f64 { 33 | let ab = Easing::interpolate_linear(a, b, t); 34 | let bc = Easing::interpolate_linear(b, c, t); 35 | 36 | Easing::interpolate_linear(ab, bc, t) 37 | } 38 | 39 | fn interpolate_linear(a: f64, b: f64, t: f64) -> f64 { 40 | (1.0 - t) * a + t * b 41 | } 42 | } 43 | 44 | #[test] 45 | fn test_easing() { 46 | // This also tests Bezier easing as linear easing 47 | // is implemented using Bezier easing. 48 | let linear_easing = Easing::linear();; 49 | assert_eq!(linear_easing.t(0.0), 0.0); 50 | assert_eq!(linear_easing.t(0.25), 0.25); 51 | assert_eq!(linear_easing.t(0.5), 0.5); 52 | assert_eq!(linear_easing.t(0.75), 0.75); 53 | assert_eq!(linear_easing.t(1.0), 1.0); 54 | } 55 | -------------------------------------------------------------------------------- /src/raytracer/animator/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::animator::Animator; 2 | pub use self::easing::Easing; 3 | pub use self::camerakeyframe::CameraKeyframe; 4 | 5 | pub mod animator; 6 | pub mod easing; 7 | pub mod camerakeyframe; 8 | -------------------------------------------------------------------------------- /src/raytracer/compositor/colorrgba.rs: -------------------------------------------------------------------------------- 1 | use num::{Float, ToPrimitive}; 2 | use std::cmp::{min, max, Ord}; 3 | use std::ops::{Add, Mul, Sub}; 4 | use vec3::Vec3; 5 | 6 | pub trait Channel: ToPrimitive { 7 | fn min_value() -> Self; 8 | fn max_value() -> Self; 9 | fn add(a: Self, b: Self) -> Self; 10 | fn sub(a: Self, b: Self) -> Self; 11 | } 12 | 13 | impl Channel for u8 { 14 | #[inline] 15 | fn min_value() -> u8 { u8::min_value() } 16 | 17 | #[inline] 18 | fn max_value() -> u8 { u8::max_value() } 19 | 20 | #[inline] 21 | fn add(a: u8, b: u8) -> u8 { a.saturating_add(b) } 22 | 23 | #[inline] 24 | fn sub(a: u8, b: u8) -> u8 { a.saturating_sub(b) } 25 | } 26 | 27 | impl Channel for f64 { 28 | #[inline] 29 | fn min_value() -> f64 { 0.0 } 30 | 31 | #[inline] 32 | fn max_value() -> f64 { 1.0 } 33 | 34 | #[inline] 35 | fn add(a: f64, b: f64) -> f64 { a + b } 36 | 37 | #[inline] 38 | fn sub(a: f64, b: f64) -> f64 { a - b } 39 | } 40 | 41 | 42 | #[derive(Copy)] 43 | pub struct ColorRGBA { 44 | pub r: T, 45 | pub g: T, 46 | pub b: T, 47 | pub a: T, 48 | } 49 | 50 | impl Clone for ColorRGBA { 51 | fn clone(&self) -> ColorRGBA { 52 | ColorRGBA { 53 | r: self.r.clone(), 54 | g: self.g.clone(), 55 | b: self.b.clone(), 56 | a: self.a.clone() 57 | } 58 | } 59 | } 60 | 61 | fn clamp(value: T, min_value: T, max_value: T) -> T { 62 | max(min(value, max_value), min_value) 63 | } 64 | 65 | // Maybe later?: ColorRGBA.quantize() -> ColorRGBA 66 | // How do we implement this more generally so that we may have ColorRGBA 67 | impl ColorRGBA { 68 | pub fn new_rgb_clamped(r: f64, g: f64, b: f64) -> ColorRGBA { 69 | let min_color: u8 = Channel::min_value(); 70 | let max_color: u8 = Channel::max_value(); 71 | 72 | ColorRGBA::new_rgb( 73 | clamp((r * max_color as f64).round() as i32, min_color as i32, max_color as i32) as u8, 74 | clamp((g * max_color as f64).round() as i32, min_color as i32, max_color as i32) as u8, 75 | clamp((b * max_color as f64).round() as i32, min_color as i32, max_color as i32) as u8) 76 | } 77 | } 78 | 79 | // Maybe later?: ColorRGBA.quantize() -> ColorRGBA 80 | // How do we implement this more generally so that we may have ColorRGBA 81 | impl ColorRGBA { 82 | #[allow(dead_code)] 83 | pub fn new_rgba(r: T, g: T, b: T, a: T) -> ColorRGBA { 84 | ColorRGBA { r: r, g: g, b: b, a: a } 85 | } 86 | 87 | #[allow(dead_code)] 88 | pub fn new_rgb(r: T, g: T, b: T) -> ColorRGBA { 89 | ColorRGBA { r: r, g: g, b: b, a: Channel::max_value() } 90 | } 91 | 92 | #[allow(dead_code)] 93 | pub fn black() -> ColorRGBA { 94 | ColorRGBA::new_rgb( 95 | Channel::min_value(), 96 | Channel::min_value(), 97 | Channel::min_value()) 98 | } 99 | 100 | #[allow(dead_code)] 101 | pub fn white() -> ColorRGBA { 102 | ColorRGBA::new_rgb( 103 | Channel::max_value(), 104 | Channel::max_value(), 105 | Channel::max_value()) 106 | } 107 | 108 | pub fn transparent() -> ColorRGBA { 109 | ColorRGBA::new_rgba( 110 | Channel::min_value(), 111 | Channel::min_value(), 112 | Channel::min_value(), 113 | Channel::min_value()) 114 | } 115 | 116 | pub fn channel_f64(&self) -> ColorRGBA { 117 | let max_val: T = Channel::max_value(); 118 | ColorRGBA { 119 | r: self.r.to_f64().unwrap() / max_val.to_f64().unwrap(), 120 | g: self.g.to_f64().unwrap() / max_val.to_f64().unwrap(), 121 | b: self.b.to_f64().unwrap() / max_val.to_f64().unwrap(), 122 | a: self.a.to_f64().unwrap() / max_val.to_f64().unwrap(), 123 | } 124 | } 125 | 126 | // Here until we have vec operations (add, mul) for color 127 | // We also need ColorRGBA 128 | pub fn to_vec3(&self) -> Vec3 { 129 | let color = self.channel_f64(); 130 | Vec3 { 131 | x: color.r, 132 | y: color.g, 133 | z: color.b, 134 | } 135 | } 136 | } 137 | 138 | impl Add for ColorRGBA { 139 | type Output = ColorRGBA; 140 | 141 | fn add(self, other: ColorRGBA) -> ColorRGBA { 142 | ColorRGBA { 143 | r: Channel::add(self.r, other.r), 144 | g: Channel::add(self.g, other.g), 145 | b: Channel::add(self.b, other.b), 146 | a: Channel::add(self.a, other.a), 147 | } 148 | } 149 | } 150 | 151 | impl Sub for ColorRGBA { 152 | type Output = ColorRGBA; 153 | 154 | fn sub(self, other: ColorRGBA) -> ColorRGBA { 155 | ColorRGBA { 156 | r: Channel::sub(self.r, other.r), 157 | g: Channel::sub(self.g, other.g), 158 | b: Channel::sub(self.b, other.b), 159 | a: Channel::sub(self.a, other.a), 160 | } 161 | } 162 | } 163 | 164 | impl Mul for ColorRGBA { 165 | type Output = ColorRGBA; 166 | 167 | fn mul(self, other: ColorRGBA) -> ColorRGBA { 168 | ColorRGBA { 169 | r: self.r * other.r, 170 | g: self.g * other.g, 171 | b: self.b * other.b, 172 | a: self.a * other.a 173 | } 174 | } 175 | } 176 | 177 | // Scalar multiplication 178 | impl Mul for ColorRGBA { 179 | type Output = ColorRGBA; 180 | 181 | fn mul(self, other: T) -> ColorRGBA { 182 | ColorRGBA { 183 | r: self.r * other, 184 | g: self.g * other, 185 | b: self.b * other, 186 | a: self.a 187 | } 188 | } 189 | } 190 | 191 | #[test] 192 | fn color_add() { 193 | let foo_color: ColorRGBA = ColorRGBA::new_rgba(1, 1, 1, 1) + 194 | ColorRGBA::new_rgba(2, 2, 2, 2); 195 | assert_eq!(foo_color.r, 3); 196 | assert_eq!(foo_color.g, 3); 197 | assert_eq!(foo_color.b, 3); 198 | assert_eq!(foo_color.a, 3); 199 | 200 | let foo_color: ColorRGBA = ColorRGBA::new_rgba(200, 1, 1, 1) + 201 | ColorRGBA::new_rgba(200, 2, 2, 2); 202 | assert_eq!(foo_color.r, 255); 203 | assert_eq!(foo_color.g, 3); 204 | assert_eq!(foo_color.b, 3); 205 | assert_eq!(foo_color.a, 3); 206 | } 207 | 208 | #[test] 209 | fn color_sub() { 210 | let foo_color: ColorRGBA = ColorRGBA::new_rgba(7, 7, 7, 7) - 211 | ColorRGBA::new_rgba(2, 2, 2, 2); 212 | assert_eq!(foo_color.r, 5); 213 | assert_eq!(foo_color.g, 5); 214 | assert_eq!(foo_color.b, 5); 215 | assert_eq!(foo_color.a, 5); 216 | } 217 | 218 | #[test] 219 | fn color_mul() { 220 | let foo_color = ColorRGBA::::new_rgb(0.5, 0.0, 0.0) * 2.0; 221 | 222 | assert_eq!(foo_color.r, 1.0); 223 | assert_eq!(foo_color.g, 0.0); 224 | assert_eq!(foo_color.b, 0.0); 225 | assert_eq!(foo_color.a, 1.0); 226 | } 227 | 228 | -------------------------------------------------------------------------------- /src/raytracer/compositor/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::colorrgba::{Channel, ColorRGBA}; 2 | pub use self::surface::Surface; 3 | pub use self::surfacefactory::SurfaceFactory; 4 | pub use self::surfaceiterator::SurfaceIterator; 5 | 6 | pub mod colorrgba; 7 | pub mod surface; 8 | pub mod surfacefactory; 9 | pub mod surfaceiterator; 10 | -------------------------------------------------------------------------------- /src/raytracer/compositor/surface.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::min; 2 | use std::iter::repeat; 3 | use std::ops::{Index, IndexMut}; 4 | 5 | use raytracer::compositor::{ColorRGBA, SurfaceFactory}; 6 | 7 | pub struct IterPixelMut<'a, T: 'a>(::std::slice::IterMut<'a, ColorRGBA>); 8 | 9 | impl<'a, T> Iterator for IterPixelMut<'a, T> where T: 'a { 10 | type Item = &'a mut ColorRGBA; 11 | 12 | fn next(&mut self) -> Option<&'a mut ColorRGBA> { 13 | self.0.next() 14 | } 15 | } 16 | 17 | #[derive(Clone)] 18 | pub struct Surface { 19 | pub width: usize, 20 | pub height: usize, 21 | pub x_off: usize, 22 | pub y_off: usize, 23 | pub background: ColorRGBA, 24 | pub buffer: Vec>, 25 | } 26 | 27 | 28 | #[allow(dead_code)] 29 | impl Surface { 30 | pub fn new(width: usize, height: usize, background: ColorRGBA) -> Surface { 31 | Surface { 32 | width: width, 33 | height: height, 34 | x_off: 0, 35 | y_off: 0, 36 | background: background, 37 | buffer: repeat(background).take(width * height).collect() 38 | } 39 | } 40 | 41 | pub fn with_offset(width: usize, height: usize, x_off: usize, y_off: usize, 42 | background: ColorRGBA) -> Surface { 43 | Surface { 44 | width: width, 45 | height: height, 46 | x_off: x_off, 47 | y_off: y_off, 48 | background: background, 49 | buffer: repeat(background).take(width * height).collect() 50 | } 51 | } 52 | 53 | pub fn divide(&self, tile_width: usize, tile_height: usize) -> SubsurfaceIterator { 54 | SubsurfaceIterator { 55 | parent_width: self.width, 56 | parent_height: self.height, 57 | background: self.background, 58 | x_delta: tile_width, 59 | y_delta: tile_height, 60 | x_off: 0, 61 | y_off: 0, 62 | } 63 | } 64 | 65 | pub fn overrender_size(&self, tile_width: usize, tile_height: usize) -> (usize, usize) { 66 | let mut width = self.width; 67 | let width_partial_tile = width % tile_width; 68 | if width_partial_tile > 0 { 69 | width -= width_partial_tile; 70 | width += tile_width; 71 | } 72 | 73 | let mut height = self.height; 74 | let height_partial_tile = height % tile_height; 75 | if height_partial_tile > 0 { 76 | height -= height_partial_tile; 77 | height += tile_height; 78 | } 79 | 80 | (width, height) 81 | } 82 | 83 | pub fn merge(&mut self, tile: &Surface) { 84 | let x_len: usize = min(tile.width, self.width - tile.x_off); 85 | let y_len: usize = min(tile.height, self.height - tile.y_off); 86 | 87 | for src_y in 0..y_len { 88 | let dst_y = tile.y_off + src_y; 89 | for src_x in 0..x_len { 90 | let dst_x = tile.x_off + src_x; 91 | self[(dst_x, dst_y)] = tile[(src_x, src_y)] 92 | } 93 | } 94 | } 95 | 96 | #[inline] 97 | pub fn pixel_count(&self) -> usize { 98 | self.buffer.len() 99 | } 100 | 101 | #[inline] 102 | fn get_idx(&self, x: usize, y: usize) -> usize { 103 | if self.width <= x { 104 | panic!("`x` out of bounds (0 <= {} < {}", x, self.width); 105 | } 106 | if self.height <= y { 107 | panic!("`y` out of bounds (0 <= {} < {}", y, self.height); 108 | } 109 | self.width * y + x 110 | } 111 | 112 | pub fn iter_pixels_mut<'a>(&'a mut self) -> IterPixelMut<'a, u8> { 113 | IterPixelMut(self.buffer.iter_mut()) 114 | } 115 | } 116 | 117 | impl Index<(usize, usize)> for Surface { 118 | type Output = ColorRGBA; 119 | 120 | fn index<'a>(&'a self, index: (usize, usize)) -> &'a ColorRGBA { 121 | let (x, y) = index; 122 | let idx = self.get_idx(x, y); 123 | &self.buffer[idx] 124 | } 125 | } 126 | 127 | impl IndexMut<(usize, usize)> for Surface { 128 | fn index_mut<'a>(&'a mut self, index: (usize, usize)) -> &'a mut ColorRGBA { 129 | let (x, y) = index; 130 | let idx = self.get_idx(x, y); 131 | &mut self.buffer[idx] 132 | } 133 | } 134 | 135 | pub struct SubsurfaceIterator { 136 | x_delta: usize, 137 | x_off: usize, 138 | y_delta: usize, 139 | y_off: usize, 140 | parent_width: usize, 141 | parent_height: usize, 142 | background: ColorRGBA, 143 | } 144 | 145 | 146 | impl SubsurfaceIterator { 147 | fn incr_tile(&mut self) { 148 | if self.x_off + self.x_delta < self.parent_width { 149 | self.x_off += self.x_delta; 150 | } else { 151 | self.x_off = 0; 152 | self.y_off += self.y_delta; 153 | } 154 | } 155 | 156 | fn current_tile(&self) -> Option { 157 | if self.x_off < self.parent_width && self.y_off < self.parent_height { 158 | Some(SurfaceFactory::new( 159 | self.x_delta, 160 | self.y_delta, 161 | self.x_off, 162 | self.y_off, 163 | self.background 164 | )) 165 | } else { 166 | None 167 | } 168 | } 169 | } 170 | 171 | impl Iterator for SubsurfaceIterator { 172 | type Item = SurfaceFactory; 173 | 174 | fn next(&mut self) -> Option { 175 | let tile = self.current_tile(); 176 | self.incr_tile(); 177 | tile 178 | } 179 | } 180 | 181 | 182 | #[test] 183 | fn test_measurement() { 184 | let width = 800; 185 | let height = 600; 186 | let width_tile = 128; 187 | let height_tile = 8; 188 | 189 | let background: ColorRGBA = ColorRGBA::new_rgb(0, 0, 0); 190 | let surf: Surface = Surface::new(width, height, background); 191 | 192 | let mut total_pixels = 0; 193 | 194 | for tile_factory in surf.divide(width_tile, height_tile) { 195 | total_pixels += tile_factory.create().pixel_count(); 196 | } 197 | 198 | let (or_width, or_height) = surf.overrender_size(width_tile, height_tile); 199 | 200 | assert_eq!(or_width * or_height, total_pixels); 201 | } 202 | 203 | #[test] 204 | fn test_paint_it_red() { 205 | let width = 800; 206 | let height = 600; 207 | let width_tile = 128; 208 | let height_tile = 8; 209 | 210 | let background: ColorRGBA = ColorRGBA::new_rgb(0, 0, 0); 211 | let mut surf: Surface = Surface::new(width, height, background); 212 | 213 | for tile_factory in surf.divide(width_tile, height_tile) { 214 | let mut tile = tile_factory.create(); 215 | for y in 0..tile.height { 216 | for x in 0..tile.width { 217 | tile[(x, y)] = ColorRGBA::new_rgb(255, 0, 0); 218 | } 219 | } 220 | for y in 0..tile.height { 221 | for x in 0..tile.width { 222 | assert_eq!(tile[(x, y)].r, 255); 223 | assert_eq!(tile[(x, y)].g, 0); 224 | assert_eq!(tile[(x, y)].b, 0); 225 | } 226 | } 227 | surf.merge(&tile); 228 | } 229 | 230 | for y in 0..surf.height { 231 | for x in 0..surf.width { 232 | let color = surf[(x, y)]; 233 | if color.r != 255 { 234 | panic!("wrong pixel at {}x{}", x, y); 235 | } 236 | if color.g != 0 { 237 | panic!("wrong pixel at {}x{}", x, y); 238 | } 239 | if color.b != 0 { 240 | panic!("wrong pixel at {}x{}", x, y); 241 | } 242 | } 243 | } 244 | 245 | // Check the iterator too 246 | for color in surf.buffer.iter() { 247 | assert_eq!(color.r, 255); 248 | assert_eq!(color.g, 0); 249 | assert_eq!(color.b, 0); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/raytracer/compositor/surfacefactory.rs: -------------------------------------------------------------------------------- 1 | use raytracer::compositor::{ColorRGBA, Surface}; 2 | 3 | 4 | pub struct SurfaceFactory { 5 | pub width: usize, 6 | pub height: usize, 7 | pub x_off: usize, 8 | pub y_off: usize, 9 | pub background: ColorRGBA 10 | } 11 | 12 | 13 | impl SurfaceFactory { 14 | pub fn new(width: usize, height: usize, x_off: usize, y_off: usize, 15 | background: ColorRGBA) -> SurfaceFactory { 16 | SurfaceFactory { 17 | width: width, 18 | height: height, 19 | x_off: x_off, 20 | y_off: y_off, 21 | background: background 22 | } 23 | } 24 | 25 | #[allow(dead_code)] 26 | pub fn create(&self) -> Surface { 27 | Surface::with_offset(self.width, self.height, self.x_off, self.y_off, self.background) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/raytracer/compositor/surfaceiterator.rs: -------------------------------------------------------------------------------- 1 | use raytracer::compositor::{ColorRGBA, SurfaceFactory}; 2 | 3 | 4 | pub struct SurfaceIterator { 5 | x_delta: usize, 6 | x_off: usize, 7 | y_delta: usize, 8 | y_off: usize, 9 | parent_width: usize, 10 | parent_height: usize, 11 | background: ColorRGBA, 12 | } 13 | 14 | 15 | impl SurfaceIterator { 16 | fn incr_tile(&mut self) { 17 | if self.x_off + self.x_delta < self.parent_width { 18 | self.x_off += self.x_delta; 19 | } else { 20 | self.x_off = 0; 21 | self.y_off += self.y_delta; 22 | } 23 | } 24 | 25 | fn current_tile(&self) -> Option { 26 | if self.x_off < self.parent_width && self.y_off < self.parent_height { 27 | Some(SurfaceFactory::new( 28 | self.x_delta, 29 | self.y_delta, 30 | self.x_off, 31 | self.y_off, 32 | self.background 33 | )) 34 | } else { 35 | None 36 | } 37 | } 38 | } 39 | 40 | impl Iterator for SurfaceIterator { 41 | type Item = SurfaceFactory; 42 | 43 | fn next(&mut self) -> Option { 44 | let tile = self.current_tile(); 45 | self.incr_tile(); 46 | tile 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/raytracer/intersection.rs: -------------------------------------------------------------------------------- 1 | use material::Material; 2 | use vec3::Vec3; 3 | 4 | pub struct Intersection<'a> { 5 | pub n: Vec3, 6 | pub t: f64, 7 | pub u: f64, 8 | pub v: f64, 9 | pub position: Vec3, 10 | pub material: &'a Box 11 | } 12 | -------------------------------------------------------------------------------- /src/raytracer/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::animator::{Animator, CameraKeyframe}; 2 | pub use self::intersection::Intersection; 3 | pub use self::ray::Ray; 4 | pub use self::octree::Octree; 5 | pub use self::renderer::{Renderer, RenderOptions}; 6 | 7 | pub mod animator; 8 | pub mod compositor; 9 | pub mod intersection; 10 | pub mod octree; 11 | pub mod ray; 12 | pub mod renderer; 13 | -------------------------------------------------------------------------------- /src/raytracer/octree.rs: -------------------------------------------------------------------------------- 1 | use std::slice::Iter; 2 | use std::iter::FromIterator; 3 | use geometry::{BBox, PartialBoundingBox}; 4 | use raytracer::Ray; 5 | use vec3::Vec3; 6 | 7 | // 8 | 9 | pub struct Octree where T: PartialBoundingBox { 10 | prims: Vec, 11 | infinites: Vec, // for infinite prims (planes) 12 | root: OctreeNode, 13 | } 14 | 15 | impl FromIterator for Octree where T: PartialBoundingBox { 16 | fn from_iter(iterator: I) -> Self where I: IntoIterator { 17 | let iterator = iterator.into_iter(); 18 | 19 | let (finites, infinites): (Vec, Vec) = 20 | iterator.partition(|item| item.partial_bounding_box().is_some()); 21 | 22 | // TODO(sell): why do we need to map here? &T isn't PartialBoundingBox, 23 | // but we need to find out how to make it so. 24 | let bounds = BBox::from_union(finites.iter().map(|i| i.partial_bounding_box())) 25 | .unwrap_or(BBox::zero()); 26 | 27 | // pbrt recommended max depth for a k-d tree (though, we're using an octree) 28 | // For a k-d tree: 8 + 1.3 * log2(N) 29 | let depth = (1.2 * (finites.len() as f64).log(8.0)).round() as i32; 30 | 31 | println!("Octree maximum depth {}", depth); 32 | let mut root_node = OctreeNode::new(bounds, depth); 33 | for (i, prim) in finites.iter().enumerate() { 34 | root_node.insert(i, prim.partial_bounding_box().unwrap()); 35 | } 36 | 37 | Octree { 38 | prims: finites, 39 | infinites: infinites, 40 | root: root_node, 41 | } 42 | } 43 | } 44 | 45 | impl Octree where T: PartialBoundingBox { 46 | pub fn intersect_iter<'a>(&'a self, ray: &'a Ray) -> OctreeIterator<'a, T> { 47 | OctreeIterator::new(self, ray) 48 | } 49 | } 50 | 51 | pub struct OctreeNode { 52 | bbox: BBox, 53 | depth: i32, 54 | children: Vec, 55 | leaf_data: Vec, 56 | } 57 | 58 | #[derive(Clone, Copy)] 59 | struct OctreeData { 60 | pub bbox: BBox, 61 | pub index: usize 62 | } 63 | 64 | impl OctreeNode { 65 | #[allow(dead_code)] 66 | pub fn new(bbox: BBox, depth: i32) -> OctreeNode { 67 | OctreeNode { 68 | bbox: bbox, 69 | depth: depth, 70 | children: Vec::new(), 71 | leaf_data: Vec::new(), 72 | } 73 | } 74 | 75 | fn subdivide(&mut self) { 76 | for x in 0u32..2 { 77 | for y in 0u32..2 { 78 | for z in 0u32..2 { 79 | let len = self.bbox.len(); 80 | 81 | let child_bbox = BBox { 82 | min: Vec3 { 83 | x: self.bbox.min.x + x as f64 * len.x / 2.0, 84 | y: self.bbox.min.y + y as f64 * len.y / 2.0, 85 | z: self.bbox.min.z + z as f64 * len.z / 2.0 86 | }, 87 | max: Vec3 { 88 | x: self.bbox.max.x - (1 - x) as f64 * len.x / 2.0, 89 | y: self.bbox.max.y - (1 - y) as f64 * len.y / 2.0, 90 | z: self.bbox.max.z - (1 - z) as f64 * len.z / 2.0, 91 | } 92 | }; 93 | 94 | self.children.push(OctreeNode::new(child_bbox, self.depth - 1)); 95 | } 96 | } 97 | } 98 | } 99 | 100 | #[allow(dead_code)] 101 | pub fn insert(&mut self, index: usize, object_bbox: BBox) -> () { 102 | // Max depth 103 | if self.depth <= 0 { 104 | self.leaf_data.push(OctreeData { index: index, bbox: object_bbox }); 105 | return; 106 | } 107 | 108 | // Empty leaf node 109 | if self.is_leaf() && self.leaf_data.len() == 0 { 110 | self.leaf_data.push(OctreeData { index: index, bbox: object_bbox }); 111 | return; 112 | } 113 | 114 | // Occupied leaf node and not max depth: subdivide node 115 | if self.is_leaf() && self.leaf_data.len() == 1 { 116 | self.subdivide(); 117 | let old = self.leaf_data.remove(0); 118 | // Reinsert old node and then fall through to insert current object 119 | self.insert(old.index, old.bbox); 120 | } 121 | 122 | // Interior node (has children) 123 | for child in self.children.iter_mut() { 124 | if child.bbox.overlaps(&object_bbox) { 125 | child.insert(index, object_bbox); 126 | } 127 | } 128 | } 129 | 130 | fn is_leaf(&self) -> bool { 131 | self.children.len() == 0 132 | } 133 | } 134 | 135 | pub struct OctreeIterator<'a, T:'a> { 136 | prims: &'a [T], 137 | stack: Vec<&'a OctreeNode>, 138 | leaf_iter: Option>, 139 | ray: &'a Ray, 140 | infinites: Iter<'a, T>, 141 | just_infinites: bool 142 | } 143 | 144 | 145 | impl<'a, T> OctreeIterator<'a, T> where T: PartialBoundingBox { 146 | fn new<'b>(octree: &'b Octree, ray: &'b Ray) -> OctreeIterator<'b, T> { 147 | OctreeIterator { 148 | prims: &octree.prims[..], 149 | stack: vec![&octree.root], 150 | leaf_iter: None, 151 | ray: ray, 152 | infinites: octree.infinites.iter(), 153 | just_infinites: false 154 | } 155 | } 156 | } 157 | 158 | 159 | impl<'a, T> Iterator for OctreeIterator<'a, T> where T: PartialBoundingBox { 160 | type Item = &'a T; 161 | 162 | fn next(&mut self) -> Option<&'a T> { 163 | if self.just_infinites { 164 | return self.infinites.next(); 165 | } 166 | 167 | loop { 168 | let ray = self.ray; 169 | if let Some(leaf_iter) = self.leaf_iter.as_mut() { 170 | if let Some(val) = leaf_iter.filter(|x| x.bbox.intersects(ray)).next() { 171 | return Some(&self.prims[val.index]); 172 | } 173 | // iterator went empty, so we'll pop from the stack and 174 | // iterate on the next node's children now, 175 | } 176 | 177 | if let Some(node) = self.stack.pop() { 178 | for child in node.children.iter() { 179 | if child.bbox.intersects(self.ray) { 180 | self.stack.push(child); 181 | } 182 | } 183 | self.leaf_iter = Some(node.leaf_data.iter()); 184 | } else { 185 | self.just_infinites = true; 186 | return self.infinites.next() 187 | } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/raytracer/ray.rs: -------------------------------------------------------------------------------- 1 | use std::f64::INFINITY; 2 | use raytracer::Intersection; 3 | use scene::Scene; 4 | use vec3::Vec3; 5 | 6 | #[cfg(test)] 7 | use geometry::prim::Prim; 8 | #[cfg(test)] 9 | use geometry::prims::Sphere; 10 | #[cfg(test)] 11 | use light::light::Light; 12 | #[cfg(test)] 13 | use material::materials::FlatMaterial; 14 | 15 | pub struct Ray { 16 | pub origin: Vec3, 17 | pub direction: Vec3, 18 | pub inverse_dir: Vec3, // This is used to optimise ray-bbox intersection checks 19 | pub signs: [bool; 3], // Handle degenerate case in bbox intersection 20 | } 21 | 22 | impl Ray { 23 | pub fn new(origin: Vec3, direction: Vec3) -> Ray { 24 | let inv_x = 1.0 / direction.x; 25 | let inv_y = 1.0 / direction.y; 26 | let inv_z = 1.0 / direction.z; 27 | 28 | Ray { 29 | origin: origin, 30 | direction: direction, 31 | inverse_dir: Vec3 { 32 | x: inv_x, 33 | y: inv_y, 34 | z: inv_z 35 | }, 36 | signs: [ 37 | inv_x > 0.0, 38 | inv_y > 0.0, 39 | inv_z > 0.0 40 | ] 41 | } 42 | } 43 | 44 | pub fn get_nearest_hit<'a>(&'a self, scene: &'a Scene) -> Option> { 45 | let t_min = 0.000001; 46 | let mut nearest_hit = None; 47 | let mut nearest_t = INFINITY; 48 | 49 | for prim in scene.octree.intersect_iter(self) { 50 | let intersection = prim.intersects(self, t_min, nearest_t); 51 | 52 | nearest_hit = match intersection { 53 | Some(intersection) => { 54 | if intersection.t > t_min && intersection.t < nearest_t { 55 | nearest_t = intersection.t; 56 | Some(intersection) 57 | } else { 58 | nearest_hit 59 | } 60 | }, 61 | None => nearest_hit 62 | }; 63 | } 64 | 65 | nearest_hit 66 | } 67 | 68 | pub fn perturb(&self, magnitude: f64) -> Ray { 69 | let rand_vec = Vec3::random() * magnitude; 70 | 71 | // Force random vectors to be in same direction as original vector 72 | let corrected_rand_vec = if rand_vec.dot(&self.direction) < 0.0 { 73 | rand_vec * -1.0 74 | } else { 75 | rand_vec 76 | }; 77 | 78 | let direction = (corrected_rand_vec + self.direction).unit(); 79 | 80 | Ray::new(self.origin, direction) 81 | } 82 | } 83 | 84 | #[test] 85 | fn it_gets_the_nearest_hit() { 86 | let lights: Vec> = Vec::new(); 87 | 88 | let mut prims: Vec> = Vec::new(); 89 | let mat = FlatMaterial { color: Vec3::one() }; 90 | let sphere_top = Sphere { 91 | center: Vec3::zero(), 92 | radius: 1.0, 93 | material: Box::new(mat.clone()), 94 | }; 95 | let sphere_mid = Sphere { 96 | center: Vec3 { x: -1.0, y: 0.0, z: 0.0 }, 97 | radius: 1.0, 98 | material: Box::new(mat.clone()), 99 | }; 100 | let sphere_bot = Sphere { 101 | center: Vec3 { x: -2.0, y: 0.0, z: 0.0 }, 102 | radius: 1.0, 103 | material: Box::new(mat.clone()), 104 | }; 105 | prims.push(Box::new(sphere_top)); 106 | prims.push(Box::new(sphere_mid)); 107 | prims.push(Box::new(sphere_bot)); 108 | 109 | println!("Generating octree..."); 110 | let octree = prims.into_iter().collect(); 111 | println!("Octree generated..."); 112 | 113 | let scene = Scene { 114 | lights: lights, 115 | background: Vec3::one(), 116 | octree: octree, 117 | skybox: None 118 | }; 119 | 120 | let intersecting_ray = Ray::new( 121 | Vec3 { x: 10.0, y: 0.0, z: 0.0 }, 122 | Vec3 { x: -1.0, y: 0.0, z: 0.0 } 123 | ); 124 | 125 | let intersection = intersecting_ray.get_nearest_hit(&scene); 126 | assert_eq!(1.0, intersection.unwrap().position.x); 127 | 128 | let non_intersecting_ray = Ray::new( 129 | Vec3 { x: 10.0, y: 0.0, z: 0.0 }, 130 | Vec3 { x: 1.0, y: 0.0, z: 0.0 }); 131 | 132 | let non_intersection = non_intersecting_ray.get_nearest_hit(&scene); 133 | assert!(non_intersection.is_none()); 134 | } 135 | -------------------------------------------------------------------------------- /src/raytracer/renderer.rs: -------------------------------------------------------------------------------- 1 | use light::Light; 2 | use raytracer::compositor::{ColorRGBA, Surface, SurfaceFactory}; 3 | use raytracer::{Intersection, Ray}; 4 | use scene::{Camera, Scene}; 5 | use std::ops::Deref; 6 | use std::sync::Arc; 7 | use std::sync::mpsc::channel; 8 | use vec3::Vec3; 9 | use rand::{thread_rng, Rng}; 10 | use threadpool::ThreadPool; 11 | 12 | pub static EPSILON: f64 = ::std::f64::EPSILON * 10000.0; 13 | 14 | #[derive(Clone, Copy)] 15 | pub struct RenderOptions { 16 | pub reflect_depth: u32, // Maximum reflection recursions. 17 | pub refract_depth: u32, // Maximum refraction recursions. A sphere takes up 2 recursions. 18 | pub shadow_samples: u32, // Number of samples for soft shadows and area lights. 19 | pub gloss_samples: u32, // Number of samples for glossy reflections. 20 | pub pixel_samples: u32, // The square of this is the number of samples per pixel. 21 | } 22 | 23 | #[derive(Clone)] 24 | pub struct Renderer { 25 | pub tasks: usize, // Minimum number of tasks to spawn. 26 | pub options: RenderOptions, 27 | } 28 | 29 | impl Renderer { 30 | pub fn render(&self, camera: Camera, shared_scene: Arc) -> Surface { 31 | 32 | let mut surface = Surface::new(camera.image_width as usize, 33 | camera.image_height as usize, 34 | ColorRGBA::new_rgb(0, 0, 0)); 35 | 36 | let pool = ThreadPool::new(self.tasks); 37 | 38 | let (tx, rx) = channel(); 39 | 40 | let mut jobs = 0; 41 | 42 | for subsurface_factory in surface.divide(128, 8) { 43 | jobs += 1; 44 | 45 | let renderer_opts = self.options.clone(); 46 | let child_tx = tx.clone(); 47 | let scene_local = shared_scene.clone(); 48 | let camera_local = camera.clone(); 49 | 50 | pool.execute(move || { 51 | let scene = scene_local.deref(); 52 | let _ = child_tx.send(Renderer::render_tile(camera_local, scene, 53 | renderer_opts, subsurface_factory)).unwrap(); 54 | }); 55 | } 56 | drop(tx); 57 | 58 | let start_time = ::time::get_time(); 59 | 60 | for (i, subsurface) in rx.iter().enumerate() { 61 | surface.merge(&subsurface); 62 | ::util::print_progress("Tile", start_time.clone(), (i + 1) as usize, jobs); 63 | } 64 | surface 65 | } 66 | 67 | fn render_tile(camera: Camera, scene: &Scene, options: RenderOptions, tile_factory: SurfaceFactory) -> Surface { 68 | let mut tile = tile_factory.create(); 69 | let mut rng = thread_rng(); 70 | let pixel_samples = options.pixel_samples; 71 | 72 | for rel_y in 0usize..tile.height { 73 | let abs_y = camera.image_height as usize - (tile.y_off + rel_y) - 1; 74 | for rel_x in 0usize..tile.width { 75 | let abs_x = tile.x_off + rel_x; 76 | 77 | // Supersampling, jitter algorithm 78 | let pixel_width = 1.0 / pixel_samples as f64; 79 | let mut color = Vec3::zero(); 80 | 81 | for y_subpixel in 0u32..pixel_samples { 82 | for x_subpixel in 0u32..pixel_samples { 83 | // Don't jitter if not antialiasing 84 | let (j_x, j_y) = if pixel_samples > 1 { 85 | (x_subpixel as f64 * pixel_width + rng.gen::() * pixel_width, 86 | y_subpixel as f64 * pixel_width + rng.gen::() * pixel_width) 87 | } else { 88 | (0.0, 0.0) 89 | }; 90 | 91 | let ray = camera.get_ray(abs_x as f64 + j_x, abs_y as f64 + j_y); 92 | let result = Renderer::trace(scene, &ray, options, false); 93 | // Clamp subpixels for now to avoid intense aliasing when combined value is clamped later 94 | // Should think of a better way to handle this 95 | color = color + result.clamp(0.0, 1.0).scale(1.0 / (pixel_samples * pixel_samples) as f64); 96 | } 97 | } 98 | tile[(rel_x, rel_y)] = ColorRGBA::new_rgb_clamped(color.x, color.y, color.z); 99 | } 100 | } 101 | 102 | tile 103 | } 104 | 105 | fn trace(scene: &Scene, ray: &Ray, options: RenderOptions, inside: bool) -> Vec3 { 106 | if options.reflect_depth <= 0 || options.refract_depth <= 0 { return Vec3::zero() } 107 | 108 | match ray.get_nearest_hit(scene) { 109 | Some(hit) => { 110 | let n = hit.n.unit(); 111 | let i = (-ray.direction).unit(); 112 | 113 | // Local lighting computation: surface shading, shadows 114 | let mut result = scene.lights.iter().fold(Vec3::zero(), |color_acc, light| { 115 | let shadow = Renderer::shadow_intensity(scene, &hit, light, options.shadow_samples); 116 | let l = (light.center() - hit.position).unit(); 117 | 118 | color_acc + light.color() * hit.material.sample(n, i, l, hit.u, hit.v) * shadow 119 | }); 120 | 121 | // Global lighting computation: reflections, refractions 122 | if hit.material.is_reflective() || hit.material.is_refractive() { 123 | let reflect_fresnel = Renderer::fresnel_reflect(hit.material.ior(), &i, &n, inside); 124 | let refract_fresnel = 1.0 - reflect_fresnel; 125 | 126 | if hit.material.is_reflective() { 127 | result = result + Renderer::global_reflection(scene, &hit, options, inside, 128 | &i, &n, reflect_fresnel); 129 | } 130 | 131 | if hit.material.is_refractive() { 132 | result = result + Renderer::global_transmission(scene, &hit, options, inside, 133 | &i, &n, refract_fresnel); 134 | } 135 | } 136 | 137 | result 138 | }, 139 | None => { 140 | match scene.skybox { 141 | Some(ref skybox) => skybox.color(ray.direction), 142 | None => scene.background 143 | } 144 | } 145 | } 146 | } 147 | 148 | fn global_reflection(scene: &Scene, hit: &Intersection, options: RenderOptions, inside: bool, 149 | i: &Vec3, n: &Vec3, reflect_fresnel: f64) -> Vec3 { 150 | 151 | let r = Vec3::reflect(&i, &n); 152 | let reflect_ray = Ray::new(hit.position, r); 153 | let next_reflect_options = RenderOptions { reflect_depth: options.reflect_depth - 1, ..options }; 154 | 155 | let reflection = if hit.material.is_glossy() { 156 | // For glossy materials, average multiple perturbed reflection rays 157 | // Potential overflow by scaling after everything is done instead of scaling every iteration? 158 | (0..options.gloss_samples).fold(Vec3::zero(), |acc, _| { 159 | let gloss_reflect_ray = reflect_ray.perturb(hit.material.glossiness()); 160 | acc + Renderer::trace(scene, &gloss_reflect_ray, next_reflect_options, inside) 161 | }).scale(1.0 / options.gloss_samples as f64) 162 | } else { 163 | // For mirror-like materials just shoot a perfectly reflected ray instead 164 | Renderer::trace(scene, &reflect_ray, next_reflect_options, inside) 165 | }; 166 | 167 | hit.material.global_specular(&reflection).scale(reflect_fresnel) 168 | } 169 | 170 | fn global_transmission(scene: &Scene, hit: &Intersection, options: RenderOptions, inside: bool, 171 | i: &Vec3, n: &Vec3, refract_fresnel: f64) -> Vec3 { 172 | 173 | let (t, actual_refract_fresnel) = match Vec3::refract(&i, &n, hit.material.ior(), inside) { 174 | Some(ref t) => (*t, refract_fresnel), 175 | None => { 176 | (Vec3::reflect(&i, &n), 1.0) // Fresnel of 1.0 = total internal reflection (TODO: verify) 177 | } 178 | }; 179 | 180 | // Offset ray origin by EPSILON * direction to avoid hitting self when refracting 181 | let refract_ray = Ray::new(hit.position + t.scale(EPSILON), t); 182 | let next_refract_options = RenderOptions { refract_depth: options.refract_depth - 1, ..options }; 183 | let refraction = Renderer::trace(scene, &refract_ray, next_refract_options, !inside); 184 | 185 | hit.material.global_transmissive(&refraction).scale(actual_refract_fresnel) 186 | } 187 | 188 | fn shadow_intensity(scene: &Scene, hit: &Intersection, 189 | light: &Box, shadow_samples: u32) -> Vec3 { 190 | 191 | if shadow_samples <= 0 { return Vec3::one() } 192 | 193 | // Point light speedup (no point in sampling a point light multiple times) 194 | let shadow_sample_tries = if light.is_point() { 1 } else { shadow_samples }; 195 | let mut shadow = Vec3::zero(); 196 | 197 | // Take average shadow color after jittering/sampling light position 198 | for _ in 0..shadow_sample_tries { 199 | // L has to be a unit vector for t_max 1:1 correspondence to 200 | // distance to light to work. Shadow feelers only search up 201 | // until light source. 202 | let sampled_light_position = light.position(); 203 | let shadow_l = (sampled_light_position - hit.position).unit(); 204 | let shadow_ray = Ray::new(hit.position, shadow_l); 205 | let distance_to_light = (sampled_light_position - hit.position).len(); 206 | 207 | // Check against candidate primitives in scene for occlusion 208 | // and multiply shadow color by occluders' shadow colors 209 | let candidate_nodes = scene.octree.intersect_iter(&shadow_ray); 210 | 211 | shadow = shadow + candidate_nodes.fold(Vec3::one(), |shadow_acc, prim| { 212 | let occlusion = prim.intersects(&shadow_ray, EPSILON, distance_to_light); 213 | match occlusion { 214 | Some(occlusion) => shadow_acc * occlusion.material.transmission(), 215 | None => shadow_acc 216 | } 217 | }); 218 | } 219 | 220 | shadow.scale(1.0 / shadow_sample_tries as f64) 221 | } 222 | 223 | /// Calculates the fresnel (reflectivity) given the index of refraction and the cos_angle 224 | /// This uses Schlick's approximation. cos_angle is normal_dot_incoming 225 | /// http://graphics.stanford.edu/courses/cs148-10-summer/docs/2006--degreve--reflection_refraction.pdf 226 | fn fresnel_reflect(ior: f64, i: &Vec3, n: &Vec3, inside: bool) -> f64 { 227 | let (n1, n2) = if inside { (ior, 1.0) } else { (1.0, ior) }; 228 | let actual_n = if inside { -*n } else { *n }; 229 | 230 | let r0_sqrt = (n1 - n2) / (n1 + n2); 231 | let r0 = r0_sqrt * r0_sqrt; 232 | 233 | let cos_angle = if n1 <= n2 { 234 | i.dot(&actual_n) 235 | } else { 236 | let t = match Vec3::refract(i, &-actual_n, ior, inside) { 237 | Some(x) => x, 238 | None => return 1.0 // n1 > n2 && TIR 239 | }; 240 | 241 | -actual_n.dot(&t) // n1 > n2 && !TIR 242 | }; 243 | 244 | let cos_term = 1.0 - cos_angle; 245 | 246 | (r0 + ((1.0 - r0) * cos_term * cos_term * cos_term * cos_term * cos_term)).max(0.0).min(1.0) 247 | } 248 | } 249 | 250 | #[test] 251 | fn it_renders_the_background_of_an_empty_scene() { 252 | let camera = Camera::new( 253 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 254 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 255 | Vec3 { x: 0.0, y: 0.0, z: 1.0 }, 256 | 45.0, 257 | 32, 258 | 32 259 | ); 260 | 261 | let test_scene = Scene { 262 | lights: vec!(), 263 | octree: vec!().into_iter().collect(), 264 | background: Vec3 { x: 1.0, y: 0.0, z: 0.0 }, 265 | skybox: None 266 | }; 267 | 268 | let shared_scene = Arc::new(test_scene); 269 | 270 | let render_options = RenderOptions { 271 | reflect_depth: 1, 272 | refract_depth: 1, 273 | shadow_samples: 1, 274 | gloss_samples: 1, 275 | pixel_samples: 1, 276 | }; 277 | 278 | 279 | let renderer = Renderer { 280 | options: render_options, 281 | tasks: 2, 282 | }; 283 | 284 | let image_data = renderer.render(camera, shared_scene); 285 | 286 | for color in image_data.buffer.iter() { 287 | assert_eq!(color.r, 255); 288 | assert_eq!(color.g, 0); 289 | assert_eq!(color.b, 0); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/scene/camera.rs: -------------------------------------------------------------------------------- 1 | use raytracer::Ray; 2 | use raytracer::animator::CameraKeyframe; 3 | use raytracer::animator::easing::Easing; 4 | use vec3::Vec3; 5 | 6 | #[derive(Clone)] 7 | pub struct Camera { 8 | pub position: Vec3, 9 | pub look_at: Vec3, 10 | pub up: Vec3, 11 | pub fov_deg: f64, 12 | pub image_width: u32, 13 | pub image_height: u32, 14 | 15 | pub eye: Vec3, 16 | pub right: Vec3, 17 | pub half_width: f64, 18 | pub half_height: f64, 19 | pub pixel_width: f64, 20 | pub pixel_height: f64, 21 | 22 | pub keyframes: Option> 23 | } 24 | 25 | impl Camera { 26 | pub fn new(position: Vec3, look_at: Vec3, up: Vec3, fov_deg: f64, 27 | image_width: u32, image_height: u32) 28 | -> Camera { 29 | 30 | let mut camera = Camera { 31 | position: position, 32 | look_at: look_at, 33 | up: up, 34 | fov_deg: fov_deg, 35 | image_width: image_width, 36 | image_height: image_height, 37 | eye: Vec3::zero(), 38 | right: Vec3::zero(), 39 | half_width: 0.0, 40 | half_height: 0.0, 41 | pixel_width: 0.0, 42 | pixel_height: 0.0, 43 | keyframes: None 44 | }; 45 | 46 | camera.update_eye_vector(); 47 | camera.update_internal_sizes(); 48 | 49 | camera 50 | } 51 | 52 | #[allow(dead_code)] 53 | pub fn new_with_keyframes(position: Vec3, look_at: Vec3, up: Vec3, fov_deg: f64, 54 | image_width: u32, image_height: u32, keyframes: Vec) 55 | -> Camera { 56 | 57 | let mut camera = Camera::new(position, look_at, up, fov_deg, image_width, image_height); 58 | camera.insert_keyframes(keyframes); 59 | camera 60 | } 61 | 62 | pub fn get_ray(&self, x: f64, y: f64) -> Ray { 63 | Ray::new( 64 | self.position, 65 | (self.eye + self.right.scale(x * self.pixel_width - self.half_width) + 66 | self.up.scale(y * self.pixel_height - self.half_height)).unit() 67 | ) 68 | } 69 | 70 | /// Add additional keyframes to the camera. The current state of the camera 71 | /// is treated as t=0, and a new keyframe at t=0 is created and added. 72 | #[allow(dead_code)] 73 | pub fn insert_keyframes(&mut self, additional_keyframes: Vec) { 74 | let t0_keyframe = CameraKeyframe { 75 | time: 0.0, 76 | position: self.position, 77 | look_at: self.look_at, 78 | up: self.up, 79 | easing: Easing::linear() 80 | }; 81 | 82 | let mut keyframes = vec![t0_keyframe]; 83 | keyframes.extend(additional_keyframes); 84 | 85 | self.keyframes = Some(keyframes); 86 | } 87 | 88 | fn update_eye_vector(&mut self) { 89 | self.eye = (self.look_at - self.position).unit(); 90 | self.right = self.eye.cross(&self.up); 91 | } 92 | 93 | fn update_internal_sizes(&mut self) { 94 | let fov_rad = self.fov_deg.to_radians(); 95 | let ratio = self.image_height as f64 / self.image_width as f64; 96 | 97 | self.half_width = fov_rad.tan(); 98 | self.half_height = self.half_width * ratio; 99 | 100 | let camera_width = self.half_width * 2.0; 101 | let camera_height = self.half_height * 2.0; 102 | 103 | self.pixel_width = camera_width / (self.image_width - 1) as f64; 104 | self.pixel_height = camera_height / (self.image_height - 1) as f64; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/scene/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::camera::Camera; 2 | pub use self::scene::Scene; 3 | 4 | pub mod camera; 5 | pub mod scene; 6 | -------------------------------------------------------------------------------- /src/scene/scene.rs: -------------------------------------------------------------------------------- 1 | use light::Light; 2 | use material::textures::CubeMap; 3 | use geometry::Prim; 4 | use raytracer::Octree; 5 | use vec3::Vec3; 6 | 7 | pub struct Scene { 8 | pub lights: Vec>, 9 | pub octree: Octree>, 10 | pub background: Vec3, 11 | pub skybox: Option 12 | } 13 | -------------------------------------------------------------------------------- /src/util/export.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{self, Write}; 3 | use raytracer::compositor::{Surface, Channel}; 4 | 5 | pub fn to_ppm(surface: &Surface, filename: &str) -> io::Result<()> { 6 | let channel_max: u8 = Channel::max_value(); 7 | let header = format!( 8 | "P3 {} {} {}\n", surface.width, surface.height, 9 | channel_max); 10 | 11 | let mut f = File::create(filename)?; 12 | 13 | f.write_all(header.as_bytes())?; 14 | for pixel in &surface.buffer { 15 | f.write_all(format!("{} {} {} ", pixel.r, pixel.g, pixel.b).as_bytes())?; 16 | } 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /src/util/import.rs: -------------------------------------------------------------------------------- 1 | use geometry::prims::TriangleOptions; 2 | use geometry::{Mesh, Prim}; 3 | use material::materials::CookTorranceMaterial; 4 | use raytracer::compositor::{Surface, ColorRGBA}; 5 | use std::fs::File; 6 | use std::path::Path; 7 | use std::io::{BufRead, BufReader}; 8 | use vec3::Vec3; 9 | 10 | /// This is limited to only CookTorranceMaterials, as I couldn't get a Box to clone 11 | /// a new material for each triangle primitive in the object model. 12 | pub fn from_obj(material: CookTorranceMaterial, flip_normals: bool, filename: &str) -> Result { 13 | let file_handle = match File::open(&filename) { 14 | Ok(f) => f, 15 | Err(err) => return Err(format!("{}", err)) 16 | }; 17 | 18 | let total_bytes = match file_handle.metadata() { 19 | Ok(metadata) => metadata.len(), 20 | Err(err) => return Err(format!("{}", err)) 21 | }; 22 | 23 | let file = BufReader::new(file_handle); 24 | 25 | let start_time = ::time::get_time(); 26 | let print_every = 2048; 27 | let mut processed_bytes = 0; 28 | 29 | let mut vertices: Vec = Vec::new(); 30 | let mut normals : Vec = Vec::new(); 31 | let mut triangles: Vec> = Vec::new(); 32 | let mut tex_coords: Vec> = Vec::new(); 33 | let mut current_line = 0; 34 | let normal_scale = if flip_normals { -1.0 } else { 1.0 }; 35 | 36 | for line_res in file.lines() { 37 | let line = line_res.unwrap(); 38 | let tokens: Vec<&str> = line[..].split_whitespace().collect(); 39 | if tokens.is_empty() { 40 | continue; 41 | } 42 | 43 | match tokens[0] { 44 | "v" => { 45 | vertices.push(Vec3 { 46 | x: tokens[1].parse().unwrap(), 47 | y: tokens[2].parse().unwrap(), 48 | z: tokens[3].parse().unwrap() 49 | }); 50 | }, 51 | "vt" => { 52 | tex_coords.push(vec![ 53 | tokens[1].parse().unwrap(), 54 | tokens[2].parse().unwrap() 55 | ]); 56 | }, 57 | "vn" => { 58 | normals.push(Vec3 { 59 | x: tokens[1].parse::().unwrap() * normal_scale, 60 | y: tokens[2].parse::().unwrap() * normal_scale, 61 | z: tokens[3].parse::().unwrap() * normal_scale 62 | }); 63 | }, 64 | "f" => { 65 | // ["f", "1/2/3", "2/2/2", "12//4"] => [[1, 2, 3], [2, 2, 2], [12, -1u, 4]] 66 | let tail = match tokens.split_first() { 67 | Some((_, tail)) => tail, 68 | None => return Err("Face syntax of OBJ not supported or malformed".to_owned()) 69 | }; 70 | 71 | let pairs: Vec> = tail.iter().map( |token| { 72 | let str_tokens: Vec<&str> = token.split('/').collect(); 73 | str_tokens.iter().map( |str_tok| { 74 | match str_tok.parse::().ok() { 75 | Some(usize_tok) => usize_tok - 1, // Have to offset as OBJ is 1-indexed 76 | None => !0 // No data available/not supplied (eg. `//` as a token) 77 | } 78 | }).collect() 79 | }).collect(); 80 | 81 | // If no texture coordinates were supplied, default to zero. 82 | // We stored nothing supplied as !0 83 | let (u, v) = if pairs[0][1] != !0 { 84 | (vec![ 85 | tex_coords[pairs[0][1]][0], 86 | tex_coords[pairs[1][1]][0], 87 | tex_coords[pairs[2][1]][0] 88 | ], 89 | vec![ 90 | tex_coords[pairs[0][1]][1], 91 | tex_coords[pairs[1][1]][1], 92 | tex_coords[pairs[2][1]][1] 93 | ]) 94 | } else { 95 | (vec![0.0, 0.0, 0.0], 96 | vec![0.0, 0.0, 0.0]) 97 | }; 98 | 99 | let mut triopts = TriangleOptions::new( 100 | vertices[pairs[0][0]], 101 | vertices[pairs[1][0]], 102 | vertices[pairs[2][0]]); 103 | 104 | triopts.material(Box::new(material.clone())); 105 | triopts.normals([ 106 | normals[pairs[0][2]], 107 | normals[pairs[1][2]], 108 | normals[pairs[2][2]], 109 | ]); 110 | triopts.texinfo([(u[0], v[0]), (u[1], v[1]), (u[2], v[2])]); 111 | 112 | triangles.push(Box::new(triopts.build())); 113 | }, 114 | _ => {} 115 | } 116 | 117 | current_line += 1; 118 | processed_bytes += line.as_bytes().len(); 119 | if current_line % print_every == 0 { 120 | ::util::print_progress("Bytes", start_time, processed_bytes, total_bytes as usize); 121 | } 122 | } 123 | 124 | // Cheat the progress meter 125 | ::util::print_progress("Bytes", start_time, total_bytes as usize, total_bytes as usize); 126 | 127 | Ok(Mesh { triangles: triangles }) 128 | } 129 | 130 | pub fn from_image>(path: P) -> Result { 131 | let image = match ::image::open(path) { 132 | Ok(image) => image.to_rgba(), 133 | Err(err) => return Err(format!("{}", err)) 134 | }; 135 | 136 | let mut surface = Surface::new(image.width() as usize, 137 | image.height() as usize, 138 | ColorRGBA::transparent()); 139 | 140 | for (src, dst_pixel) in image.pixels().zip(surface.iter_pixels_mut()) { 141 | *dst_pixel = ColorRGBA::new_rgba(src[0], src[1], src[2], src[3]); 142 | } 143 | 144 | Ok(surface) 145 | } 146 | 147 | #[test] 148 | pub fn test_obj_loads_correct_number_of_triangles() { 149 | let material: CookTorranceMaterial = Default::default(); 150 | let mesh = from_obj(material, false, "test/res/cube.obj") 151 | .ok().expect("failed to laod test obj `test/res/cube.obj`"); 152 | 153 | assert_eq!(mesh.triangles.len(), 12); 154 | } 155 | 156 | #[test] 157 | pub fn test_from_png24() { 158 | let surface = from_image("test/res/png24.png") 159 | .ok().expect("failed to load test image `test/res/png24.png`"); 160 | 161 | let expected_image: [[(u8, u8, u8, u8); 10]; 2] = [[ 162 | (0, 0, 0, 255), (1, 1, 1, 255), (2, 2, 2, 255), 163 | (3, 3, 3, 255), (4, 4, 4, 255), (5, 5, 5, 255), 164 | (6, 6, 6, 255), (7, 7, 7, 255), (8, 8, 8, 255), 165 | (9, 9, 9, 255) 166 | ], [ 167 | (255, 0, 0, 255), (255, 0, 0, 127), (255, 0, 0, 0), 168 | (0, 255, 0, 255), (0, 255, 0, 127), (0, 255, 0, 0), 169 | (0, 0, 255, 255), (0, 0, 255, 127), (0, 0, 255, 0), 170 | (0, 0, 0, 0) 171 | ]]; 172 | 173 | for y in 0..1 { 174 | for x in 0..9 { 175 | let pixel = surface[(x, y)]; 176 | let expected = expected_image[y][x]; 177 | assert_eq!(expected, (pixel.r, pixel.g, pixel.b, pixel.a)); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | use std::iter::repeat; 2 | use std::io::Write; 3 | 4 | pub mod export; 5 | pub mod import; 6 | 7 | pub fn print_progress(noun: &str, start_time: ::time::Timespec, done: usize, total: usize) { 8 | let remaining_jobs = total - done; 9 | let progress: f64 = 100f64 * done as f64 / total as f64; 10 | let current_time = ::time::get_time().sec; 11 | let time_per_job = (current_time - start_time.sec) as f64 / done as f64; 12 | let remaining_time = time_per_job * remaining_jobs as f64; 13 | 14 | print!("\r{} {}/{} complete\t{:.2}% [{}]", 15 | noun, done, total, progress, 16 | ::util::make_progress_bar(progress / 100.0, 20) 17 | ); 18 | 19 | if remaining_jobs == 0 { 20 | println!(" (took {:.2} min) ", (current_time - start_time.sec) as f64 / 60.0); 21 | } else { 22 | print!(" ETA {:.2} min ", remaining_time / 60.0); 23 | ::std::io::stdout().flush().expect("failed to flush io"); 24 | } 25 | } 26 | 27 | fn make_progress_bar(ratio: f64, length: usize) -> String { 28 | let filled = (ratio * length as f64).round() as usize; 29 | let mut pbar: String = repeat('|').take(filled).collect(); 30 | 31 | for _ in 0..(length - filled) { 32 | pbar.push('-'); 33 | } 34 | 35 | pbar 36 | } 37 | -------------------------------------------------------------------------------- /src/vec3.rs: -------------------------------------------------------------------------------- 1 | use rand::{thread_rng, Rng}; 2 | use std::cmp; 3 | use std::fmt; 4 | use std::ops::{Add, Div, Mul, Neg, Sub}; 5 | 6 | #[derive(Clone, Copy, Default)] 7 | pub struct Vec3 { 8 | pub x: f64, 9 | pub y: f64, 10 | pub z: f64, 11 | } 12 | 13 | impl Vec3 { 14 | pub fn zero() -> Vec3 { 15 | Vec3 { 16 | x: 0.0, 17 | y: 0.0, 18 | z: 0.0, 19 | } 20 | } 21 | 22 | pub fn one() -> Vec3 { 23 | Vec3 { 24 | x: 1.0, 25 | y: 1.0, 26 | z: 1.0, 27 | } 28 | } 29 | 30 | pub fn len(&self) -> f64 { 31 | (self.x * self.x + self.y * self.y + self.z * self.z).sqrt() 32 | } 33 | 34 | pub fn dot(&self, other: &Vec3) -> f64 { 35 | self.x * other.x + self.y * other.y + self.z * other.z 36 | } 37 | 38 | pub fn cross(&self, other: &Vec3) -> Vec3 { 39 | Vec3 { 40 | x: self.y * other.z - self.z * other.y, 41 | y: self.z * other.x - self.x * other.z, 42 | z: self.x * other.y - self.y * other.x, 43 | } 44 | } 45 | 46 | pub fn unit(&self) -> Vec3 { 47 | let len = self.len(); 48 | 49 | Vec3 { 50 | x: self.x / len, 51 | y: self.y / len, 52 | z: self.z / len, 53 | } 54 | } 55 | 56 | pub fn scale(&self, scalar: f64) -> Vec3 { 57 | Vec3 { 58 | x: self.x * scalar, 59 | y: self.y * scalar, 60 | z: self.z * scalar, 61 | } 62 | } 63 | 64 | /// V, N should be unit vectors 65 | /// 66 | /// ^ ^ 67 | /// V \ | N 68 | /// \| 69 | /// ========= 70 | pub fn reflect(v: &Vec3, n: &Vec3) -> Vec3 { 71 | n.scale(2.0 * (n.dot(v))) - *v 72 | } 73 | 74 | /// V, N should be unit vectors 75 | /// ior: Refractive index 76 | /// inside: Is the ray inside an object (ie. going out of an object)? 77 | pub fn refract(v: &Vec3, n: &Vec3, ior: f64, inside: bool) -> Option { 78 | let (n1, n2, n_dot_v, nn): (f64, f64, _, _) = if !inside { 79 | (1.0, ior, n.dot(v), *n) 80 | } else { 81 | (ior, 1.0, -n.dot(v), -*n) 82 | }; 83 | 84 | let ratio = n1 / n2; 85 | let disc = 1.0 - ((ratio * ratio) * (1.0 - n_dot_v * n_dot_v)); 86 | 87 | if disc < 0.0 { 88 | None // Total internal reflection 89 | } else { 90 | Some(v.scale(-ratio) + nn.scale(ratio * n_dot_v - disc.sqrt())) 91 | } 92 | } 93 | 94 | pub fn lerp(v1: &Vec3, v2: &Vec3, alpha: f64) -> Vec3 { 95 | Vec3 { 96 | x: v1.x + (v2.x - v1.x) * alpha, 97 | y: v1.y + (v2.y - v1.y) * alpha, 98 | z: v1.z + (v2.z - v1.z) * alpha, 99 | } 100 | } 101 | 102 | pub fn clamp(&self, min: f64, max: f64) -> Vec3 { 103 | Vec3 { 104 | x: self.x.max(min).min(max), 105 | y: self.y.max(min).min(max), 106 | z: self.z.max(min).min(max), 107 | } 108 | } 109 | 110 | /// Generates a random vector across a uniform distribution using the answer found in 111 | /// http://stackoverflow.com/questions/5408276/python-uniform-spherical-distribution 112 | pub fn random() -> Vec3 { 113 | let mut rng = thread_rng(); 114 | let phi: f64 = rng.gen_range(0.0, 2.0 * ::std::f64::consts::PI); 115 | let costheta: f64 = rng.gen_range(-1.0, 1.0); 116 | let u: f64 = rng.gen_range(0.0, 1.0); 117 | 118 | let theta = costheta.acos(); 119 | let r = u.powf(1.0 / 3.0); 120 | 121 | Vec3 { 122 | x: r * theta.sin() * phi.cos(), 123 | y: r * theta.sin() * phi.sin(), 124 | z: r * theta.cos(), 125 | } 126 | } 127 | } 128 | 129 | impl Add for Vec3 { 130 | type Output = Vec3; 131 | 132 | fn add(self, other: Vec3) -> Vec3 { 133 | Vec3 { 134 | x: self.x + other.x, 135 | y: self.y + other.y, 136 | z: self.z + other.z, 137 | } 138 | } 139 | } 140 | 141 | impl Add for Vec3 { 142 | type Output = Vec3; 143 | 144 | fn add(self, other: f64) -> Vec3 { 145 | Vec3 { 146 | x: self.x + other, 147 | y: self.y + other, 148 | z: self.z + other, 149 | } 150 | } 151 | } 152 | 153 | impl Sub for Vec3 { 154 | type Output = Vec3; 155 | 156 | fn sub(self, other: Vec3) -> Vec3 { 157 | Vec3 { 158 | x: self.x - other.x, 159 | y: self.y - other.y, 160 | z: self.z - other.z, 161 | } 162 | } 163 | } 164 | 165 | impl Sub for Vec3 { 166 | type Output = Vec3; 167 | 168 | fn sub(self, other: f64) -> Vec3 { 169 | Vec3 { 170 | x: self.x - other, 171 | y: self.y - other, 172 | z: self.z - other, 173 | } 174 | } 175 | } 176 | 177 | impl Mul for Vec3 { 178 | type Output = Vec3; 179 | 180 | fn mul(self, other: Vec3) -> Vec3 { 181 | Vec3 { 182 | x: self.x * other.x, 183 | y: self.y * other.y, 184 | z: self.z * other.z, 185 | } 186 | } 187 | } 188 | 189 | impl Mul for Vec3 { 190 | type Output = Vec3; 191 | 192 | fn mul(self, other: f64) -> Vec3 { 193 | Vec3 { 194 | x: self.x * other, 195 | y: self.y * other, 196 | z: self.z * other, 197 | } 198 | } 199 | } 200 | 201 | impl Div for Vec3 { 202 | type Output = Vec3; 203 | 204 | fn div(self, other: Vec3) -> Vec3 { 205 | Vec3 { 206 | x: self.x / other.x, 207 | y: self.y / other.y, 208 | z: self.z / other.z, 209 | } 210 | } 211 | } 212 | 213 | impl Div for Vec3 { 214 | type Output = Vec3; 215 | 216 | fn div(self, other: f64) -> Vec3 { 217 | Vec3 { 218 | x: self.x / other, 219 | y: self.y / other, 220 | z: self.z / other, 221 | } 222 | } 223 | } 224 | 225 | impl Neg for Vec3 { 226 | type Output = Vec3; 227 | 228 | fn neg(self) -> Vec3 { 229 | Vec3 { 230 | x: -self.x, 231 | y: -self.y, 232 | z: -self.z, 233 | } 234 | } 235 | } 236 | 237 | impl cmp::PartialEq for Vec3 { 238 | fn eq(&self, other: &Vec3) -> bool { 239 | self.x == other.x && self.y == other.y && self.z == other.z 240 | } 241 | } 242 | 243 | impl fmt::Debug for Vec3 { 244 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 245 | write!(f, "({}, {}, {})", self.x, self.y, self.z) 246 | } 247 | } 248 | 249 | #[test] 250 | fn it_implements_debug() { 251 | let vec = Vec3 { 252 | x: 0.0, 253 | y: 1.0, 254 | z: 1.3, 255 | }; 256 | let formatted_string = format!("{:?}", vec); 257 | let expected_string = "(0, 1, 1.3)"; 258 | assert_eq!(&formatted_string, expected_string); 259 | } 260 | 261 | #[test] 262 | fn it_does_cross_product() { 263 | assert_eq!( 264 | Vec3 { 265 | x: -1.0, 266 | y: 2.0, 267 | z: -1.0 268 | }, 269 | Vec3 { 270 | x: 1.0, 271 | y: 2.0, 272 | z: 3.0 273 | }.cross(&Vec3 { 274 | x: 2.0, 275 | y: 3.0, 276 | z: 4.0 277 | }) 278 | ); 279 | } 280 | 281 | #[test] 282 | fn it_does_dot_product() { 283 | assert_eq!( 284 | 5.0, 285 | Vec3 { 286 | x: 0.0, 287 | y: 1.0, 288 | z: 2.0 289 | }.dot(&Vec3 { 290 | x: 0.0, 291 | y: 1.0, 292 | z: 2.0 293 | }) 294 | ); 295 | } 296 | 297 | #[test] 298 | fn it_computes_length_of_a_vec3() { 299 | assert_eq!( 300 | Vec3 { 301 | x: -1.0, 302 | y: -1.0, 303 | z: -1.0 304 | }, 305 | -Vec3::one() 306 | ); 307 | assert_eq!( 308 | 29.0_f64.sqrt(), 309 | Vec3 { 310 | x: 2.0, 311 | y: 3.0, 312 | z: 4.0 313 | }.len() 314 | ); 315 | assert_eq!( 316 | 1.0, 317 | Vec3 { 318 | x: 10.0, 319 | y: 0.0, 320 | z: 0.0 321 | }.unit() 322 | .len() 323 | ); 324 | } 325 | 326 | #[test] 327 | fn it_has_vec3vec3_equality() { 328 | assert!(Vec3::zero() != Vec3::one()); 329 | assert!(Vec3::zero() == Vec3::zero()); 330 | } 331 | 332 | #[test] 333 | fn it_adds_vec3s_and_scalars() { 334 | assert_eq!( 335 | Vec3 { 336 | x: 2.0, 337 | y: 2.0, 338 | z: 2.0 339 | }, 340 | Vec3::one() + Vec3::one() 341 | ); 342 | assert_eq!( 343 | Vec3 { 344 | x: 2.0, 345 | y: 2.0, 346 | z: 2.0 347 | }, 348 | Vec3::one() + 1.0 349 | ); 350 | } 351 | 352 | #[test] 353 | fn it_subtracts_vec3s_and_scalars() { 354 | assert_eq!(Vec3::zero(), Vec3::one() - Vec3::one()); 355 | assert_eq!(Vec3::zero(), Vec3::one() - 1.0); 356 | } 357 | 358 | #[test] 359 | fn it_multiplies_vec3s_and_scalars_elementwise() { 360 | assert_eq!( 361 | Vec3 { 362 | x: 2.0, 363 | y: 2.0, 364 | z: 2.0 365 | }, 366 | Vec3::one().scale(2.0) 367 | ); 368 | assert_eq!( 369 | Vec3 { 370 | x: 2.0, 371 | y: 2.0, 372 | z: 2.0 373 | }, 374 | Vec3::one() * 2.0 375 | ); 376 | assert_eq!( 377 | Vec3 { 378 | x: 4.0, 379 | y: 9.0, 380 | z: -4.0 381 | }, 382 | Vec3 { 383 | x: 2.0, 384 | y: 3.0, 385 | z: 4.0 386 | } * Vec3 { 387 | x: 2.0, 388 | y: 3.0, 389 | z: -1.0 390 | } 391 | ); 392 | } 393 | 394 | #[test] 395 | fn it_divides_vec3s_and_scalars_elementwise() { 396 | assert_eq!( 397 | Vec3 { 398 | x: 0.5, 399 | y: 0.5, 400 | z: 0.5 401 | }, 402 | Vec3::one() / 2.0 403 | ); 404 | assert_eq!( 405 | Vec3 { 406 | x: 0.5, 407 | y: 0.5, 408 | z: 0.5 409 | }, 410 | Vec3::one() / Vec3 { 411 | x: 2.0, 412 | y: 2.0, 413 | z: 2.0 414 | } 415 | ); 416 | } 417 | 418 | #[test] 419 | fn it_linearly_interpolates() { 420 | assert_eq!(Vec3::zero(), Vec3::lerp(&Vec3::zero(), &Vec3::one(), 0.0)); 421 | assert_eq!( 422 | Vec3 { 423 | x: 0.5, 424 | y: 0.5, 425 | z: 0.5 426 | }, 427 | Vec3::lerp(&Vec3::zero(), &Vec3::one(), 0.5) 428 | ); 429 | assert_eq!(Vec3::one(), Vec3::lerp(&Vec3::zero(), &Vec3::one(), 1.0)); 430 | } 431 | -------------------------------------------------------------------------------- /test/res/cube.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.70 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | mtllib cube.mtl 4 | o Cube 5 | v 1.000000 -1.000000 -1.000000 6 | v 1.000000 -1.000000 1.000000 7 | v -1.000000 -1.000000 1.000000 8 | v -1.000000 -1.000000 -1.000000 9 | v 1.000000 1.000000 -1.000000 10 | v 1.000000 1.000000 1.000001 11 | v -1.000000 1.000000 1.000000 12 | v -1.000000 1.000000 -1.000000 13 | vn 0.000000 -1.000000 0.000000 14 | vn 0.000000 1.000000 -0.000000 15 | vn 1.000000 0.000000 0.000000 16 | vn -0.000000 -0.000000 1.000000 17 | vn -1.000000 -0.000000 -0.000000 18 | vn 0.000000 0.000000 -1.000000 19 | vn 1.000000 -0.000000 0.000000 20 | usemtl Material 21 | s off 22 | f 2//1 3//1 4//1 23 | f 5//2 8//2 7//2 24 | f 5//3 6//3 2//3 25 | f 2//4 6//4 7//4 26 | f 7//5 8//5 4//5 27 | f 1//6 4//6 8//6 28 | f 1//1 2//1 4//1 29 | f 6//2 5//2 7//2 30 | f 1//7 5//7 2//7 31 | f 3//4 2//4 7//4 32 | f 3//5 7//5 4//5 33 | f 5//6 1//6 8//6 34 | -------------------------------------------------------------------------------- /test/res/png24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gyng/rust-raytracer/855f2d053ac936a5fb2962d278d629f6269d6bf4/test/res/png24.png -------------------------------------------------------------------------------- /tools/bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for conf in cow bunny box teapot; do 4 | CONF_FILE="tools/conf/${conf}.json" 5 | test -e "$CONF_FILE" && { 6 | echo "=== $1 $conf ===" 7 | time "$1" "$CONF_FILE" 8 | } 9 | done 10 | 11 | -------------------------------------------------------------------------------- /tools/cbenchdec.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | from collections import OrderedDict 4 | 5 | 6 | render_vers = re.compile(r'=== ./rust-raytracer-(\S+) (\S+) ===') 7 | render_time = re.compile(r'Render done at \d+ \((\d+)s\)\.\.\.') 8 | 9 | 10 | def process(stream_in): 11 | mode = 'lfvers' 12 | (version, jobname) = (None, None) 13 | for line in stream_in: 14 | line = line.strip() 15 | if mode == 'lfvers': 16 | vers_match = render_vers.match(line) 17 | if vers_match: 18 | (version, jobname) = vers_match.groups() 19 | mode = 'lftime' 20 | elif mode == 'lftime': 21 | time_match = render_time.match(line) 22 | if time_match: 23 | (time, ) = time_match.groups() 24 | yield (version, jobname, int(time)) 25 | mode = 'lfvers' 26 | else: 27 | raise Exception() 28 | 29 | 30 | if __name__ == '__main__': 31 | output = OrderedDict() 32 | for (version, jobname, time) in process(sys.stdin): 33 | # print("{}\t{}\t{}".format(version, jobname, time)) 34 | if (version, jobname) not in output: 35 | add_to_list = list() 36 | output[(version, jobname)] = add_to_list 37 | else: 38 | add_to_list = output[(version, jobname)] 39 | add_to_list.append(time) 40 | 41 | for ((version, jobname), value) in output.items(): 42 | print("{version} {jobname}\t{rest}".format( 43 | version=version, 44 | jobname=jobname, 45 | rest=' '.join(map(str, value)))) 46 | -------------------------------------------------------------------------------- /tools/conf/box.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "box", 3 | "size": [512, 512], 4 | "fov": 30.0, 5 | "reflect_depth": 4, 6 | "refract_depth": 6, 7 | "shadow_samples": 64, 8 | "gloss_samples": 4, 9 | "pixel_samples": 2, 10 | "output_file": "test", 11 | "animating": false, 12 | "fps": 25.0, 13 | "time_slice": [0.0, 10.0], 14 | "starting_frame_number": 0 15 | } 16 | -------------------------------------------------------------------------------- /tools/conf/bunny.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bunny", 3 | "size": [512, 512], 4 | "fov": 30.0, 5 | "reflect_depth": 4, 6 | "refract_depth": 6, 7 | "shadow_samples": 64, 8 | "gloss_samples": 4, 9 | "pixel_samples": 2, 10 | "output_file": "test", 11 | "animating": false, 12 | "fps": 25.0, 13 | "time_slice": [0.0, 10.0], 14 | "starting_frame_number": 0 15 | } 16 | -------------------------------------------------------------------------------- /tools/conf/cow.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cow", 3 | "size": [512, 512], 4 | "fov": 30.0, 5 | "reflect_depth": 4, 6 | "refract_depth": 6, 7 | "shadow_samples": 64, 8 | "gloss_samples": 4, 9 | "pixel_samples": 2, 10 | "output_file": "test", 11 | "animating": false, 12 | "fps": 25.0, 13 | "time_slice": [0.0, 10.0], 14 | "starting_frame_number": 0 15 | } 16 | -------------------------------------------------------------------------------- /tools/conf/teapot.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "teapot", 3 | "size": [512, 512], 4 | "fov": 30.0, 5 | "reflect_depth": 4, 6 | "refract_depth": 6, 7 | "shadow_samples": 64, 8 | "gloss_samples": 4, 9 | "pixel_samples": 2, 10 | "output_file": "test", 11 | "animating": false, 12 | "fps": 25.0, 13 | "time_slice": [0.0, 10.0], 14 | "starting_frame_number": 0 15 | } 16 | --------------------------------------------------------------------------------
(obj_iter: I) -> Option 86 | where 87 | P: PartialBoundingBox, 88 | I: Iterator { 89 | 90 | obj_iter 91 | .filter_map(|item| item.partial_bounding_box()) 92 | .fold(None, |acc, item| { 93 | Some(acc 94 | .map(|a| union_bbox(&a, &item)) 95 | .unwrap_or(item)) 96 | }) 97 | } 98 | 99 | pub fn intersects(&self, ray: &Ray) -> bool { 100 | // Using ray.inverse_dir is an optimisation. Normally, for simplicity we would do 101 | // 102 | // let d = -ray.direction; 103 | // tx1 = (self.min.x - o.x) / d.x; 104 | // ty1 = (self.min.y - o.y) / d.y; 105 | // ... 106 | // 107 | // but: 108 | // 109 | // 1. div is usually more expensive than mul 110 | // 2. we are recomputing the inverse of d each time we do an intersection check 111 | // 112 | // By caching 1.0 / -ray.direction inside the ray itself we do not need 113 | // to waste CPU cycles recomputing that every intersection check. 114 | // 115 | // See: https://truesculpt.googlecode.com/hg-history/Release%25200.8/Doc/ray_box_intersect.pdf 116 | 117 | let o = ray.origin; 118 | 119 | let (min_bound, max_bound) = if ray.signs[0] { 120 | (self.min, self.max) 121 | } else { 122 | (self.max, self.min) 123 | }; 124 | let mut t_min = (min_bound.x - o.x) * ray.inverse_dir.x; 125 | let mut t_max = (max_bound.x - o.x) * ray.inverse_dir.x; 126 | 127 | let (min_y_bound, max_y_bound) = if ray.signs[1] { 128 | (self.min, self.max) 129 | } else { 130 | (self.max, self.min) 131 | }; 132 | let ty_min = (min_y_bound.y - o.y) * ray.inverse_dir.y; 133 | let ty_max = (max_y_bound.y - o.y) * ray.inverse_dir.y; 134 | 135 | if t_min > ty_max || ty_min > t_max { 136 | return false 137 | } 138 | if ty_min > t_min { 139 | t_min = ty_min; 140 | } 141 | if ty_max < t_max { 142 | t_max = ty_max; 143 | } 144 | 145 | let (min_z_bound, max_z_bound) = if ray.signs[2] { 146 | (self.min, self.max) 147 | } else { 148 | (self.max, self.min) 149 | }; 150 | let tz_min = (min_z_bound.z - o.z) * ray.inverse_dir.z; 151 | let tz_max = (max_z_bound.z - o.z) * ray.inverse_dir.z; 152 | 153 | if t_min > tz_max || tz_min > t_max { 154 | return false 155 | } 156 | if tz_min > t_min { 157 | t_min = tz_min; 158 | } 159 | if tz_max < t_max { 160 | t_max = tz_max; 161 | } 162 | 163 | // tmin < t1 && tmax > t0 164 | t_min < ::std::f64::INFINITY && t_max > 0.0 165 | } 166 | 167 | pub fn overlaps(&self, other: &BBox) -> bool { 168 | let x = self.max.x >= other.min.x && self.min.x <= other.max.x; 169 | let y = self.max.y >= other.min.y && self.min.y <= other.max.y; 170 | let z = self.max.z >= other.min.z && self.min.z <= other.max.z; 171 | 172 | x && y && z 173 | } 174 | 175 | pub fn inside(&self, p: &Vec3) -> bool { 176 | p.x >= self.min.x && p.x <= self.max.x && 177 | p.y >= self.min.y && p.y <= self.max.y && 178 | p.z >= self.min.z && p.z <= self.max.z 179 | } 180 | 181 | pub fn contains(&self, other: &BBox) -> bool { 182 | other.min.x >= self.min.x && 183 | other.min.y >= self.min.y && 184 | other.min.z >= self.min.z && 185 | other.max.x <= self.max.x && 186 | other.max.y <= self.max.y && 187 | other.max.z <= self.max.z 188 | } 189 | 190 | /// Pad bounding box by a constant factor. 191 | pub fn expand(&self, delta: f64) -> BBox { 192 | let delta_vec3 = Vec3 { x: delta, y: delta, z: delta }; 193 | 194 | BBox { 195 | min: self.min - delta_vec3, 196 | max: self.max + delta_vec3 197 | } 198 | } 199 | 200 | /// Returns which axis is the widest. 0: x, 1: y, 2: z 201 | pub fn max_extent(&self) -> u8 { 202 | let diag = self.max - self.min; 203 | if diag.x > diag.y && diag.x > diag.z { 204 | 0 205 | } else if diag.y > diag.z { 206 | 1 207 | } else { 208 | 2 209 | } 210 | } 211 | 212 | /// Interpolate between corners of the box. 213 | pub fn lerp(&self, t_x: f64, t_y: f64, t_z: f64) -> Vec3 { 214 | let diag = self.max - self.min; 215 | Vec3 { 216 | x: self.min.x + diag.x * t_x, 217 | y: self.min.y + diag.y * t_y, 218 | z: self.min.z + diag.z * t_z 219 | } 220 | } 221 | 222 | /// Offset from minimum corner point 223 | pub fn offset(&self, offset: &Vec3) -> Vec3 { 224 | let diag = self.max - self.min; 225 | Vec3 { 226 | x: (offset.x - self.min.x) / diag.x, 227 | y: (offset.y - self.min.y) / diag.y, 228 | z: (offset.z - self.min.z) / diag.z 229 | } 230 | } 231 | 232 | pub fn x_len(&self) -> f64 { 233 | self.max.x - self.min.x 234 | } 235 | 236 | pub fn y_len(&self) -> f64 { 237 | self.max.y - self.min.y 238 | } 239 | 240 | pub fn z_len(&self) -> f64 { 241 | self.max.z - self.min.z 242 | } 243 | 244 | pub fn len(&self) -> Vec3 { 245 | self.max - self.min 246 | } 247 | } 248 | 249 | #[test] 250 | fn it_intersects_with_a_ray() { 251 | let bbox = BBox { 252 | min: Vec3::zero(), 253 | max: Vec3::one() 254 | }; 255 | 256 | // Out of the box 257 | let mut intersecting_ray = Ray::new(Vec3 { x: 0.5, y: 1.5, z: 0.5 }, Vec3 { x: 0.0, y: -1.0, z: 0.0 }); 258 | assert!(bbox.intersects(&intersecting_ray)); 259 | 260 | // In the box 261 | intersecting_ray = Ray::new(Vec3 { x: 0.5, y: 0.5, z: 0.5 }, Vec3 { x: 0.0, y: -1.0, z: 0.0 }); 262 | assert!(bbox.intersects(&intersecting_ray)); 263 | 264 | // Away from box 265 | let mut non_intersecting_ray = Ray::new(Vec3 { x: 0.5, y: 1.5, z: 0.5 }, Vec3 { x: 0.0, y: 1.0, z: 0.0 }); 266 | assert_eq!(false, bbox.intersects(&non_intersecting_ray)); 267 | 268 | // To the side 269 | non_intersecting_ray = Ray::new(Vec3 { x: 0.5, y: 1.5, z: 0.5 }, Vec3 { x: 1000.0, y: -1.0, z: 1000.0 }); 270 | assert_eq!(false, bbox.intersects(&non_intersecting_ray)); 271 | } 272 | 273 | #[test] 274 | fn it_unions_a_bbox_with_a_point() { 275 | let original_bbox = BBox { 276 | min: Vec3::zero(), 277 | max: Vec3::one() 278 | }; 279 | 280 | let smaller_point = Vec3 { x: -1.0, y: -1.0, z: -1.0 }; 281 | let unioned_bbox = union_point(&original_bbox, &smaller_point); 282 | assert_eq!(unioned_bbox.min, smaller_point); 283 | assert_eq!(unioned_bbox.max, Vec3::one()); 284 | 285 | let larger_point = Vec3 { x: 2.0, y: 2.0, z: 2.0 }; 286 | let unioned_bbox2 = union_point(&unioned_bbox, &larger_point); 287 | assert_eq!(unioned_bbox2.min, smaller_point); 288 | assert_eq!(unioned_bbox2.max, larger_point); 289 | } 290 | 291 | #[test] 292 | fn it_unions_two_points() { 293 | // Larger to smaller 294 | let unioned_bbox = union_points(&Vec3::one(), &Vec3::zero()); 295 | assert_eq!(unioned_bbox.min, Vec3::zero()); 296 | assert_eq!(unioned_bbox.max, Vec3::one()); 297 | 298 | // Smaller to larger 299 | let unioned_bbox2 = union_points(&-Vec3::one(), &Vec3::zero()); 300 | assert_eq!(unioned_bbox2.min, -Vec3::one()); 301 | assert_eq!(unioned_bbox2.max, Vec3::zero()); 302 | } 303 | 304 | #[test] 305 | fn it_unions_two_bboxes() { 306 | let bbox_one = BBox { 307 | min: Vec3::zero(), 308 | max: Vec3::one() 309 | }; 310 | 311 | let bbox_two = BBox { 312 | min: -Vec3::one(), 313 | max: Vec3::zero() 314 | }; 315 | 316 | let unioned_bbox = union_bbox(&bbox_one, &bbox_two); 317 | assert_eq!(unioned_bbox.min, -Vec3::one()); 318 | assert_eq!(unioned_bbox.max, Vec3::one()); 319 | } 320 | 321 | #[test] 322 | fn it_checks_for_bbox_overlap() { 323 | let bbox = BBox { 324 | min: Vec3::zero(), 325 | max: Vec3::one() 326 | }; 327 | 328 | let overlapping = BBox { 329 | min: Vec3 { x: 0.5, y: 0.5, z: 0.5 }, 330 | max: Vec3 { x: 1.5, y: 1.5, z: 1.5 } 331 | }; 332 | 333 | let not_overlapping = BBox { 334 | min: Vec3 { x: 1.5, y: 1.5, z: 1.5 }, 335 | max: Vec3 { x: 2.5, y: 2.5, z: 2.5 } 336 | }; 337 | 338 | assert!(bbox.overlaps(&overlapping)); 339 | assert_eq!(false, bbox.overlaps(¬_overlapping)); 340 | } 341 | 342 | #[test] 343 | fn it_checks_for_point_inside() { 344 | let bbox = BBox { 345 | min: Vec3::zero(), 346 | max: Vec3::one() 347 | }; 348 | 349 | let inside = Vec3 { x: 0.5, y: 0.5, z: 0.5 }; 350 | assert!(bbox.inside(&inside)); 351 | 352 | let outside_1 = Vec3 { x: 1.5, y: 1.5, z: 1.5 }; 353 | let outside_2 = Vec3 { x: 0.5, y: 1.5, z: 0.5 }; 354 | let outside_3 = Vec3 { x: -0.5, y: 0.5, z: 0.5 }; 355 | 356 | assert_eq!(false, bbox.inside(&outside_1)); 357 | assert_eq!(false, bbox.inside(&outside_2)); 358 | assert_eq!(false, bbox.inside(&outside_3)); 359 | } 360 | 361 | #[test] 362 | fn it_checks_for_contains_another_bbox() { 363 | let bbox = BBox { 364 | min: Vec3::zero(), 365 | max: Vec3::one() 366 | }; 367 | 368 | let overlapping = BBox { 369 | min: Vec3 { x: 0.5, y: 0.5, z: 0.5 }, 370 | max: Vec3 { x: 1.5, y: 1.5, z: 1.5 } 371 | }; 372 | 373 | let not_overlapping = BBox { 374 | min: Vec3 { x: 1.5, y: 1.5, z: 1.5 }, 375 | max: Vec3 { x: 2.5, y: 2.5, z: 2.5 } 376 | }; 377 | 378 | let inside = BBox { 379 | min: Vec3 { x: 0.25, y: 0.25, z: 0.25 }, 380 | max: Vec3 { x: 0.75, y: 0.75, z: 0.75 } 381 | }; 382 | 383 | assert_eq!(false, bbox.contains(&overlapping)); 384 | assert_eq!(false, bbox.contains(¬_overlapping)); 385 | assert!(bbox.contains(&inside)); 386 | } 387 | 388 | #[test] 389 | fn it_expands_by_a_factor() { 390 | let bbox = BBox { 391 | min: Vec3::zero(), 392 | max: Vec3::one() 393 | }; 394 | 395 | let expanded = bbox.expand(1.0); 396 | assert_eq!(-Vec3::one(), expanded.min); 397 | assert_eq!(Vec3::one().scale(2.0), expanded.max); 398 | 399 | let shrunken = bbox.expand(-0.25); 400 | assert_eq!(Vec3 { x: 0.25, y: 0.25, z: 0.25 }, shrunken.min); 401 | assert_eq!(Vec3 { x: 0.75, y: 0.75, z: 0.75 }, shrunken.max); 402 | } 403 | 404 | #[test] 405 | fn it_returns_max_extent() { 406 | let x = BBox { 407 | min: Vec3::zero(), 408 | max: Vec3 { x: 2.0, y: 1.0, z: 1.0 } 409 | }; 410 | 411 | let y = BBox { 412 | min: Vec3::zero(), 413 | max: Vec3 { x: 1.0, y: 2.0, z: 1.0 } 414 | }; 415 | 416 | let z = BBox { 417 | min: Vec3::zero(), 418 | max: Vec3 { x: 1.0, y: 1.0, z: 2.0 } 419 | }; 420 | 421 | assert_eq!(0u8, x.max_extent()); 422 | assert_eq!(1u8, y.max_extent()); 423 | assert_eq!(2u8, z.max_extent()); 424 | } 425 | 426 | #[test] 427 | fn it_returns_offset_length_from_min_corner() { 428 | let bbox = BBox { 429 | min: -Vec3::one(), 430 | max: Vec3::one() 431 | }; 432 | 433 | let offset_point = bbox.offset(&Vec3::one()); 434 | assert_eq!(Vec3::one(), offset_point); 435 | } 436 | 437 | #[test] 438 | fn it_returns_side_lengths() { 439 | let bbox = BBox { 440 | min: Vec3::zero(), 441 | max: Vec3 { x: 1.0, y: 2.0, z: 3.0 } 442 | }; 443 | 444 | assert_eq!(1.0, bbox.x_len()); 445 | assert_eq!(2.0, bbox.y_len()); 446 | assert_eq!(3.0, bbox.z_len()); 447 | } 448 | -------------------------------------------------------------------------------- /src/geometry/mesh.rs: -------------------------------------------------------------------------------- 1 | use geometry::Prim; 2 | use mat4::Transform; 3 | 4 | #[allow(dead_code)] 5 | pub struct Mesh { 6 | pub triangles: Vec> 7 | } 8 | 9 | impl Mesh { 10 | pub fn mut_transform(&mut self, transform: &Transform) { 11 | for triangle in &mut self.triangles { 12 | triangle.mut_transform(transform); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/geometry/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::prim::Prim; 2 | pub use self::mesh::Mesh; 3 | pub use self::bbox::{BBox, PartialBoundingBox}; 4 | 5 | pub mod bbox; 6 | pub mod prim; 7 | pub mod mesh; 8 | 9 | pub mod prims { 10 | pub use self::plane::Plane; 11 | pub use self::sphere::Sphere; 12 | pub use self::triangle::{Triangle, TriangleOptions}; 13 | 14 | mod plane; 15 | mod sphere; 16 | mod triangle; 17 | } 18 | -------------------------------------------------------------------------------- /src/geometry/prim.rs: -------------------------------------------------------------------------------- 1 | use geometry::{BBox, PartialBoundingBox}; 2 | use raytracer::{Ray, Intersection}; 3 | use mat4::Transform; 4 | 5 | pub trait Prim: PartialBoundingBox { 6 | fn intersects<'a>(&'a self, ray: &Ray, t_min: f64, t_max: f64) -> Option>; 7 | 8 | // fn transform(&self, transform: &Transform) -> Box; 9 | fn mut_transform(&mut self, transform: &Transform); 10 | } 11 | 12 | impl<'a> PartialBoundingBox for Box { 13 | fn partial_bounding_box(&self) -> Option { 14 | (**self).partial_bounding_box() 15 | } 16 | } -------------------------------------------------------------------------------- /src/geometry/prims/plane.rs: -------------------------------------------------------------------------------- 1 | use geometry::{BBox, PartialBoundingBox, Prim}; 2 | use material::Material; 3 | use mat4::{Mat4, Transform}; 4 | use raytracer::{Ray, Intersection}; 5 | use vec3::Vec3; 6 | 7 | #[cfg(test)] 8 | use material::materials::FlatMaterial; 9 | 10 | #[allow(dead_code)] 11 | pub struct Plane { 12 | pub a: f64, // normal.x 13 | pub b: f64, // normal.y 14 | pub c: f64, // normal.z 15 | pub d: f64, 16 | pub material: Box 17 | } 18 | 19 | impl PartialBoundingBox for Plane { 20 | fn partial_bounding_box(&self) -> Option { 21 | None // more infinite than infinityb 22 | } 23 | } 24 | 25 | impl Prim for Plane { 26 | fn intersects<'a>(&'a self, ray: &Ray, t_min: f64, t_max: f64) -> Option> { 27 | let n = Vec3 { x: self.a, y: self.b, z: self.c }; 28 | let nrd = n.dot(&ray.direction); 29 | let nro = n.dot(&ray.origin); 30 | let t = (-self.d - nro) / nrd; 31 | 32 | if t < t_min || t > t_max { 33 | None 34 | } else { 35 | let intersection_point = ray.origin + ray.direction.scale(t); 36 | let u_axis = Vec3 { x: n.y, y: n.z, z: -n.x }; 37 | let v_axis = u_axis.cross(&n); 38 | let u = intersection_point.dot(&u_axis); 39 | let v = intersection_point.dot(&v_axis); 40 | 41 | Some(Intersection { 42 | n: n, 43 | t: t, 44 | u: u, 45 | v: v, 46 | position: intersection_point, 47 | material: &self.material 48 | }) 49 | } 50 | } 51 | 52 | /// This transformation is entirely ad-hoc, do not trust this 53 | fn mut_transform(&mut self, transform: &Transform) { 54 | let new_v = Mat4::mult_v(&transform.m, &Vec3 { x: self.a, y: self.b, z: self.c }); 55 | 56 | self.a = new_v.x; 57 | self.b = new_v.y; 58 | self.c = new_v.z; 59 | 60 | let trans = Vec3 { 61 | x: transform.m.m[0][3], 62 | y: transform.m.m[1][3], 63 | z: transform.m.m[2][3] 64 | }; 65 | 66 | let t_x = transform.m.m[0][3].powf(2.0) * if trans.x < 0.0 { -1.0 } else { 1.0 }; 67 | let t_y = transform.m.m[1][3].powf(2.0) * if trans.y < 0.0 { -1.0 } else { 1.0 }; 68 | let t_z = transform.m.m[2][3].powf(2.0) * if trans.z < 0.0 { -1.0 } else { 1.0 }; 69 | let add_sub = if t_x + t_y + t_z < 0.0 { -1.0 } else { 1.0 }; 70 | 71 | self.d += trans.len() * add_sub; 72 | } 73 | } 74 | 75 | #[test] 76 | fn it_intersects() { 77 | let plane = Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(FlatMaterial { color: Vec3::one() }) }; 78 | 79 | // Tests actual intersection 80 | let intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 1.0, z: 0.0 }, Vec3 { x: 0.0, y: -1.0, z: 0.0 }); 81 | let intersection = plane.intersects(&intersecting_ray, 0.0, 10.0).unwrap(); 82 | assert_eq!(intersection.position.x, 0.0); 83 | assert_eq!(intersection.position.y, 0.0); 84 | assert_eq!(intersection.position.z, 0.0); 85 | assert_eq!(intersection.n.x, 0.0); 86 | assert_eq!(intersection.n.y, 1.0); 87 | assert_eq!(intersection.n.z, 0.0); 88 | 89 | // Parallel ray 90 | let mut non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 1.0, z: 0.0 }, Vec3 { x: 1.0, y: 0.0, z: 1.0 }); 91 | let mut non_intersection = plane.intersects(&non_intersecting_ray, 0.0, 10000.0); 92 | assert!(non_intersection.is_none()); 93 | 94 | // Ray in opposite direction 95 | non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 1.0, z: 0.0 }, Vec3 { x: 0.0, y: 1.0, z: 0.0 }); 96 | non_intersection = plane.intersects(&non_intersecting_ray, 0.0, 10.0); 97 | assert!(non_intersection.is_none()); 98 | } 99 | 100 | #[test] 101 | fn it_intersects_only_in_tmin_tmax() { 102 | let plane = Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(FlatMaterial { color: Vec3::one() }) }; 103 | 104 | // Tests tmin 105 | let intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 1.0, z: 0.0 }, Vec3 { x: 0.0, y: -1.0, z: 0.0 }); 106 | let mut non_intersection = plane.intersects(&intersecting_ray, 1000.0, 10000.0); 107 | assert!(non_intersection.is_none()); 108 | 109 | // Tests tmax 110 | non_intersection = plane.intersects(&intersecting_ray, 0.0, 0.0001); 111 | assert!(non_intersection.is_none()); 112 | } 113 | -------------------------------------------------------------------------------- /src/geometry/prims/sphere.rs: -------------------------------------------------------------------------------- 1 | use geometry::bbox::{BBox, PartialBoundingBox}; 2 | use geometry::prim::Prim; 3 | use material::Material; 4 | use mat4::{Mat4, Transform}; 5 | use raytracer::{Ray, Intersection}; 6 | use vec3::Vec3; 7 | 8 | #[cfg(test)] 9 | use material::materials::FlatMaterial; 10 | 11 | #[allow(dead_code)] 12 | pub struct Sphere { 13 | pub center: Vec3, 14 | pub radius: f64, 15 | pub material: Box 16 | } 17 | 18 | impl PartialBoundingBox for Sphere { 19 | fn partial_bounding_box(&self) -> Option { 20 | Some(BBox { 21 | min: Vec3 { 22 | x: self.center.x - self.radius, 23 | y: self.center.y - self.radius, 24 | z: self.center.z - self.radius 25 | }, 26 | max: Vec3 { 27 | x: self.center.x + self.radius, 28 | y: self.center.y + self.radius, 29 | z: self.center.z + self.radius 30 | } 31 | }) 32 | } 33 | } 34 | 35 | impl Prim for Sphere { 36 | fn intersects<'a>(&'a self, ray: &Ray, t_min: f64, t_max: f64) -> Option> { 37 | let i = ray.origin - self.center; 38 | let a = 1.0; 39 | let b = 2.0 * ray.direction.dot(&i); 40 | let c = i.dot(&i) - self.radius * self.radius; 41 | let discriminant = b * b - 4.0 * a * c; 42 | 43 | if discriminant <= 0.0 { 44 | None 45 | } else { 46 | // Up to two intersections 47 | let disc_sqrt = discriminant.sqrt(); 48 | let t1 = (-b + disc_sqrt) / 2.0 * a; 49 | let t2 = (-b - disc_sqrt) / 2.0 * a; 50 | 51 | if t1 >= t_min && t1 <= t_max || 52 | t2 >= t_min && t2 <= t_max { 53 | // Valid intersection(s): get nearer intersection 54 | let t = if t1.abs() < t2.abs() { t1 } else { t2 }; 55 | let intersection_point = ray.origin + ray.direction.scale(t); 56 | let n = (intersection_point - self.center).unit(); 57 | 58 | let u = 0.5 + n.z.atan2(n.x) / (::std::f64::consts::PI * 2.0); 59 | let v = 0.5 - n.y.asin() / ::std::f64::consts::PI; 60 | 61 | Some(Intersection { 62 | n: n, 63 | t: t, 64 | u: u, 65 | v: v, 66 | position: intersection_point, 67 | material: &self.material 68 | }) 69 | } else { 70 | None 71 | } 72 | } 73 | } 74 | 75 | fn mut_transform(&mut self, transform: &Transform) { 76 | let new_center = Mat4::mult_p(&transform.m, &self.center); 77 | 78 | let new_radius = if transform.m.has_scale() { 79 | self.radius * transform.m.scale() 80 | } else { 81 | self.radius 82 | }; 83 | 84 | self.center = new_center; 85 | self.radius = new_radius; 86 | } 87 | } 88 | 89 | #[test] 90 | fn it_intersects() { 91 | let sphere = Sphere { 92 | center: Vec3::zero(), 93 | radius: 1.0, 94 | material: Box::new(FlatMaterial { color: Vec3::one() }) 95 | }; 96 | 97 | // Tests actual intersection 98 | let intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.0, z: -2.0 }, Vec3 { x: 0.0, y: 0.0, z: 1.0 }); 99 | let intersection = sphere.intersects(&intersecting_ray, 0.0, 10.0).unwrap(); 100 | assert_eq!(intersection.position.x, 0.0); 101 | assert_eq!(intersection.position.y, 0.0); 102 | assert_eq!(intersection.position.z, -1.0); 103 | assert_eq!(intersection.n.x, 0.0); 104 | assert_eq!(intersection.n.y, 0.0); 105 | assert_eq!(intersection.n.z, -1.0); 106 | 107 | // Ray off to the sides 108 | let mut non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.0, z: -2.0 }, Vec3 { x: 100.0, y: 100.0, z: 0.1 }); 109 | let mut non_intersection = sphere.intersects(&non_intersecting_ray, 0.0, 10.0); 110 | assert!(non_intersection.is_none()); 111 | 112 | non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.0, z: -2.0 }, Vec3 { x: -100.0, y: -100.0, z: 0.1 }); 113 | non_intersection = sphere.intersects(&non_intersecting_ray, 0.0, 10.0); 114 | assert!(non_intersection.is_none()); 115 | 116 | // Ray in opposite direction 117 | non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.0, z: -2.0 }, Vec3 {x: 0.0, y: 0.0, z: -1.0 }); 118 | non_intersection = sphere.intersects(&non_intersecting_ray, 0.0, 10.0); 119 | assert!(non_intersection.is_none()); 120 | } 121 | 122 | #[test] 123 | fn it_intersects_only_in_tmin_tmax() { 124 | let sphere = Sphere { 125 | center: Vec3::zero(), 126 | radius: 1.0, 127 | material: Box::new(FlatMaterial { color: Vec3::one() }) 128 | }; 129 | 130 | // Tests tmin 131 | let intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.0, z: -2.0 }, Vec3 { x: 0.0, y: 0.0, z: 1.0 }); 132 | let mut non_intersection = sphere.intersects(&intersecting_ray, 1000.0, 10000.0); 133 | assert!(non_intersection.is_none()); 134 | 135 | // Tests tmax 136 | non_intersection = sphere.intersects(&intersecting_ray, 0.0, 0.0001); 137 | assert!(non_intersection.is_none()); 138 | } 139 | -------------------------------------------------------------------------------- /src/geometry/prims/triangle.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use geometry::bbox::{union_point, union_points, BBox, PartialBoundingBox}; 4 | use geometry::prim::Prim; 5 | use material::Material; 6 | use mat4::{Mat4, Transform}; 7 | use raytracer::{Ray, Intersection}; 8 | use vec3::Vec3; 9 | 10 | use material::materials::FlatMaterial; 11 | 12 | 13 | struct UvValue { 14 | u: f64, 15 | v: f64 16 | } 17 | 18 | impl UvValue { 19 | pub fn from_tuple(uv: (f64, f64)) -> UvValue { 20 | UvValue { u: uv.0, v: uv.1 } 21 | } 22 | 23 | fn default3() -> [UvValue; 3] { 24 | [ 25 | UvValue { u: 0.5, v: 1.0 }, 26 | UvValue { u: 0.0, v: 0.0 }, 27 | UvValue { u: 1.0, v: 0.0 }, 28 | ] 29 | } 30 | } 31 | 32 | pub struct TriangleOptions { 33 | vertices: [Vec3; 3], 34 | normals: Option<[Vec3; 3]>, 35 | texinfo: Option<[UvValue; 3]>, 36 | material: Option>, 37 | } 38 | 39 | fn get_auto_normals(v: [Vec3; 3]) -> [Vec3; 3] { 40 | let n = (v[1] - v[0]).cross(&(v[2] - v[0])); 41 | [n, n, n] 42 | } 43 | 44 | impl TriangleOptions { 45 | pub fn new(v0: Vec3, v1: Vec3, v2: Vec3) -> TriangleOptions { 46 | TriangleOptions { 47 | vertices: [v0, v1, v2], 48 | normals: None, 49 | texinfo: None, 50 | material: None, 51 | } 52 | } 53 | 54 | /// In the default case, all three normals at vertices are perpendicular 55 | /// to the triangle plane. 56 | pub fn normals(&mut self, normals: [Vec3; 3]) -> &mut Self { 57 | self.normals = Some(normals); 58 | self 59 | } 60 | 61 | pub fn texinfo(&mut self, texinfo: [(f64, f64); 3]) -> &mut Self { 62 | self.texinfo = Some([ 63 | UvValue::from_tuple(texinfo[0]), 64 | UvValue::from_tuple(texinfo[1]), 65 | UvValue::from_tuple(texinfo[2]), 66 | ]); 67 | self 68 | } 69 | 70 | pub fn material(&mut self, material: Box) -> &mut Self { 71 | self.material = Some(material); 72 | self 73 | } 74 | 75 | pub fn build(self) -> Triangle { 76 | let normals = self.normals.unwrap_or_else(|| get_auto_normals(self.vertices)); 77 | let texinfo = self.texinfo.unwrap_or_else(UvValue::default3); 78 | let material = self.material.unwrap_or_else(|| Box::new(FlatMaterial { color: Vec3::one() })); 79 | 80 | Triangle { 81 | vertices: self.vertices, 82 | normals: normals, 83 | texinfo: texinfo, 84 | material: material, 85 | } 86 | } 87 | } 88 | 89 | pub struct Triangle { 90 | vertices: [Vec3; 3], 91 | 92 | // All the same if our triangle is ``flat''. 93 | // Values differ when we want interpolation. e.g. round things like teapot. 94 | normals: [Vec3; 3], 95 | 96 | // Used in textured triangles, can be [UvValue; 3]::default() otherwise. 97 | texinfo: [UvValue; 3], 98 | 99 | material: Box 100 | } 101 | 102 | impl PartialBoundingBox for Triangle { 103 | fn partial_bounding_box(&self) -> Option { 104 | Some(union_point(&union_points(&self.vertices[0], &self.vertices[1]), &self.vertices[2])) 105 | } 106 | } 107 | 108 | impl Prim for Triangle { 109 | /// http://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm 110 | /// Barycentric coordinates. 111 | fn intersects<'a>(&'a self, ray: &Ray, t_min: f64, t_max: f64) -> Option> { 112 | let e1 = self.vertices[1] - self.vertices[0]; 113 | let e2 = self.vertices[2] - self.vertices[0]; 114 | let p = ray.direction.cross(&e2); 115 | let det = e1.dot(&p); 116 | 117 | // if determinant is near zero, ray lies in plane of triangle 118 | if det > -::std::f64::EPSILON && det < ::std::f64::EPSILON { 119 | return None 120 | } 121 | 122 | let inv_det = 1.0 / det; 123 | let s = ray.origin - self.vertices[0]; 124 | let beta = inv_det * s.dot(&p); 125 | if beta < 0.0 || beta > 1.0 { return None } 126 | 127 | let q = s.cross(&e1); 128 | let gamma = inv_det * ray.direction.dot(&q); 129 | if gamma < 0.0 || beta + gamma > 1.0 { return None } 130 | 131 | let t = inv_det * e2.dot(&q); 132 | 133 | if t < t_min || t > t_max { 134 | None 135 | } else { 136 | let intersection_point = ray.origin + ray.direction.scale(t); 137 | 138 | let alpha = 1.0 - beta - gamma; 139 | 140 | // Interpolate normals at vertices to get normal 141 | let n = self.normals[0].scale(alpha) + self.normals[1].scale(beta) + self.normals[2].scale(gamma); 142 | 143 | // Interpolate UVs at vertices to get UV 144 | let u = self.texinfo[0].u * alpha + self.texinfo[1].u * beta + self.texinfo[2].u * gamma; 145 | let v = self.texinfo[0].v * alpha + self.texinfo[1].v * beta + self.texinfo[2].v * gamma; 146 | 147 | Some(Intersection { 148 | n: n, 149 | t: t, 150 | u: u, 151 | v: v, 152 | position: intersection_point, 153 | material: &self.material 154 | }) 155 | } 156 | } 157 | 158 | fn mut_transform(&mut self, transform: &Transform) { 159 | let v0_t = Mat4::mult_p(&transform.m, &self.vertices[0]); 160 | let v1_t = Mat4::mult_p(&transform.m, &self.vertices[1]); 161 | let v2_t = Mat4::mult_p(&transform.m, &self.vertices[2]); 162 | 163 | let n0_t = Mat4::transform_normal(&self.normals[0], &transform.m); 164 | let n1_t = Mat4::transform_normal(&self.normals[1], &transform.m); 165 | let n2_t = Mat4::transform_normal(&self.normals[2], &transform.m); 166 | 167 | self.vertices[0] = v0_t; 168 | self.vertices[1] = v1_t; 169 | self.vertices[2] = v2_t; 170 | 171 | self.normals[0] = n0_t; 172 | self.normals[1] = n1_t; 173 | self.normals[2] = n2_t; 174 | } 175 | } 176 | 177 | #[test] 178 | fn it_intersects_and_interpolates() { 179 | let mut triopts = TriangleOptions::new( 180 | Vec3 { x: -1.0, y: 0.0, z: 0.0 }, 181 | Vec3 { x: 1.0, y: 0.0, z: 0.0 }, 182 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }); 183 | triopts.normals([ 184 | Vec3 { x: -1.0, y: 0.0, z: 0.0 }, 185 | Vec3 { x: 1.0, y: 0.0, z: 0.0 }, 186 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }]); 187 | triopts.texinfo([(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)]); 188 | 189 | let triangle = triopts.build(); 190 | 191 | // Tests actual intersection 192 | let intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.5, z: -1.0 }, Vec3 { x: 0.0, y: 0.0, z: 1.0 }); 193 | let intersection = triangle.intersects(&intersecting_ray, 0.0, 10.0).unwrap(); 194 | assert_eq!(intersection.position.x, 0.0); 195 | assert_eq!(intersection.position.y, 0.5); 196 | assert_eq!(intersection.position.z, 0.0); 197 | assert_eq!(intersection.u, 0.25); 198 | assert_eq!(intersection.v, 0.5); 199 | assert_eq!(intersection.n.x, 0.0); 200 | assert_eq!(intersection.n.y, 0.5); 201 | assert_eq!(intersection.n.z, 0.0); 202 | 203 | // Ray off to the sides 204 | let mut non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.5, z: -1.0 }, Vec3 { x: 100.0, y: 100.0, z: 1.0 }); 205 | let mut non_intersection = triangle.intersects(&non_intersecting_ray, 0.0, 10.0); 206 | assert!(non_intersection.is_none()); 207 | 208 | non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.5, z: -1.0 }, Vec3 { x: -100.0, y: -100.0, z: 1.0 }); 209 | non_intersection = triangle.intersects(&non_intersecting_ray, 0.0, 10.0); 210 | assert!(non_intersection.is_none()); 211 | 212 | // Ray in opposite direction 213 | non_intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.5, z: -1.0 }, Vec3 { x: 0.0, y: 0.0, z: -1.0 }); 214 | non_intersection = triangle.intersects(&non_intersecting_ray, 0.0, 10.0); 215 | assert!(non_intersection.is_none()); 216 | } 217 | 218 | #[test] 219 | fn it_intersects_only_in_tmin_tmax() { 220 | let mut triopts = TriangleOptions::new( 221 | Vec3 { x: -1.0, y: 0.0, z: 0.0 }, 222 | Vec3 { x: 1.0, y: 0.0, z: 0.0 }, 223 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }); 224 | triopts.normals([Vec3::zero(), Vec3::zero(), Vec3::one()]); 225 | triopts.texinfo([(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)]); 226 | 227 | let triangle = triopts.build(); 228 | 229 | // Tests tmin 230 | let intersecting_ray = Ray::new(Vec3 { x: 0.0, y: 0.5, z: -1.0 }, Vec3 { x: 0.0, y: 0.0, z: 1.0 }); 231 | let mut non_intersection = triangle.intersects(&intersecting_ray, 1000.0, 10000.0); 232 | assert!(non_intersection.is_none()); 233 | 234 | // Tests tmax 235 | non_intersection = triangle.intersects(&intersecting_ray, 0.0, 0.0001); 236 | assert!(non_intersection.is_none()); 237 | } 238 | -------------------------------------------------------------------------------- /src/light/light.rs: -------------------------------------------------------------------------------- 1 | use vec3::Vec3; 2 | 3 | pub trait Light { 4 | fn position(&self) -> Vec3; 5 | fn color(&self) -> Vec3; 6 | fn center(&self) -> Vec3; 7 | fn is_point(&self) -> bool; 8 | } 9 | -------------------------------------------------------------------------------- /src/light/lights/pointlight.rs: -------------------------------------------------------------------------------- 1 | use light::light::Light; 2 | use vec3::Vec3; 3 | 4 | #[allow(dead_code)] 5 | pub struct PointLight { 6 | pub position: Vec3, 7 | pub color: Vec3 8 | } 9 | 10 | impl Light for PointLight { 11 | fn position(&self) -> Vec3 { 12 | self.position 13 | } 14 | 15 | fn color(&self) -> Vec3 { 16 | self.color 17 | } 18 | 19 | fn center(&self) -> Vec3 { 20 | self.position 21 | } 22 | 23 | fn is_point(&self) -> bool { 24 | true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/light/lights/spherelight.rs: -------------------------------------------------------------------------------- 1 | use rand::{thread_rng, Rng}; 2 | use light::light::Light; 3 | use vec3::Vec3; 4 | 5 | #[allow(dead_code)] 6 | pub struct SphereLight { 7 | pub position: Vec3, 8 | pub color: Vec3, 9 | pub radius: f64 10 | } 11 | 12 | impl Light for SphereLight { 13 | fn position(&self) -> Vec3 { 14 | let mut rng = thread_rng(); 15 | 16 | let jitter = Vec3 { 17 | x: self.radius * (rng.gen::() - 0.5), 18 | y: self.radius * (rng.gen::() - 0.5), 19 | z: self.radius * (rng.gen::() - 0.5) 20 | }; 21 | 22 | self.position + jitter 23 | } 24 | 25 | fn color(&self) -> Vec3 { 26 | self.color 27 | } 28 | 29 | fn center(&self) -> Vec3 { 30 | self.position 31 | } 32 | 33 | fn is_point(&self) -> bool { 34 | false 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/light/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::light::Light; 2 | pub mod light; 3 | 4 | pub mod lights { 5 | pub use self::pointlight::PointLight; 6 | pub use self::spherelight::SphereLight; 7 | 8 | mod pointlight; 9 | mod spherelight; 10 | } 11 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_imports)] 2 | 3 | extern crate image; 4 | extern crate num; 5 | extern crate num_cpus; 6 | extern crate rand; 7 | extern crate rustc_serialize; 8 | extern crate threadpool; 9 | extern crate time; 10 | 11 | use std::fs::File; 12 | use std::io::{self, Read, Write}; 13 | use std::env; 14 | use std::process; 15 | use std::sync::Arc; 16 | use rustc_serialize::json; 17 | use rustc_serialize::json::DecoderError::MissingFieldError; 18 | 19 | mod geometry; 20 | mod light; 21 | mod material; 22 | mod my_scene; 23 | mod raytracer; 24 | mod scene; 25 | mod util; 26 | mod vec3; 27 | mod mat4; 28 | 29 | // Replace this with argparse eventually 30 | struct ProgramArgs { 31 | config_file: String 32 | } 33 | 34 | #[derive(RustcDecodable, RustcEncodable)] 35 | struct SceneConfig { 36 | name: String, 37 | size: (u32, u32), 38 | fov: f64, 39 | reflect_depth: u32, 40 | refract_depth: u32, 41 | shadow_samples: u32, 42 | gloss_samples: u32, 43 | pixel_samples: u32, 44 | output_file: String, 45 | animating: bool, 46 | fps: f64, 47 | time_slice: (f64, f64), 48 | starting_frame_number: u32 49 | } 50 | 51 | fn parse_args(args: env::Args) -> Result { 52 | let args = args.collect::>(); 53 | 54 | let program_name = &args[0]; 55 | match args.len() { 56 | // I wouldn't expect this in the wild 57 | 0 => panic!("Args do not even include a program name"), 58 | 2 => Ok(ProgramArgs { config_file: args[1].clone() }), 59 | _ => Err(format!("Usage: {} scene_config.json", program_name)), 60 | } 61 | } 62 | 63 | fn main() { 64 | let start_time = ::time::get_time().sec; 65 | 66 | let program_args = match parse_args(env::args()) { 67 | Ok(program_args) => program_args, 68 | Err(error_str) => { 69 | write!(&mut io::stderr(), "{}\n", error_str).unwrap(); 70 | process::exit(1) 71 | } 72 | }; 73 | let mut file_handle = match File::open(&program_args.config_file) { 74 | Ok(file) => file, 75 | Err(err) => { 76 | write!(&mut io::stderr(), "{}\n", err).unwrap(); 77 | process::exit(1) 78 | } 79 | }; 80 | 81 | let mut json_data = String::new(); 82 | if let Err(ref err) = file_handle.read_to_string(&mut json_data) { 83 | write!(&mut io::stderr(), "{}\n", err).unwrap(); 84 | process::exit(1); 85 | } 86 | 87 | let config: SceneConfig = match json::decode(&json_data) { 88 | Ok(data) => data, 89 | Err(err) => { 90 | let msg = match err { 91 | MissingFieldError(field_name) => { 92 | format!("parse failure, missing field ``{}''\n", field_name) 93 | }, 94 | _ => { 95 | format!("parse failure: {:?}", err) 96 | } 97 | }; 98 | write!(&mut io::stderr(), "{}\n", msg).unwrap(); 99 | process::exit(1) 100 | } 101 | }; 102 | 103 | println!("Job started at {}...\nLoading scene...", start_time); 104 | 105 | let scene_config = match my_scene::scene_by_name(&config.name) { 106 | Some(scene_config) => scene_config, 107 | None => { 108 | write!(&mut io::stderr(), "unknown scene ``{}''\n", config.name).unwrap(); 109 | process::exit(1) 110 | } 111 | }; 112 | 113 | let (image_width, image_height) = config.size; 114 | let fov = config.fov; 115 | 116 | // Hackish solution for animator 117 | let shared_scene = Arc::new(scene_config.get_scene()); 118 | 119 | let camera = if config.animating { 120 | scene_config.get_animation_camera(image_width, image_height, fov) 121 | } else { 122 | scene_config.get_camera(image_width, image_height, fov) 123 | }; 124 | 125 | let scene_time = ::time::get_time().sec; 126 | println!("Scene loaded at {} ({}s)...", scene_time, scene_time - start_time); 127 | 128 | let render_options = raytracer::RenderOptions { 129 | reflect_depth: config.reflect_depth, 130 | refract_depth: config.refract_depth, 131 | shadow_samples: config.shadow_samples, 132 | gloss_samples: config.gloss_samples, 133 | pixel_samples: config.pixel_samples, 134 | }; 135 | 136 | let renderer = raytracer::Renderer { 137 | options: render_options, 138 | tasks: ::num_cpus::get(), // Number of tasks to spawn. Will use up max available cores. 139 | }; 140 | 141 | if config.animating { 142 | let (animate_from, animate_to) = config.time_slice; 143 | 144 | let animator = raytracer::animator::Animator { 145 | fps: config.fps, 146 | animate_from: animate_from, 147 | animate_to: animate_to, 148 | starting_frame_number: config.starting_frame_number, 149 | renderer: renderer 150 | }; 151 | 152 | println!("Animating - tasks: {}, FPS: {}, start: {}s, end:{}s, starting frame: {}", 153 | ::num_cpus::get(), animator.fps, animator.animate_from, animator.animate_to, 154 | animator.starting_frame_number); 155 | animator.animate(camera, shared_scene, &config.output_file); 156 | let render_time = ::time::get_time().sec; 157 | println!("Render done at {} ({}s)", 158 | render_time, render_time - scene_time); 159 | } else { 160 | // Still frame 161 | println!("Rendering with {} tasks...", ::num_cpus::get()); 162 | let image_data = renderer.render(camera, shared_scene); 163 | let render_time = ::time::get_time().sec; 164 | println!("Render done at {} ({}s)...\nWriting file...", 165 | render_time, render_time - scene_time); 166 | 167 | let out_file = format!("{}{}", config.output_file, ".ppm"); 168 | util::export::to_ppm(&image_data, &out_file).expect("ppm write failure"); 169 | let export_time = ::time::get_time().sec; 170 | 171 | println!("Write done: {} ({}s). Written to {}\nTotal: {}s", 172 | export_time, export_time - render_time, 173 | config.output_file, export_time - start_time); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/mat4.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use geometry::bbox::BBox; 4 | use raytracer::Ray; 5 | use std::cmp; 6 | use std::f64; 7 | use std::f64::consts::PI; 8 | use std::fmt; 9 | use std::ops::{Add, Mul, Sub}; 10 | use vec3::Vec3; 11 | 12 | /// Stored in row-major, M_(i, j) = i-th row and j-th column 13 | /// 0-indexed 14 | #[derive(Clone, Copy)] 15 | pub struct Mat4 { 16 | pub m: [[f64; 4]; 4] 17 | } 18 | 19 | /// We store the inverse matrix for convenience as per pbrt's recommendation 20 | pub struct Transform { 21 | pub m: Mat4, 22 | pub inv: Mat4 23 | } 24 | 25 | impl Transform { 26 | pub fn new(mat: Mat4) -> Transform { 27 | Transform { 28 | m: mat, 29 | inv: mat.inverse() 30 | } 31 | } 32 | } 33 | 34 | fn are_equal_rel(a: f64, b: f64) -> bool { 35 | let mut max = a; 36 | if max < b { 37 | max = b; 38 | } 39 | (a - b).abs() <= f64::EPSILON * max 40 | } 41 | 42 | /// Most implementations adapted from pbrt 43 | impl Mat4 { 44 | pub fn new(t00: f64, t01: f64, t02: f64, t03: f64, 45 | t10: f64, t11: f64, t12: f64, t13: f64, 46 | t20: f64, t21: f64, t22: f64, t23: f64, 47 | t30: f64, t31: f64, t32: f64, t33: f64) 48 | -> Mat4 { 49 | 50 | let mut m = [[0.0, 0.0, 0.0, 0.0], 51 | [0.0, 0.0, 0.0, 0.0], 52 | [0.0, 0.0, 0.0, 0.0], 53 | [0.0, 0.0, 0.0, 0.0]]; 54 | 55 | m[0][0] = t00; 56 | m[0][1] = t01; 57 | m[0][2] = t02; 58 | m[0][3] = t03; 59 | 60 | m[1][0] = t10; 61 | m[1][1] = t11; 62 | m[1][2] = t12; 63 | m[1][3] = t13; 64 | 65 | m[2][0] = t20; 66 | m[2][1] = t21; 67 | m[2][2] = t22; 68 | m[2][3] = t23; 69 | 70 | m[3][0] = t30; 71 | m[3][1] = t31; 72 | m[3][2] = t32; 73 | m[3][3] = t33; 74 | 75 | Mat4 { m: m } 76 | } 77 | 78 | pub fn get(&self, row: usize, column: usize) -> f64 { 79 | self.m[row][column] 80 | } 81 | 82 | pub fn identity() -> Mat4 { 83 | Mat4::new( 84 | 1.0, 0.0, 0.0, 0.0, 85 | 0.0, 1.0, 0.0, 0.0, 86 | 0.0, 0.0, 1.0, 0.0, 87 | 0.0, 0.0, 0.0, 1.0 88 | ) 89 | } 90 | 91 | pub fn zero() -> Mat4 { 92 | Mat4::new( 93 | 0.0, 0.0, 0.0, 0.0, 94 | 0.0, 0.0, 0.0, 0.0, 95 | 0.0, 0.0, 0.0, 0.0, 96 | 0.0, 0.0, 0.0, 0.0 97 | ) 98 | } 99 | 100 | pub fn translate_matrix(v: &Vec3) -> Mat4 { 101 | Mat4::new( 102 | 1.0, 0.0, 0.0, v.x, 103 | 0.0, 1.0, 0.0, v.y, 104 | 0.0, 0.0, 1.0, v.z, 105 | 0.0, 0.0, 0.0, 1.0 106 | ) 107 | } 108 | 109 | pub fn scale_matrix(v: &Vec3) -> Mat4 { 110 | Mat4::new( 111 | v.x, 0.0, 0.0, 0.0, 112 | 0.0, v.y, 0.0, 0.0, 113 | 0.0, 0.0, v.z, 0.0, 114 | 0.0, 0.0, 0.0, 1.0 115 | ) 116 | } 117 | 118 | pub fn has_scale(&self) -> bool { 119 | Mat4::approx_eq(self.get(0, 0), self.get(1, 1)) && 120 | Mat4::approx_eq(self.get(0, 0), self.get(2, 2)) 121 | } 122 | 123 | /// This assumes the matrix is scalar; check has_scale(&self) -> bool before use 124 | pub fn scale(&self) -> f64 { 125 | self.m[0][0] 126 | } 127 | 128 | pub fn rotate_x_deg_matrix(angle: f64) -> Mat4 { 129 | let sin_t = Mat4::deg_to_rad(angle).sin(); 130 | let cos_t = Mat4::deg_to_rad(angle).cos(); 131 | 132 | Mat4::new( 133 | 1.0, 0.0, 0.0, 0.0, 134 | 0.0, cos_t, -sin_t, 0.0, 135 | 0.0, sin_t, cos_t, 0.0, 136 | 0.0, 0.0, 0.0, 1.0 137 | ) 138 | } 139 | 140 | pub fn rotate_y_deg_matrix(angle: f64) -> Mat4 { 141 | let sin_t = Mat4::deg_to_rad(angle).sin(); 142 | let cos_t = Mat4::deg_to_rad(angle).cos(); 143 | 144 | Mat4::new( 145 | cos_t, 0.0, sin_t, 0.0, 146 | 0.0, 1.0, 0.0, 0.0, 147 | -sin_t, 0.0, cos_t, 0.0, 148 | 0.0, 0.0, 0.0, 1.0 149 | ) 150 | } 151 | 152 | pub fn rotate_z_deg_matrix(angle: f64) -> Mat4 { 153 | let sin_t = Mat4::deg_to_rad(angle).sin(); 154 | let cos_t = Mat4::deg_to_rad(angle).cos(); 155 | 156 | Mat4::new( 157 | cos_t, -sin_t, 0.0, 0.0, 158 | sin_t, cos_t, 0.0, 0.0, 159 | 0.0, 0.0, 1.0, 0.0, 160 | 0.0, 0.0, 0.0, 1.0 161 | ) 162 | } 163 | 164 | pub fn rotate_axis_deg_matrix(angle: f64, axis: &Vec3) -> Mat4 { 165 | let a = axis.unit(); 166 | let s = Mat4::deg_to_rad(angle).sin(); 167 | let c = Mat4::deg_to_rad(angle).cos(); 168 | 169 | let mut m = [[0.0, 0.0, 0.0, 0.0], 170 | [0.0, 0.0, 0.0, 0.0], 171 | [0.0, 0.0, 0.0, 0.0], 172 | [0.0, 0.0, 0.0, 0.0]]; 173 | 174 | m[0][0] = a.x * a.x + (1.0 - a.x * a.x) * c; 175 | m[0][1] = a.x * a.y * (1.0 - c) - a.z * s; 176 | m[0][2] = a.x * a.z * (1.0 - c) + a.y * s; 177 | m[0][3] = 0.0; 178 | 179 | m[1][0] = a.x * a.y * (1.0 - c) + a.z * s; 180 | m[1][1] = a.y * a.y + (1.0 - a.y * a.y) * c; 181 | m[1][2] = a.y * a.z * (1.0 - c) - a.x * s; 182 | m[1][3] = 0.0; 183 | 184 | m[2][0] = a.x * a.z * (1.0 - c) - a.y * s; 185 | m[2][1] = a.y * a.z * (1.0 - c) + a.x * s; 186 | m[2][2] = a.z * a.z + (1.0 - a.z * a.z) * c; 187 | m[2][3] = 0.0; 188 | 189 | m[3][0] = 0.0; 190 | m[3][1] = 0.0; 191 | m[3][2] = 0.0; 192 | m[3][3] = 1.0; 193 | 194 | Mat4 { m: m } 195 | } 196 | 197 | /// This matrix translates between world-space and camera-space 198 | pub fn look_at_matrix(pos: &Vec3, up: &Vec3, look_at: &Vec3) -> Mat4 { 199 | let dir = (*look_at - *pos).unit(); 200 | let left = (up.unit().cross(&dir)).unit(); 201 | let new_up = dir.cross(&left); 202 | 203 | Mat4::new( 204 | left.x, new_up.x, dir.x, pos.x, 205 | left.y, new_up.y, dir.y, pos.y, 206 | left.z, new_up.z, dir.z, pos.z, 207 | 0.0, 0.0, 0.0, 1.0 208 | ) 209 | } 210 | 211 | pub fn transpose(&self) -> Mat4 { 212 | Mat4::new( 213 | self.m[0][0], self.m[1][0], self.m[2][0], self.m[3][0], 214 | self.m[0][1], self.m[1][1], self.m[2][1], self.m[3][1], 215 | self.m[0][2], self.m[1][2], self.m[2][2], self.m[3][2], 216 | self.m[0][3], self.m[1][3], self.m[2][3], self.m[3][3] 217 | ) 218 | } 219 | 220 | /// Normals cannot have the transformation matrix directly applied to them 221 | pub fn transform_normal(n: &Vec3, transform: &Mat4) -> Vec3 { 222 | let inv = transform.inverse(); 223 | 224 | Vec3 { 225 | x: inv.m[0][0] * n.x + inv.m[1][0] * n.y + inv.m[2][0] * n.z, 226 | y: inv.m[0][1] * n.x + inv.m[1][1] * n.y + inv.m[2][1] * n.z, 227 | z: inv.m[0][2] * n.x + inv.m[1][2] * n.y + inv.m[2][2] * n.z 228 | } 229 | } 230 | 231 | pub fn transform_ray(_r: &Ray) -> Ray { 232 | panic!("Ray transform not implemented"); 233 | } 234 | 235 | pub fn transform_bbox(_bbox: &BBox) -> BBox { 236 | panic!("BBox transform not implemented"); 237 | } 238 | 239 | pub fn inverse(&self) -> Mat4 { 240 | let s0 = self.m[0][0] * self.m[1][1] - self.m[1][0] * self.m[0][1]; 241 | let s1 = self.m[0][0] * self.m[1][2] - self.m[1][0] * self.m[0][2]; 242 | let s2 = self.m[0][0] * self.m[1][3] - self.m[1][0] * self.m[0][3]; 243 | let s3 = self.m[0][1] * self.m[1][2] - self.m[1][1] * self.m[0][2]; 244 | let s4 = self.m[0][1] * self.m[1][3] - self.m[1][1] * self.m[0][3]; 245 | let s5 = self.m[0][2] * self.m[1][3] - self.m[1][2] * self.m[0][3]; 246 | 247 | let c5 = self.m[2][2] * self.m[3][3] - self.m[3][2] * self.m[2][3]; 248 | let c4 = self.m[2][1] * self.m[3][3] - self.m[3][1] * self.m[2][3]; 249 | let c3 = self.m[2][1] * self.m[3][2] - self.m[3][1] * self.m[2][2]; 250 | let c2 = self.m[2][0] * self.m[3][3] - self.m[3][0] * self.m[2][3]; 251 | let c1 = self.m[2][0] * self.m[3][2] - self.m[3][0] * self.m[2][2]; 252 | let c0 = self.m[2][0] * self.m[3][1] - self.m[3][0] * self.m[2][1]; 253 | 254 | let invdet = 1.0 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0); 255 | 256 | let mut m = [[0.0, 0.0, 0.0, 0.0], 257 | [0.0, 0.0, 0.0, 0.0], 258 | [0.0, 0.0, 0.0, 0.0], 259 | [0.0, 0.0, 0.0, 0.0]]; 260 | 261 | let a = self.m; 262 | 263 | m[0][0] = ( a[1][1] * c5 - a[1][2] * c4 + a[1][3] * c3) * invdet; 264 | m[0][1] = (-a[0][1] * c5 + a[0][2] * c4 - a[0][3] * c3) * invdet; 265 | m[0][2] = ( a[3][1] * s5 - a[3][2] * s4 + a[3][3] * s3) * invdet; 266 | m[0][3] = (-a[2][1] * s5 + a[2][2] * s4 - a[2][3] * s3) * invdet; 267 | 268 | m[1][0] = (-a[1][0] * c5 + a[1][2] * c2 - a[1][3] * c1) * invdet; 269 | m[1][1] = ( a[0][0] * c5 - a[0][2] * c2 + a[0][3] * c1) * invdet; 270 | m[1][2] = (-a[3][0] * s5 + a[3][2] * s2 - a[3][3] * s1) * invdet; 271 | m[1][3] = ( a[2][0] * s5 - a[2][2] * s2 + a[2][3] * s1) * invdet; 272 | 273 | m[2][0] = ( a[1][0] * c4 - a[1][1] * c2 + a[1][3] * c0) * invdet; 274 | m[2][1] = (-a[0][0] * c4 + a[0][1] * c2 - a[0][3] * c0) * invdet; 275 | m[2][2] = ( a[3][0] * s4 - a[3][1] * s2 + a[3][3] * s0) * invdet; 276 | m[2][3] = (-a[2][0] * s4 + a[2][1] * s2 - a[2][3] * s0) * invdet; 277 | 278 | m[3][0] = (-a[1][0] * c3 + a[1][1] * c1 - a[1][2] * c0) * invdet; 279 | m[3][1] = ( a[0][0] * c3 - a[0][1] * c1 + a[0][2] * c0) * invdet; 280 | m[3][2] = (-a[3][0] * s3 + a[3][1] * s1 - a[3][2] * s0) * invdet; 281 | m[3][3] = ( a[2][0] * s3 - a[2][1] * s1 + a[2][2] * s0) * invdet; 282 | 283 | Mat4 { m: m } 284 | } 285 | 286 | /// http://csherratt.github.io/csherratt/blog/2013/11/24/matrix-multiply-in-rust/ 287 | /// Note: This is the slow, unoptimised version! 288 | pub fn mult_m(a: &Mat4, b: &Mat4) -> Mat4 { 289 | let mut out = Mat4 { 290 | m: [[0.0, 0.0, 0.0, 0.0], 291 | [0.0, 0.0, 0.0, 0.0], 292 | [0.0, 0.0, 0.0, 0.0], 293 | [0.0, 0.0, 0.0, 0.0]] 294 | }; 295 | 296 | for i in 0usize..4 { 297 | for j in 0usize..4 { 298 | for k in 0usize..4 { 299 | out.m[i][j] += a.m[i][k] * b.m[k][j]; 300 | } 301 | } 302 | } 303 | 304 | out 305 | } 306 | 307 | pub fn mult_v(m: &Mat4, v: &Vec3) -> Vec3 { 308 | Vec3 { 309 | x: m.m[0][0] * v.x + m.m[0][1] * v.y + m.m[0][2] * v.z, 310 | y: m.m[1][0] * v.x + m.m[1][1] * v.y + m.m[1][2] * v.z, 311 | z: m.m[2][0] * v.x + m.m[2][1] * v.y + m.m[2][2] * v.z 312 | } 313 | } 314 | 315 | pub fn mult_p(m: &Mat4, p: &Vec3) -> Vec3 { 316 | let xp = m.m[0][0] * p.x + m.m[0][1] * p.y + m.m[0][2] * p.z + m.m[0][3]; 317 | let yp = m.m[1][0] * p.x + m.m[1][1] * p.y + m.m[1][2] * p.z + m.m[1][3]; 318 | let zp = m.m[2][0] * p.x + m.m[2][1] * p.y + m.m[2][2] * p.z + m.m[2][3]; 319 | let wp = m.m[3][0] * p.x + m.m[3][1] * p.y + m.m[3][2] * p.z + m.m[3][3]; 320 | 321 | if are_equal_rel(wp, 1.0) { 322 | // Optimisation, wp == 1.0 is common 323 | Vec3 { 324 | x: xp, 325 | y: yp, 326 | z: zp 327 | } 328 | } else { 329 | // Perspective division 330 | Vec3 { 331 | x: xp / wp, 332 | y: yp / wp, 333 | z: zp / wp 334 | } 335 | } 336 | } 337 | 338 | fn approx_eq(f1: f64, f2: f64) -> bool { 339 | (f1 - f2).abs() < ::std::f64::EPSILON 340 | } 341 | 342 | fn deg_to_rad(deg: f64) -> f64 { 343 | deg * PI / 180.0 344 | } 345 | } 346 | 347 | impl cmp::PartialEq for Mat4 { 348 | fn eq(&self, other: &Mat4) -> bool { 349 | self.m[0][0] == other.m[0][0] && 350 | self.m[0][1] == other.m[0][1] && 351 | self.m[0][2] == other.m[0][2] && 352 | self.m[0][3] == other.m[0][3] && 353 | 354 | self.m[1][0] == other.m[1][0] && 355 | self.m[1][1] == other.m[1][1] && 356 | self.m[1][2] == other.m[1][2] && 357 | self.m[1][3] == other.m[1][3] && 358 | 359 | self.m[2][0] == other.m[2][0] && 360 | self.m[2][1] == other.m[2][1] && 361 | self.m[2][2] == other.m[2][2] && 362 | self.m[2][3] == other.m[2][3] && 363 | 364 | self.m[3][0] == other.m[3][0] && 365 | self.m[3][1] == other.m[3][1] && 366 | self.m[3][2] == other.m[3][2] && 367 | self.m[3][3] == other.m[3][3] 368 | } 369 | } 370 | 371 | impl Add for Mat4 { 372 | type Output = Mat4; 373 | 374 | fn add(self, other: Mat4) -> Mat4 { 375 | let mut out = Mat4 { 376 | m: [[0.0, 0.0, 0.0, 0.0], 377 | [0.0, 0.0, 0.0, 0.0], 378 | [0.0, 0.0, 0.0, 0.0], 379 | [0.0, 0.0, 0.0, 0.0]] 380 | }; 381 | 382 | out.m[0][0] = self.m[0][0] + other.m[0][0]; 383 | out.m[0][1] = self.m[0][1] + other.m[0][1]; 384 | out.m[0][2] = self.m[0][2] + other.m[0][2]; 385 | out.m[0][3] = self.m[0][3] + other.m[0][3]; 386 | 387 | out.m[1][0] = self.m[1][0] + other.m[1][0]; 388 | out.m[1][1] = self.m[1][1] + other.m[1][1]; 389 | out.m[1][2] = self.m[1][2] + other.m[1][2]; 390 | out.m[1][3] = self.m[1][3] + other.m[1][3]; 391 | 392 | out.m[2][0] = self.m[2][0] + other.m[2][0]; 393 | out.m[2][1] = self.m[2][1] + other.m[2][1]; 394 | out.m[2][2] = self.m[2][2] + other.m[2][2]; 395 | out.m[2][3] = self.m[2][3] + other.m[2][3]; 396 | 397 | out.m[3][0] = self.m[3][0] + other.m[3][0]; 398 | out.m[3][1] = self.m[3][1] + other.m[3][1]; 399 | out.m[3][2] = self.m[3][2] + other.m[3][2]; 400 | out.m[3][3] = self.m[3][3] + other.m[3][3]; 401 | 402 | out 403 | } 404 | } 405 | 406 | impl Sub for Mat4 { 407 | type Output = Mat4; 408 | 409 | fn sub(self, other: Mat4) -> Mat4 { 410 | let mut out = Mat4 { 411 | m: [[0.0, 0.0, 0.0, 0.0], 412 | [0.0, 0.0, 0.0, 0.0], 413 | [0.0, 0.0, 0.0, 0.0], 414 | [0.0, 0.0, 0.0, 0.0]] 415 | }; 416 | 417 | out.m[0][0] = self.m[0][0] - other.m[0][0]; 418 | out.m[0][1] = self.m[0][1] - other.m[0][1]; 419 | out.m[0][2] = self.m[0][2] - other.m[0][2]; 420 | out.m[0][3] = self.m[0][3] - other.m[0][3]; 421 | 422 | out.m[1][0] = self.m[1][0] - other.m[1][0]; 423 | out.m[1][1] = self.m[1][1] - other.m[1][1]; 424 | out.m[1][2] = self.m[1][2] - other.m[1][2]; 425 | out.m[1][3] = self.m[1][3] - other.m[1][3]; 426 | 427 | out.m[2][0] = self.m[2][0] - other.m[2][0]; 428 | out.m[2][1] = self.m[2][1] - other.m[2][1]; 429 | out.m[2][2] = self.m[2][2] - other.m[2][2]; 430 | out.m[2][3] = self.m[2][3] - other.m[2][3]; 431 | 432 | out.m[3][0] = self.m[3][0] - other.m[3][0]; 433 | out.m[3][1] = self.m[3][1] - other.m[3][1]; 434 | out.m[3][2] = self.m[3][2] - other.m[3][2]; 435 | out.m[3][3] = self.m[3][3] - other.m[3][3]; 436 | 437 | out 438 | } 439 | } 440 | 441 | impl Mul for Mat4 { 442 | type Output = Mat4; 443 | 444 | fn mul(self, other: Mat4) -> Mat4 { 445 | Mat4::mult_m(&self, &other) 446 | } 447 | } 448 | 449 | impl fmt::Debug for Mat4 { 450 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 451 | // 46 spaces in between 452 | write!(f, 453 | "\n┌ ┐\n\ 454 | │{: >10.3}, {: >10.3}, {: >10.3}, {: >10.3}│\n\ 455 | │{: >10.3}, {: >10.3}, {: >10.3}, {: >10.3}│\n\ 456 | │{: >10.3}, {: >10.3}, {: >10.3}, {: >10.3}│\n\ 457 | │{: >10.3}, {: >10.3}, {: >10.3}, {: >10.3}│\n\ 458 | └ ┘\n", 459 | self.m[0][0], self.m[0][1], self.m[0][2], self.m[0][3], 460 | self.m[1][0], self.m[1][1], self.m[1][2], self.m[1][3], 461 | self.m[2][0], self.m[2][1], self.m[2][2], self.m[2][3], 462 | self.m[3][0], self.m[3][1], self.m[3][2], self.m[3][3] 463 | ) 464 | } 465 | } 466 | 467 | #[test] 468 | fn test_add() { 469 | let m = Mat4::new( 470 | 1.0, 2.0, 3.0, 4.0, 471 | 5.0, 6.0, 7.0, 8.0, 472 | 9.0, 10.0, 11.0, 12.0, 473 | 13.0, 14.0, 15.0, 16.0 474 | ); 475 | 476 | let expected = Mat4::new( 477 | 2.0, 4.0, 6.0, 8.0, 478 | 10.0, 12.0, 14.0, 16.0, 479 | 18.0, 20.0, 22.0, 24.0, 480 | 26.0, 28.0, 30.0, 32.0 481 | ); 482 | 483 | assert_eq!(m + m, expected); 484 | } 485 | 486 | #[test] 487 | fn test_sub() { 488 | let m = Mat4::new( 489 | 1.0, 2.0, 3.0, 4.0, 490 | 5.0, 6.0, 7.0, 8.0, 491 | 9.0, 10.0, 11.0, 12.0, 492 | 13.0, 14.0, 15.0, 16.0 493 | ); 494 | 495 | assert_eq!(m - m, Mat4::zero()); 496 | } 497 | 498 | #[test] 499 | fn test_mul() { 500 | let a = Mat4::new( 501 | 1.0, 3.0, 5.0, 7.0, 502 | 11.0, 13.0, 17.0, 23.0, 503 | 29.0, 31.0, 37.0, 41.0, 504 | 43.0, 47.0, 53.0, 59.0 505 | ); 506 | 507 | let b = Mat4::new( 508 | 1.0, 2.0, 3.0, 4.0, 509 | 5.0, 6.0, 7.0, 8.0, 510 | 9.0, 10.0, 11.0, 12.0, 511 | 13.0, 14.0, 15.0, 16.0 512 | ); 513 | 514 | let expected = Mat4::new( 515 | 152.0, 168.0, 184.0, 200.0, 516 | 528.0, 592.0, 656.0, 720.0, 517 | 1050.0, 1188.0, 1326.0, 1464.0, 518 | 1522.0, 1724.0, 1926.0, 2128.0 519 | ); 520 | 521 | let out = Mat4::mult_m(&a, &b); 522 | assert_eq!(out, expected); 523 | } 524 | 525 | #[test] 526 | fn test_equality() { 527 | let i1 = Mat4::identity(); 528 | let i2 = Mat4::identity(); 529 | let zero = Mat4::zero(); 530 | 531 | assert!(i1 == i2); 532 | assert!(i1 != zero); 533 | } 534 | 535 | #[test] 536 | fn test_inverse() { 537 | let i = Mat4::identity(); 538 | assert_eq!(i, i.inverse()); 539 | 540 | let m = Mat4::new( 541 | 1.0, 0.0, 1.0, 1.0, 542 | 2.0, 0.0, 1.0, 0.0, 543 | 2.0, 1.0, 1.0, 0.0, 544 | 0.0, 0.0, 1.0, 3.0 545 | ); 546 | 547 | let m_inverse = Mat4::new( 548 | -3.0, 2.0, 0.0, 1.0, 549 | 0.0, -1.0, 1.0, 0.0, 550 | 6.0, -3.0, 0.0, -2.0, 551 | -2.0, 1.0, 0.0, 1.0 552 | ); 553 | 554 | assert_eq!(m.inverse(), m_inverse); 555 | } 556 | 557 | #[test] 558 | fn test_transpose() { 559 | let m = Mat4::new( 560 | 1.0, 2.0, 3.0, 4.0, 561 | 5.0, 6.0, 7.0, 8.0, 562 | 9.0, 10.0, 11.0, 12.0, 563 | 13.0, 14.0, 15.0, 16.0 564 | ); 565 | 566 | let mt = Mat4::new( 567 | 1.0, 5.0, 9.0, 13.0, 568 | 2.0, 6.0, 10.0, 14.0, 569 | 3.0, 7.0, 11.0, 15.0, 570 | 4.0, 8.0, 12.0, 16.0 571 | ); 572 | 573 | assert!(m.transpose() == mt); 574 | } 575 | 576 | #[test] 577 | fn test_mul_with_vec() { 578 | let m = Mat4::new( 579 | 1.0, 2.0, 3.0, 4.0, 580 | 5.0, 6.0, 7.0, 8.0, 581 | 9.0, 10.0, 11.0, 12.0, 582 | 13.0, 14.0, 15.0, 16.0 583 | ); 584 | 585 | let v = Vec3 { 586 | x: 1.0, 587 | y: 2.0, 588 | z: 3.0 589 | }; 590 | 591 | let expected_w0 = Vec3 { 592 | x: 1.0 * 1.0 + 2.0 * 2.0 + 3.0 * 3.0, 593 | y: 5.0 * 1.0 + 6.0 * 2.0 + 7.0 * 3.0, 594 | z: 9.0 * 1.0 + 10.0 * 2.0 + 11.0 * 3.0 595 | }; 596 | 597 | let multiplied_w0 = Mat4::mult_v(&m, &v); 598 | assert_eq!(multiplied_w0, expected_w0); 599 | } 600 | -------------------------------------------------------------------------------- /src/material/material.rs: -------------------------------------------------------------------------------- 1 | use vec3::Vec3; 2 | 3 | /// TODO: Move specular/transmissive properties into traits 4 | pub trait Material { 5 | fn sample(&self, n: Vec3, i: Vec3, l: Vec3, u: f64, v: f64) -> Vec3; 6 | fn is_reflective(&self) -> bool; 7 | fn is_refractive(&self) -> bool; 8 | fn global_specular(&self, color: &Vec3) -> Vec3; 9 | fn global_transmissive(&self, color: &Vec3) -> Vec3; 10 | fn transmission(&self) -> Vec3; 11 | fn ior(&self) -> f64; 12 | fn is_glossy(&self) -> bool; 13 | fn glossiness(&self) -> f64; 14 | } 15 | -------------------------------------------------------------------------------- /src/material/materials/cooktorrancematerial.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts::PI; 2 | use material::{Material, Texture}; 3 | use raytracer::compositor::ColorRGBA; 4 | use vec3::Vec3; 5 | 6 | #[allow(dead_code)] 7 | #[derive(Clone)] 8 | pub struct CookTorranceMaterial { 9 | pub k_a: f64, // Ambient coefficient 10 | pub k_d: f64, // Diffuse coefficient 11 | pub k_s: f64, // Local specular coefficient 12 | pub k_sg: f64, // Global specular coefficient (mirror reflection) 13 | pub k_tg: f64, // Global transmissive coefficient (refraction) 14 | pub ambient: Vec3, // Ambient color 15 | pub diffuse: Vec3, // Diffuse color 16 | pub transmission: Vec3, // Transmissive color 17 | pub specular: Vec3, // Specular color 18 | pub roughness: f64, // Smaller = shininer => smaller highlight spot on surface 19 | pub glossiness: f64, // How glossy reflections are. 0 for non-glossy surfaces. 20 | pub gauss_constant: f64, // Controls curve of distribution of microfacets 21 | pub ior: f64, // Index of refraction, also used for specular highlights 22 | pub diffuse_texture: Option> 23 | } 24 | 25 | impl Material for CookTorranceMaterial { 26 | fn sample(&self, n: Vec3, i: Vec3, l: Vec3, u: f64, v: f64) -> Vec3 { 27 | let ambient = self.ambient.scale(self.k_a); 28 | let diffuse = self.diffuse.scale(self.k_d).scale(n.dot(&l)) * match self.diffuse_texture { 29 | Some(ref x) => x.color(u, v), 30 | None => ColorRGBA::white() 31 | }.to_vec3(); 32 | 33 | // Specular calculations 34 | let h = (l + i).unit(); 35 | let n_dot_h = n.dot(&h); 36 | let n_dot_l = n.dot(&l); 37 | let v_dot_h = i.dot(&h); 38 | let n_dot_v = n.dot(&i); 39 | 40 | // Fresnel term (Schlick's approximation) 41 | let n1 = 1.0; 42 | let n2 = self.ior; 43 | let f0 = ((n1 - n2) / (n1 + n2)).powf(2.0); 44 | let f = (1.0 - v_dot_h).powf(5.0) * (1.0 - f0) + f0; 45 | 46 | // Microfacet distribution 47 | let alpha = n_dot_h.acos(); 48 | let d = self.gauss_constant * (-alpha / self.roughness.sqrt()).exp(); 49 | 50 | // Geometric attenuation factor 51 | let g1 = (2.0 * n_dot_h * n_dot_v) / v_dot_h; 52 | let g2 = (2.0 * n_dot_h * n_dot_l) / v_dot_h; 53 | let g = g1.min(g2); 54 | 55 | let brdf = f * d * g / (n_dot_v * n_dot_l * PI); 56 | 57 | self.specular.scale(self.k_s * brdf) + diffuse + ambient 58 | } 59 | 60 | fn is_reflective(&self) -> bool { 61 | self.k_sg > 0.0 62 | } 63 | 64 | fn is_refractive(&self) -> bool { 65 | self.k_tg > 0.0 66 | } 67 | 68 | fn global_specular(&self, color: &Vec3) -> Vec3 { 69 | color.scale(self.k_sg) 70 | } 71 | 72 | fn global_transmissive(&self, color: &Vec3) -> Vec3 { 73 | color.scale(self.k_tg) 74 | } 75 | 76 | fn transmission(&self) -> Vec3 { 77 | self.transmission 78 | } 79 | 80 | fn ior(&self) -> f64 { 81 | self.ior 82 | } 83 | 84 | fn is_glossy(&self) -> bool { 85 | self.glossiness > ::std::f64::EPSILON 86 | } 87 | 88 | fn glossiness(&self) -> f64 { 89 | self.glossiness 90 | } 91 | } 92 | 93 | impl Default for CookTorranceMaterial { 94 | fn default() -> CookTorranceMaterial { 95 | CookTorranceMaterial { 96 | k_a: 0.0, 97 | k_d: 1.0, 98 | k_s: 1.0, 99 | k_sg: 0.0, 100 | k_tg: 0.0, 101 | gauss_constant: 1.0, 102 | roughness: 0.15, 103 | glossiness: 0.0, 104 | ior: 1.5, 105 | ambient: Vec3::one(), 106 | diffuse: Vec3 { x: 0.5, y: 0.5, z: 0.5 }, 107 | specular: Vec3::one(), 108 | transmission: Vec3::zero(), 109 | diffuse_texture: None 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/material/materials/flatmaterial.rs: -------------------------------------------------------------------------------- 1 | use material::Material; 2 | use vec3::Vec3; 3 | 4 | #[allow(dead_code)] 5 | #[derive(Clone)] 6 | pub struct FlatMaterial { 7 | pub color: Vec3 8 | } 9 | 10 | impl Material for FlatMaterial { 11 | fn sample(&self, _n: Vec3, _i: Vec3, _l: Vec3, _u: f64, _v: f64) -> Vec3 { 12 | self.color 13 | } 14 | 15 | fn is_reflective(&self) -> bool { 16 | false 17 | } 18 | 19 | fn is_refractive(&self) -> bool { 20 | false 21 | } 22 | 23 | fn global_specular(&self, _color: &Vec3) -> Vec3 { 24 | Vec3::zero() 25 | } 26 | 27 | fn global_transmissive(&self, _color: &Vec3) -> Vec3 { 28 | Vec3::zero() 29 | } 30 | 31 | fn transmission(&self) -> Vec3 { 32 | Vec3::zero() 33 | } 34 | 35 | fn ior(&self) -> f64 { 36 | 1.0 37 | } 38 | 39 | fn is_glossy(&self) -> bool { 40 | false 41 | } 42 | 43 | fn glossiness(&self) -> f64 { 44 | 0.0 45 | } 46 | } 47 | 48 | impl Default for FlatMaterial { 49 | fn default() -> FlatMaterial { 50 | FlatMaterial { color: Vec3 { x: 0.5, y: 0.5, z: 0.5 } } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/material/materials/phongmaterial.rs: -------------------------------------------------------------------------------- 1 | use material::{Material, Texture}; 2 | use raytracer::compositor::ColorRGBA; 3 | use vec3::Vec3; 4 | 5 | #[allow(dead_code)] 6 | #[derive(Clone)] 7 | pub struct PhongMaterial { 8 | pub k_a: f64, // Ambient coefficient 9 | pub k_d: f64, // Diffuse coefficient 10 | pub k_s: f64, // Local specular coefficient 11 | pub k_sg: f64, // Global specular coefficient (mirror reflection) 12 | pub k_tg: f64, // Global transmissive coefficient (refraction) 13 | pub ambient: Vec3, // Ambient color 14 | pub diffuse: Vec3, // Diffuse color 15 | pub transmission: Vec3, // Transmissive color 16 | pub specular: Vec3, // Specular color 17 | pub shininess: f64, // Size of Phong specular highlight 18 | pub glossiness: f64, // How glossy reflections are. 0 for non-glossy surfaces. 19 | pub ior: f64, // Index of refraction 20 | pub diffuse_texture: Option> 21 | } 22 | 23 | impl Material for PhongMaterial { 24 | fn sample(&self, n: Vec3, i: Vec3, l: Vec3, u: f64, v: f64) -> Vec3 { 25 | let h = (l + i).unit(); 26 | 27 | // Blinn-Phong approximation 28 | let ambient = self.ambient.scale(self.k_a); 29 | let diffuse = self.diffuse.scale(self.k_d).scale(n.dot(&l)) * match self.diffuse_texture { 30 | Some(ref x) => x.color(u, v), 31 | None => ColorRGBA::white() 32 | }.to_vec3(); 33 | let specular = self.specular.scale(self.k_s).scale(n.dot(&h).powf(self.shininess)); 34 | 35 | ambient + diffuse + specular 36 | } 37 | 38 | fn is_reflective(&self) -> bool { 39 | self.k_sg > 0.0 40 | } 41 | 42 | fn is_refractive(&self) -> bool { 43 | self.k_tg > 0.0 44 | } 45 | 46 | fn global_specular(&self, color: &Vec3) -> Vec3 { 47 | color.scale(self.k_sg) 48 | } 49 | 50 | fn global_transmissive(&self, color: &Vec3) -> Vec3 { 51 | color.scale(self.k_tg) 52 | } 53 | 54 | fn transmission(&self) -> Vec3 { 55 | self.transmission 56 | } 57 | 58 | fn ior(&self) -> f64 { 59 | self.ior 60 | } 61 | 62 | fn is_glossy(&self) -> bool { 63 | self.glossiness > ::std::f64::EPSILON 64 | } 65 | 66 | fn glossiness(&self) -> f64 { 67 | self.glossiness 68 | } 69 | } 70 | 71 | impl Default for PhongMaterial { 72 | fn default() -> PhongMaterial { 73 | PhongMaterial { 74 | k_a: 0.0, 75 | k_d: 1.0, 76 | k_s: 1.0, 77 | k_sg: 0.0, 78 | k_tg: 0.0, 79 | shininess: 10.0, 80 | glossiness: 0.0, 81 | ior: 1.0, 82 | ambient: Vec3::one(), 83 | diffuse: Vec3 { x: 0.5, y: 0.5, z: 0.5 }, 84 | specular: Vec3::one(), 85 | transmission: Vec3::zero(), 86 | diffuse_texture: None 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/material/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::material::Material; 2 | pub use self::texture::Texture; 3 | pub mod material; 4 | pub mod texture; 5 | 6 | pub mod materials { 7 | pub use self::cooktorrancematerial::CookTorranceMaterial; 8 | pub use self::flatmaterial::FlatMaterial; 9 | pub use self::phongmaterial::PhongMaterial; 10 | 11 | mod cooktorrancematerial; 12 | mod flatmaterial; 13 | mod phongmaterial; 14 | } 15 | 16 | pub mod textures { 17 | pub use self::checkertexture::CheckerTexture; 18 | pub use self::uvtexture::UVTexture; 19 | pub use self::imagetexture::ImageTexture; 20 | pub use self::cubemap::CubeMap; 21 | 22 | mod checkertexture; 23 | mod uvtexture; 24 | mod imagetexture; 25 | mod cubemap; 26 | } 27 | -------------------------------------------------------------------------------- /src/material/texture.rs: -------------------------------------------------------------------------------- 1 | use raytracer::compositor::ColorRGBA; 2 | 3 | pub trait Texture { 4 | fn color(&self, u: f64, v: f64) -> ColorRGBA; 5 | fn clone_self(&self) -> Box; 6 | } 7 | 8 | impl Clone for Box { 9 | fn clone(&self) -> Box { 10 | self.clone_self() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/material/textures/checkertexture.rs: -------------------------------------------------------------------------------- 1 | use material::Texture; 2 | use raytracer::compositor::ColorRGBA; 3 | 4 | 5 | #[derive(Clone)] 6 | pub struct CheckerTexture { 7 | pub color1: ColorRGBA, 8 | pub color2: ColorRGBA, 9 | pub scale: f64 // Controls how large the squares are. 10 | } 11 | 12 | impl Texture for CheckerTexture { 13 | fn color(&self, u: f64, v: f64) -> ColorRGBA { 14 | let s = (u % self.scale).abs(); 15 | let t = (v % self.scale).abs(); 16 | let half = self.scale / 2.0; 17 | 18 | if s > half && t < half || s < half && t > half { 19 | self.color1 20 | } else { 21 | self.color2 22 | } 23 | } 24 | 25 | fn clone_self(&self) -> Box { 26 | Box::new(CheckerTexture { 27 | color1: self.color1, 28 | color2: self.color2, 29 | scale: self.scale 30 | }) as Box 31 | } 32 | } 33 | 34 | impl CheckerTexture { 35 | #[allow(dead_code)] 36 | pub fn black_and_white(scale: f64) -> CheckerTexture { 37 | CheckerTexture { 38 | color1: ColorRGBA::::black(), 39 | color2: ColorRGBA::::white(), 40 | scale: scale 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/material/textures/cubemap.rs: -------------------------------------------------------------------------------- 1 | use material::textures::ImageTexture; 2 | use std::sync::mpsc::channel; 3 | use std::thread; 4 | use vec3::Vec3; 5 | 6 | #[allow(dead_code)] 7 | pub struct CubeMap { 8 | pub faces: Vec 9 | } 10 | 11 | impl CubeMap { 12 | /// For y-axis as up, load: left, right, down, up, front, back 13 | #[allow(dead_code)] 14 | pub fn load(x: &str, x_neg: &str, y: &str, y_neg: &str, z: &str, z_neg: &str) -> CubeMap { 15 | let filenames = vec![ 16 | x, x_neg, 17 | y, y_neg, 18 | z, z_neg, 19 | ]; 20 | 21 | let mut faces: Vec = Vec::with_capacity(6); 22 | unsafe { faces.set_len(6); } 23 | 24 | let (tx, rx) = channel(); 25 | 26 | for (i, filename) in filenames.iter().take(6).enumerate() { 27 | let task_tx = tx.clone(); 28 | let filename = filename.to_string(); 29 | 30 | thread::spawn(move || { 31 | task_tx.send((i, ImageTexture::load(&filename))).unwrap(); 32 | }); 33 | } 34 | drop(tx); 35 | 36 | for (i, tex) in rx { 37 | let p = faces.as_mut_ptr(); 38 | unsafe { ::std::ptr::write::(p.offset(i as isize), tex); } 39 | } 40 | 41 | CubeMap { faces: faces } 42 | } 43 | 44 | #[allow(dead_code)] 45 | pub fn color(&self, dir: Vec3) -> Vec3 { 46 | let x_mag = dir.x.abs(); 47 | let y_mag = dir.y.abs(); 48 | let z_mag = dir.z.abs(); 49 | 50 | let mut face = !0; 51 | let mut s = 0.0; 52 | let mut t = 0.0; 53 | 54 | if x_mag >= y_mag && x_mag >= z_mag { 55 | // +x -x direction 56 | face = if dir.x <= 0.0 { 0 } else { 1 }; 57 | let scale = if dir.x < 0.0 { 1.0 } else { -1.0 }; 58 | s = scale * dir.z / dir.x.abs(); 59 | t = dir.y / dir.x.abs(); 60 | } else if y_mag >= x_mag && y_mag >= z_mag { 61 | // +y -y direction 62 | face = if dir.y <= 0.0 { 2 } else { 3 }; 63 | let scale = if dir.y < 0.0 { 1.0 } else { -1.0 }; 64 | s = scale * dir.x / dir.y.abs(); 65 | t = dir.z / dir.y.abs(); 66 | } else if z_mag >= y_mag && z_mag >= x_mag { 67 | // +z -z direction 68 | face = if dir.z <= 0.0 { 4 } else { 5 }; 69 | let scale = if dir.z < 0.0 { -1.0 } else { 1.0 }; 70 | s = scale * dir.x / dir.z.abs(); 71 | t = dir.y / dir.z.abs(); 72 | } 73 | 74 | // [-1..1] -> [0..1] 75 | let seam_delta = 0.0001; 76 | s = (1.0 - (s * 0.5 + 0.5)).max(seam_delta).min(1.0 - seam_delta); 77 | t = (1.0 - (t * 0.5 + 0.5)).max(seam_delta).min(1.0 - seam_delta); 78 | 79 | if face == !0 { 80 | panic!("CubeMap could not get a cube face for direction {} {} {}", dir.x, dir.y, dir.z); 81 | } 82 | 83 | self.faces[face].sample(s, t) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/material/textures/imagetexture.rs: -------------------------------------------------------------------------------- 1 | use vec3::Vec3; 2 | use material::Texture; 3 | use raytracer::compositor::{Surface, ColorRGBA}; 4 | 5 | /// Maps the supplied (u, v) coordinate to the image (s, t). 6 | #[derive(Clone)] 7 | pub struct ImageTexture { 8 | pub image: Surface 9 | } 10 | 11 | impl ImageTexture { 12 | #[allow(dead_code)] 13 | pub fn load(filename: &str) -> ImageTexture { 14 | ImageTexture { image: ::util::import::from_image(filename).unwrap() } 15 | } 16 | 17 | // Alias, used by skybox sampling. This is needed because we aren't storing the skybox 18 | // ImageTextures as a more generic Texture (vec of objects with the Texture trait). 19 | // An ImageTexture-specific function needs to exist to be called. 20 | pub fn sample(&self, u: f64, v: f64) -> Vec3 { 21 | self.color(u, v).to_vec3() 22 | } 23 | } 24 | 25 | impl Texture for ImageTexture { 26 | fn color(&self, u: f64, v: f64) -> ColorRGBA { 27 | // Avoid out-of-bounds during bilinear filtering 28 | let s = u % 1.0 * (self.image.width as f64 - 1.0); 29 | let t = v % 1.0 * (self.image.height as f64 - 1.0); 30 | 31 | let x = s.floor() as usize; 32 | let y = t.floor() as usize; 33 | let u_ratio = s - x as f64; 34 | let v_ratio = t - y as f64; 35 | let u_opposite = 1.0 - u_ratio; 36 | let v_opposite = 1.0 - v_ratio; 37 | 38 | ( 39 | ( 40 | self.image[(x , y )].channel_f64() * u_opposite 41 | + self.image[(x + 1, y )].channel_f64() * u_ratio 42 | ) * v_opposite + ( 43 | self.image[(x , y + 1)].channel_f64() * u_opposite 44 | + self.image[(x + 1, y + 1)].channel_f64() * u_ratio 45 | ) * v_ratio 46 | ) 47 | } 48 | 49 | fn clone_self(&self) -> Box { 50 | let tex: Box = Box::new(ImageTexture { 51 | image: self.image.clone() 52 | }); 53 | tex 54 | } 55 | } 56 | 57 | #[test] 58 | fn it_bilinearly_filters() { 59 | let background = ColorRGBA::new_rgb(0, 0, 0); 60 | let mut surface = Surface::new(2, 2, background); 61 | 62 | surface[(0, 0)] = ColorRGBA::new_rgb(255, 0, 0); 63 | surface[(0, 1)] = ColorRGBA::new_rgb(0, 255, 0); 64 | surface[(1, 0)] = ColorRGBA::new_rgb(0, 0, 255); 65 | surface[(1, 1)] = ColorRGBA::new_rgb(0, 0, 0); 66 | 67 | let texture = ImageTexture { image: surface }; 68 | 69 | let left = texture.color(0.0, 0.5); 70 | assert_eq!(left.r, 0.5); 71 | assert_eq!(left.g, 0.5); 72 | assert_eq!(left.b, 0.0); 73 | 74 | let center = texture.color(0.5, 0.5); 75 | assert_eq!(center.r, 0.25); 76 | assert_eq!(center.g, 0.25); 77 | assert_eq!(center.b, 0.25); 78 | } 79 | -------------------------------------------------------------------------------- /src/material/textures/uvtexture.rs: -------------------------------------------------------------------------------- 1 | use material::Texture; 2 | use raytracer::compositor::{ColorRGBA, Channel}; 3 | 4 | 5 | /// Maps the supplied (u, v) coordinate to the (red, green) color channels. 6 | #[derive(Clone)] 7 | pub struct UVTexture; 8 | 9 | impl Texture for UVTexture { 10 | fn color(&self, u: f64, v: f64) -> ColorRGBA { 11 | let min_value = ::min_value(); 12 | let range = ::max_value() - min_value; 13 | ColorRGBA::new_rgb(u % range + min_value, v % range + min_value, min_value) 14 | } 15 | 16 | fn clone_self(&self) -> Box { 17 | Box::new(UVTexture) as Box 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/my_scene/bunny.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use scene::{Camera, Scene}; 12 | use vec3::Vec3; 13 | 14 | // 300 polys, octree is slightly slower than no octree 15 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 16 | Camera::new( 17 | Vec3 { x: 0.0, y: -150.0, z: 30.0 }, 18 | Vec3 { x: 0.0, y: 60.0, z: 50.0 }, 19 | Vec3 { x: 0.0, y: 0.0, z: 1.0 }, 20 | fov, 21 | image_width, 22 | image_height 23 | ) 24 | } 25 | 26 | pub fn get_scene() -> Scene { 27 | let mut lights: Vec> = Vec::new(); 28 | lights.push(Box::new(SphereLight { position: Vec3 { x: 200.0, y: -200.0, z: 100.0 }, color: Vec3::one(), radius: 40.0 })); 29 | lights.push(Box::new(SphereLight { position: Vec3 { x: -95.0, y: 20.0, z: 170.0 }, color: Vec3 { x: 0.5, y: 0.5, z: 0.3 }, radius: 15.0 })); 30 | 31 | let red = CookTorranceMaterial { k_a: 0.1, k_d: 0.4, k_s: 0.5, k_sg: 0.5, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.05, glossiness: 0.0, ior: 0.98, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 0.25, z: 0.1 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None}; 32 | let green = CookTorranceMaterial { k_a: 0.0, k_d: 0.4, k_s: 0.6, k_sg: 0.7, k_tg: 0.0, gauss_constant: 50.0, roughness: 0.3, glossiness: 0.0, ior: 1.5, ambient: Vec3::one(), diffuse: Vec3 { x: 0.2, y: 0.7, z: 0.2 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None}; 33 | let shiny = CookTorranceMaterial { k_a: 0.0, k_d: 0.2, k_s: 0.7, k_sg: 1.0, k_tg: 0.0, gauss_constant: 25.0, roughness: 0.01, glossiness: 0.0, ior: 0.2, ambient: Vec3::one(), diffuse: Vec3 { x: 0.9, y: 0.9, z: 0.1 }, specular: Vec3 {x: 0.9, y: 0.9, z: 0.1}, transmission: Vec3::zero(), diffuse_texture: None}; 34 | 35 | let mut prims: Vec> = Vec::new(); 36 | prims.push(Box::new(Plane { a: 0.0, b: 0.0, c: 1.0, d: -10.0, material: Box::new(green)})); 37 | prims.push(Box::new(Sphere { center: Vec3 { x: -75.0, y: 60.0, z: 50.0 }, radius: 40.0, material: Box::new(shiny.clone()) })); 38 | prims.push(Box::new(Sphere { center: Vec3 { x: -75.0, y: 60.0, z: 140.0 }, radius: 40.0, material: Box::new(shiny.clone()) })); 39 | let bunny = ::util::import::from_obj(red, false, "./docs/assets/models/bunny.obj").expect("failed to load obj model"); 40 | for triangle in bunny.triangles { prims.push(triangle); } 41 | 42 | println!("Generating octree..."); 43 | let octree = prims.into_iter().collect(); 44 | println!("Octree generated..."); 45 | 46 | Scene { 47 | lights: lights, 48 | octree: octree, 49 | background: Vec3 { x: 0.3, y: 0.5, z: 0.8 }, 50 | skybox: Some(CubeMap::load( 51 | "./docs/assets/textures/skyboxes/storm_y_up/left.png", 52 | "./docs/assets/textures/skyboxes/storm_y_up/right.png", 53 | "./docs/assets/textures/skyboxes/storm_y_up/down.png", 54 | "./docs/assets/textures/skyboxes/storm_y_up/up.png", 55 | "./docs/assets/textures/skyboxes/storm_y_up/front.png", 56 | "./docs/assets/textures/skyboxes/storm_y_up/back.png" 57 | )) 58 | } 59 | } 60 | 61 | pub struct BunnyConfig; 62 | 63 | impl super::SceneConfig for BunnyConfig { 64 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 65 | get_camera(image_width, image_height, fov) 66 | } 67 | 68 | fn get_scene(&self) -> Scene { 69 | get_scene() 70 | } 71 | } -------------------------------------------------------------------------------- /src/my_scene/cornell.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle, TriangleOptions}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use raytracer::compositor::ColorRGBA; 12 | use scene::{Camera, Scene}; 13 | use vec3::Vec3; 14 | 15 | // 10 primitives, octree is super inefficient for this scene 16 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 17 | Camera::new( 18 | Vec3 { x: 50.0, y: 25.0, z: 150.0 }, 19 | Vec3 { x: 50.0, y: 50.0, z: 50.0 }, 20 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 21 | fov, 22 | image_width, 23 | image_height 24 | ) 25 | } 26 | 27 | pub fn get_scene() -> Scene { 28 | let mut lights: Vec> = Vec::new(); 29 | lights.push(Box::new(SphereLight {position: Vec3 { x: 50.0, y: 80.0, z: 50.0 }, color: Vec3::one(), radius: 10.0 })); 30 | 31 | // Example of a textured material 32 | let checker: Box = Box::new(CheckerTexture { color1: ColorRGBA::white(), color2: ColorRGBA::new_rgb(0.8, 0.1, 0.1), scale: 16.0 }); 33 | let checker_grey = CookTorranceMaterial { k_a: 0.0, k_d: 1.0, k_s: 0.0, k_sg: 0.0, k_tg: 0.0, gauss_constant: 1.0, roughness: 0.15, glossiness: 0.0, ior: 0.7, ambient: Vec3::one(), diffuse: Vec3 { x: 0.6, y: 0.6, z: 0.6 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: Some(checker.clone()) }; 34 | 35 | // Example of a short-form material definition using defaults 36 | // let grey = CookTorranceMaterial { k_a: 0.0, k_d: 1.0, k_s: 1.0, k_sg: 0.0, k_tg: 0.0, gauss_constant: 1.0, roughness: 0.15, glossiness: 0.0, ior: 1.5, ambient: Vec3::one(), diffuse: Vec3 { x: 0.6, y: 0.6, z: 0.6 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 37 | let grey = CookTorranceMaterial { diffuse: Vec3 { x: 0.6, y: 0.6, z: 0.6 }, ..Default::default() }; 38 | 39 | let blue = CookTorranceMaterial { k_a: 0.0, k_d: 0.3, k_s: 0.7, k_sg: 0.0, k_tg: 0.0, gauss_constant: 50.0, roughness: 0.1, glossiness: 0.0, ior: 1.3, ambient: Vec3::one(), diffuse: Vec3 { x: 0.1, y: 0.1, z: 1.0 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 40 | let red = PhongMaterial { k_a: 0.0, k_d: 0.6, k_s: 0.4, k_sg: 0.8, k_tg: 0.0, shininess: 10.0, glossiness: 0.0, ior: 0.5, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 0.0, z: 0.0 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 41 | let green = PhongMaterial { k_a: 0.0, k_d: 0.9, k_s: 0.1, k_sg: 0.5, k_tg: 0.0, shininess: 10.0, glossiness: 0.0, ior: 0.7, ambient: Vec3::one(), diffuse: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 42 | let shiny = CookTorranceMaterial { k_a: 0.0, k_d: 0.2, k_s: 1.0, k_sg: 0.8, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 0.25, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None }; 43 | let shiny_glossy = CookTorranceMaterial { k_a: 0.0, k_d: 0.7, k_s: 1.0, k_sg: 0.4, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.2, ior: 0.25, ambient: Vec3::one(), diffuse: Vec3 { x: 0.3, y: 0.3, z: 1.0 }, specular: Vec3 { x: 0.3, y: 0.3, z: 1.0 }, transmission: Vec3::zero(), diffuse_texture: None }; 44 | let refract = CookTorranceMaterial { k_a: 0.0, k_d: 0.0, k_s: 1.0, k_sg: 1.0, k_tg: 1.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 3.0, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3 { x: 0.8, y: 0.8, z: 0.8 }, diffuse_texture: None }; 45 | 46 | let mut prims: Vec> = Vec::new(); 47 | prims.push(Box::new(Plane { a: 0.0, b: 0.0, c: 1.0, d: 0.0, material: Box::new(grey.clone()) })); // Ahead 48 | prims.push(Box::new(Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(checker_grey.clone()) })); // Bottom 49 | prims.push(Box::new(Plane { a: 0.0, b: -1.0, c: 0.0, d: 100.0, material: Box::new(grey.clone()) })); // Top 50 | prims.push(Box::new(Plane { a: 1.0, b: 0.0, c: 0.0, d: 0.0, material: Box::new(red.clone()) })); // Left 51 | prims.push(Box::new(Plane { a: -1.0, b: 0.0, c: 0.0, d: 100.0, material: Box::new(green.clone()) })); // Right 52 | prims.push(Box::new(Sphere { center: Vec3 { x: 30.0, y: 15.0, z: 20.0 }, radius: 15.0, material: Box::new(shiny.clone())})); 53 | prims.push(Box::new(Sphere { center: Vec3 { x: 70.0, y: 17.0, z: 60.0 }, radius: 17.0, material: Box::new(refract.clone())})); 54 | prims.push(Box::new(Sphere { center: Vec3 { x: 50.0, y: 50.0, z: 20.0 }, radius: 10.0, material: Box::new(shiny_glossy.clone())})); 55 | prims.push(Box::new(Sphere { center: Vec3 { x: 20.0, y: 13.0, z: 90.0 }, radius: 13.0, material: Box::new(blue.clone())})); 56 | 57 | let mut triopts = TriangleOptions::new( 58 | Vec3 { x: 20.0, y: 95.0, z: 20.0 }, 59 | Vec3 { x: 15.0, y: 50.0, z: 40.0 }, 60 | Vec3 { x: 35.0, y: 50.0, z: 35.0 }); 61 | triopts.texinfo([(0.5, 1.0), (0.0, 0.0), (1.0, 0.0)]); 62 | triopts.material(Box::new(blue)); 63 | prims.push(Box::new(triopts.build())); 64 | 65 | println!("Generating octree..."); 66 | let octree = prims.into_iter().collect(); 67 | println!("Octree generated..."); 68 | 69 | Scene { 70 | lights: lights, 71 | octree: octree, 72 | background: Vec3::one(), 73 | skybox: None 74 | } 75 | } 76 | 77 | pub struct CornelConfig; 78 | 79 | impl super::SceneConfig for CornelConfig { 80 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 81 | get_camera(image_width, image_height, fov) 82 | } 83 | 84 | fn get_scene(&self) -> Scene { 85 | get_scene() 86 | } 87 | } -------------------------------------------------------------------------------- /src/my_scene/cow.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use scene::{Camera, Scene}; 12 | use vec3::Vec3; 13 | 14 | // 5000 polys, cow. Octree helps. 15 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 16 | Camera::new( 17 | Vec3 { x: -2.0, y: 4.0, z: 10.0 }, 18 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 19 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 20 | fov, 21 | image_width, 22 | image_height 23 | ) 24 | } 25 | 26 | pub fn get_scene() -> Scene { 27 | let mut lights: Vec> = Vec::new(); 28 | lights.push(Box::new(SphereLight { position: Vec3 {x: 3.0, y: 10.0, z: 6.0}, color: Vec3::one(), radius: 5.0 })); 29 | 30 | let red = CookTorranceMaterial { k_a: 0.0, k_d: 0.6, k_s: 1.0, k_sg: 0.2, k_tg: 0.0, gauss_constant: 30.0, roughness: 0.1, glossiness: 0.0, ior: 0.8, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 0.25, z: 0.1 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 31 | let green = CookTorranceMaterial { k_a: 0.0, k_d: 0.5, k_s: 0.4, k_sg: 0.1, k_tg: 0.0, gauss_constant: 25.0, roughness: 0.4, glossiness: 0.0, ior: 0.95, ambient: Vec3::one(), diffuse: Vec3 { x: 0.2, y: 0.7, z: 0.2 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 32 | 33 | let mut prims: Vec> = Vec::new(); 34 | prims.push(Box::new(Plane { a: 0.0, b: 1.0, c: 0.0, d: 3.6, material: Box::new(green) })); 35 | let cow = ::util::import::from_obj(red, true, "./docs/assets/models/cow.obj").expect("failed to load obj model");; 36 | for triangle in cow.triangles { prims.push(triangle); } 37 | 38 | println!("Generating octree..."); 39 | let octree = prims.into_iter().collect(); 40 | println!("Octree generated..."); 41 | 42 | Scene { 43 | lights: lights, 44 | octree: octree, 45 | background: Vec3 { x: 0.3, y: 0.5, z: 0.8 }, 46 | skybox: None 47 | } 48 | } 49 | 50 | pub struct CowConfig; 51 | 52 | impl super::SceneConfig for CowConfig { 53 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 54 | get_camera(image_width, image_height, fov) 55 | } 56 | 57 | fn get_scene(&self) -> Scene { 58 | get_scene() 59 | } 60 | } -------------------------------------------------------------------------------- /src/my_scene/easing.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use raytracer::compositor::ColorRGBA; 12 | use raytracer::animator::easing::Easing; 13 | use scene::{Camera, Scene}; 14 | use vec3::Vec3; 15 | 16 | // Easing test scene 17 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 18 | Camera::new( 19 | Vec3 { x: 0.0, y: 0.0, z: 150.0 }, 20 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 21 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 22 | fov, 23 | image_width, 24 | image_height 25 | ) 26 | } 27 | 28 | pub fn get_animation_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 29 | Camera::new_with_keyframes( 30 | Vec3 { x: 0.0, y: 0.0, z: 150.0 }, 31 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 32 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 33 | fov, 34 | image_width, 35 | image_height, 36 | vec![ 37 | CameraKeyframe { 38 | time: 10.0, 39 | position: Vec3 { x: 0.0, y: 1000.0, z: 150.0 }, 40 | look_at: Vec3 { x: 0.0, y: 1000.0, z: 0.0 }, 41 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 42 | easing: Easing { a: 0.0, b: 0.05, c: 0.1, d: 1.0 } 43 | }, 44 | ] 45 | ) 46 | } 47 | 48 | pub fn get_scene() -> Scene { 49 | let mut lights: Vec> = Vec::new(); 50 | lights.push(Box::new(SphereLight { 51 | position: Vec3 { x: 0.0, y: 0.0, z: 150.0 }, 52 | color: Vec3::one(), 53 | radius: 10.0 54 | })); 55 | 56 | lights.push(Box::new(SphereLight { 57 | position: Vec3 { x: 0.0, y: 1000.0, z: 150.0 }, 58 | color: Vec3::one(), 59 | radius: 10.0 60 | })); 61 | 62 | let checker: Box = Box::new(CheckerTexture { 63 | color1: ColorRGBA::white(), 64 | color2: ColorRGBA::new_rgb(0.1, 0.1, 0.1), 65 | scale: 32.0 66 | }); 67 | let checker_mat = CookTorranceMaterial { 68 | k_a: 0.0, 69 | k_d: 1.0, 70 | k_s: 0.0, 71 | k_sg: 0.0, 72 | k_tg: 0.0, 73 | gauss_constant: 1.0, 74 | roughness: 0.15, 75 | glossiness: 0.0, 76 | ior: 0.7, 77 | ambient: Vec3::one(), 78 | diffuse: Vec3 { x: 0.6, y: 0.6, z: 0.6 }, 79 | specular: Vec3::one(), 80 | transmission: Vec3::zero(), 81 | diffuse_texture: Some(checker) 82 | }; 83 | 84 | let mut prims: Vec> = Vec::new(); 85 | prims.push(Box::new(Plane { 86 | a: 0.0, 87 | b: 0.0, 88 | c: 1.0, 89 | d: 0.0, 90 | material: Box::new(checker_mat) 91 | })); 92 | 93 | let octree = prims.into_iter().collect(); 94 | 95 | Scene { 96 | lights: lights, 97 | octree: octree, 98 | background: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, 99 | skybox: None 100 | } 101 | } 102 | 103 | pub struct EasingConfig; 104 | 105 | impl super::SceneConfig for EasingConfig { 106 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 107 | get_camera(image_width, image_height, fov) 108 | } 109 | 110 | fn get_animation_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 111 | get_animation_camera(image_width, image_height, fov) 112 | } 113 | 114 | fn get_scene(&self) -> Scene { 115 | get_scene() 116 | } 117 | } -------------------------------------------------------------------------------- /src/my_scene/fresnel.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use raytracer::compositor::ColorRGBA; 12 | use raytracer::animator::easing::Easing; 13 | use scene::{Camera, Scene}; 14 | use vec3::Vec3; 15 | 16 | // Fresnel test scene 17 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 18 | let height = 50.0; 19 | 20 | Camera::new( 21 | Vec3 { x: 50.0, y: height, z: 250.0 }, 22 | Vec3 { x: 50.0, y: 50.0, z: 50.0 }, 23 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 24 | fov, 25 | image_width, 26 | image_height 27 | ) 28 | } 29 | 30 | pub fn get_animation_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 31 | // State at time t=0 32 | // A keyframe at time t=0 is automatically created when insert_keyframes is called 33 | Camera::new_with_keyframes( 34 | Vec3 { x: 0.0, y: 1.0, z: 250.0 }, 35 | Vec3 { x: 0.0, y: 1.0, z: 50.0 }, 36 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 37 | fov, 38 | image_width, 39 | image_height, 40 | vec![ 41 | CameraKeyframe { 42 | time: 2.5, 43 | position: Vec3 { x: 50.0, y: 100.0, z: 250.0 }, 44 | look_at: Vec3 { x: 0.0, y: 1.0, z: 50.0 }, 45 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 46 | easing: Easing::linear() 47 | }, 48 | CameraKeyframe { 49 | time: 5.0, 50 | position: Vec3 { x: 0.0, y: 200.0, z: 250.0 }, 51 | look_at: Vec3 { x: 0.0, y: 1.0, z: 50.0 }, 52 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 53 | easing: Easing::linear() 54 | }, 55 | CameraKeyframe { 56 | time: 7.5, 57 | position: Vec3 { x: -50.0, y: 100.0, z: 250.0 }, 58 | look_at: Vec3 { x: 0.0, y: 1.0, z: 50.0 }, 59 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 60 | easing: Easing::linear() 61 | }, 62 | CameraKeyframe { 63 | time: 10.0, 64 | position: Vec3 { x: 0.0, y: 1.0, z: 250.0 }, 65 | look_at: Vec3 { x: 0.0, y: 1.0, z: 50.0 }, 66 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 67 | easing: Easing::linear() 68 | }, 69 | ] 70 | ) 71 | } 72 | 73 | pub fn get_scene() -> Scene { 74 | let mut lights: Vec> = Vec::new(); 75 | lights.push(Box::new(SphereLight { position: Vec3 { x: 50.0, y: 80.0, z: 50.0 }, color: Vec3::one(), radius: 10.0 })); 76 | 77 | 78 | let checker: Box = Box::new(CheckerTexture { color1: ColorRGBA::white(), color2: ColorRGBA::new_rgb(0.1, 0.1, 0.1), scale: 32.0 }); 79 | let checker_red = CookTorranceMaterial { k_a: 0.0, k_d: 1.0, k_s: 0.0, k_sg: 0.0, k_tg: 0.0, gauss_constant: 1.0, roughness: 0.15, glossiness: 0.0, ior: 1.5, ambient: Vec3::one(), diffuse: Vec3 { x: 0.6, y: 0.6, z: 0.6 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: Some(checker.clone()) }; 80 | let shiny = CookTorranceMaterial { k_a: 0.0, k_d: 0.2, k_s: 1.0, k_sg: 1.0, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 0.15, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None }; 81 | let global_specular_only = CookTorranceMaterial { k_a: 0.0, k_d: 0.0, k_s: 0.0, k_sg: 1.0, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 1.5, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None }; 82 | let refract = CookTorranceMaterial { k_a: 0.0, k_d: 0.0, k_s: 1.0, k_sg: 1.0, k_tg: 1.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 3.0, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None }; 83 | 84 | let mut prims: Vec> = Vec::new(); 85 | prims.push(Box::new(Plane { a: 0.0, b: 0.0, c: 1.0, d: 0.0, material: Box::new(checker_red.clone()) })); // Ahead 86 | prims.push(Box::new(Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(global_specular_only.clone()) })); // Bottom 87 | prims.push(Box::new(Sphere { center: Vec3 {x: 30.0, y: 15.0, z: 20.0 }, radius: 15.0, material: Box::new(shiny.clone()) })); 88 | prims.push(Box::new(Sphere { center: Vec3 {x: 70.0, y: 17.0, z: 60.0 }, radius: 17.0, material: Box::new(refract.clone()) })); 89 | 90 | println!("Generating octree..."); 91 | let octree = prims.into_iter().collect(); 92 | println!("Octree generated..."); 93 | 94 | Scene { 95 | lights: lights, 96 | octree: octree, 97 | background: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, 98 | skybox: None 99 | } 100 | } 101 | 102 | 103 | pub struct FresnelConfig; 104 | 105 | impl super::SceneConfig for FresnelConfig { 106 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 107 | get_camera(image_width, image_height, fov) 108 | } 109 | 110 | fn get_animation_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 111 | get_animation_camera(image_width, image_height, fov) 112 | } 113 | 114 | fn get_scene(&self) -> Scene { 115 | get_scene() 116 | } 117 | } -------------------------------------------------------------------------------- /src/my_scene/heptoroid.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use scene::{Camera, Scene}; 12 | use vec3::Vec3; 13 | 14 | // 114688 tris, 57302 verts 15 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 16 | Camera::new( 17 | Vec3 { x: 7.0, y: 2.0, z: -6.0 }, 18 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 19 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 20 | fov, 21 | image_width, 22 | image_height 23 | ) 24 | } 25 | 26 | pub fn get_scene(material_option: HeptoroidMaterial) -> Scene { 27 | let mut lights: Vec> = Vec::new(); 28 | lights.push(Box::new(SphereLight { position: Vec3 { x: 2.0, y: 3.0, z: -2.0 }, color: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, radius: 1.0 })); 29 | 30 | let heptoroid_material = match material_option { 31 | HeptoroidMaterial::Shiny => { 32 | CookTorranceMaterial { k_a: 0.0, k_d: 0.2, k_s: 1.0, k_sg: 0.55, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 0.25, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None } 33 | } 34 | HeptoroidMaterial::Refractive => { 35 | CookTorranceMaterial { k_a: 0.0, k_d: 0.0, k_s: 1.0, k_sg: 1.0, k_tg: 1.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 1.50, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3 { x: 0.8, y: 0.8, z: 0.8 }, diffuse_texture: None } 36 | } 37 | HeptoroidMaterial::White => { 38 | CookTorranceMaterial { k_a: 0.0, k_d: 0.9, k_s: 1.0, k_sg: 0.15, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.1, ior: 0.5, glossiness: 0.0, ambient: Vec3::one(), diffuse: Vec3 { x: 0.9, y: 0.85, z: 0.7 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None } 39 | } 40 | }; 41 | 42 | let mut prims: Vec> = Vec::new(); 43 | let heptoroid = ::util::import::from_obj(heptoroid_material, false, "./docs/assets/models/heptoroid.obj").ok().expect("failed to load obj model");; 44 | for triangle in heptoroid.triangles.into_iter() { prims.push(triangle); } 45 | 46 | println!("Generating octree..."); 47 | let octree = prims.into_iter().collect(); 48 | println!("Octree generated..."); 49 | 50 | Scene { 51 | lights: lights, 52 | octree: octree, 53 | background: Vec3 { x: 0.84, y: 0.34, z: 0.0 }, 54 | skybox: Some(CubeMap::load( 55 | "./docs/assets/textures/skyboxes/miramar_y_up/left.png", 56 | "./docs/assets/textures/skyboxes/miramar_y_up/right.png", 57 | "./docs/assets/textures/skyboxes/miramar_y_up/down.png", 58 | "./docs/assets/textures/skyboxes/miramar_y_up/up.png", 59 | "./docs/assets/textures/skyboxes/miramar_y_up/front.png", 60 | "./docs/assets/textures/skyboxes/miramar_y_up/back.png" 61 | )) 62 | } 63 | } 64 | 65 | #[derive(Copy, Clone)] 66 | pub enum HeptoroidMaterial { 67 | Shiny, 68 | Refractive, 69 | White, 70 | } 71 | 72 | pub struct HeptoroidConfig { 73 | material: HeptoroidMaterial, 74 | } 75 | 76 | impl HeptoroidConfig { 77 | pub fn shiny() -> HeptoroidConfig { 78 | HeptoroidConfig { material: HeptoroidMaterial::Shiny } 79 | } 80 | 81 | pub fn white() -> HeptoroidConfig { 82 | HeptoroidConfig { material: HeptoroidMaterial::White } 83 | } 84 | 85 | pub fn refractive() -> HeptoroidConfig { 86 | HeptoroidConfig { material: HeptoroidMaterial::Refractive } 87 | } 88 | } 89 | 90 | impl super::SceneConfig for HeptoroidConfig { 91 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 92 | get_camera(image_width, image_height, fov) 93 | } 94 | 95 | fn get_scene(&self) -> Scene { 96 | get_scene(self.material) 97 | } 98 | } -------------------------------------------------------------------------------- /src/my_scene/lucy.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use scene::{Camera, Scene}; 12 | use vec3::Vec3; 13 | 14 | // 50000 polys, model not included! 15 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 16 | Camera::new( 17 | Vec3 { x: -1500.0, y: 300.0, z: 600.0 }, 18 | Vec3 { x: 0.0, y: 400.0, z: -200.0 }, 19 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 20 | fov, 21 | image_width, 22 | image_height 23 | ) 24 | } 25 | 26 | pub fn get_scene() -> Scene { 27 | let mut lights: Vec> = Vec::new(); 28 | lights.push(Box::new(SphereLight { position: Vec3 { x: -1400.0, y: 200.0, z: 100.0 }, color: Vec3 { x: 1.0, y: 0.80, z: 0.40 }, radius: 50.0 })); 29 | 30 | let grey = CookTorranceMaterial { k_a: 0.0, k_d: 0.5, k_s: 0.8, k_sg: 0.5, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.1, glossiness: 0.0, ior: 0.4, ambient: Vec3::one(), diffuse: Vec3 { x: 0.6, y: 0.6, z: 0.65 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 31 | 32 | let mut prims: Vec> = Vec::new(); 33 | let lucy = ::util::import::from_obj(grey, true, "./docs/assets/models/lucy.obj").ok().expect("failed to load obj model");; 34 | for triangle in lucy.triangles.into_iter() { prims.push(triangle); } 35 | 36 | println!("Generating octree..."); 37 | let octree = prims.into_iter().collect(); 38 | println!("Octree generated..."); 39 | 40 | Scene { 41 | lights: lights, 42 | octree: octree, 43 | background: Vec3 { x: 0.84, y: 0.34, z: 0.0 }, 44 | skybox: Some(CubeMap::load( 45 | "./docs/assets/textures/skyboxes/storm_y_up/left.png", 46 | "./docs/assets/textures/skyboxes/storm_y_up/right.png", 47 | "./docs/assets/textures/skyboxes/storm_y_up/down.png", 48 | "./docs/assets/textures/skyboxes/storm_y_up/up.png", 49 | "./docs/assets/textures/skyboxes/storm_y_up/front.png", 50 | "./docs/assets/textures/skyboxes/storm_y_up/back.png" 51 | )) 52 | } 53 | } 54 | 55 | pub struct LucyConfig; 56 | 57 | impl super::SceneConfig for LucyConfig { 58 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 59 | get_camera(image_width, image_height, fov) 60 | } 61 | 62 | fn get_scene(&self) -> Scene { 63 | get_scene() 64 | } 65 | } -------------------------------------------------------------------------------- /src/my_scene/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(test, allow(dead_code))] 2 | use ::scene::{Camera, Scene}; 3 | 4 | pub mod bunny; 5 | pub mod cornell; 6 | pub mod cow; 7 | pub mod easing; 8 | pub mod fresnel; 9 | pub mod heptoroid; 10 | pub mod lucy; 11 | pub mod sibenik; 12 | pub mod sphere; 13 | pub mod sponza; 14 | pub mod tachikoma; 15 | pub mod teapot; 16 | 17 | pub trait SceneConfig { 18 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera; 19 | 20 | fn get_animation_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 21 | self.get_camera(image_width, image_height, fov) 22 | } 23 | 24 | fn get_scene(&self) -> Scene; 25 | } 26 | 27 | pub fn scene_by_name(name: &str) -> Option> { 28 | Some(match name { 29 | "bunny" => Box::new(bunny::BunnyConfig), 30 | "cornell" => Box::new(cornell::CornelConfig), 31 | "cow" => Box::new(cow::CowConfig), 32 | "easing" => Box::new(easing::EasingConfig), 33 | "fresnel" => Box::new(fresnel::FresnelConfig), 34 | "heptoroid-shiny" => Box::new(heptoroid::HeptoroidConfig::shiny()), 35 | "heptoroid-white" => Box::new(heptoroid::HeptoroidConfig::white()), 36 | "heptoroid-refractive" => Box::new(heptoroid::HeptoroidConfig::refractive()), 37 | "lucy" => Box::new(lucy::LucyConfig), 38 | "sibenik" => Box::new(sibenik::SibenikConfig), 39 | "sphere" => Box::new(sphere::SphereConfig), 40 | "sponza" => Box::new(sponza::SponzaConfig), 41 | "tachikoma" => Box::new(tachikoma::TachikomaConfig), 42 | "teapot" => Box::new(teapot::TeapotConfig), 43 | _ => return None, 44 | }) 45 | } -------------------------------------------------------------------------------- /src/my_scene/sibenik.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use raytracer::animator::easing::Easing; 12 | use raytracer::compositor::ColorRGBA; 13 | use scene::{Camera, Scene}; 14 | use vec3::Vec3; 15 | 16 | // ~70K triangles, no textures yet 17 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 18 | Camera::new( 19 | Vec3 { x: -16.0, y: -14.5, z: -2.0 }, 20 | Vec3 { x: 8.0, y: -3.0, z: 2.0 }, 21 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 22 | fov, 23 | image_width, 24 | image_height 25 | ) 26 | } 27 | // 7s target length 28 | pub fn get_animation_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 29 | Camera::new_with_keyframes( 30 | Vec3 { x: -16.0, y: -14.5, z: -2.0 }, 31 | Vec3 { x: 8.0, y: -3.0, z: 2.0 }, 32 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 33 | fov, 34 | image_width, 35 | image_height, 36 | vec![ 37 | CameraKeyframe { 38 | time: 7.0, 39 | position: Vec3 { x: 8.0, y: -13.5, z: 0.2 }, 40 | look_at: Vec3 { x: 8.5, y: 8.0, z: 2.0 }, 41 | up: Vec3 { x: -0.9, y: 0.0, z: -0.7 }, 42 | easing: Easing::linear() 43 | } 44 | ] 45 | ) 46 | } 47 | 48 | pub fn get_scene() -> Scene { 49 | let mut lights: Vec> = Vec::new(); 50 | lights.push(Box::new(SphereLight { position: Vec3 { x: 8.0, y: 8.0, z: 0.0 }, color: Vec3 { x: 1.0, y: 0.8, z: 0.4}, radius: 0.5 })); 51 | lights.push(Box::new(SphereLight { position: Vec3 { x: 8.0, y: -5.0, z: 0.0 }, color: Vec3 { x: 0.5, y: 0.4, z: 0.2}, radius: 1.0 })); 52 | lights.push(Box::new(PointLight { position: Vec3 { x: -16.0, y: -14.5, z: -2.0 }, color: Vec3 { x: 0.15, y: 0.07, z: 0.05 } })); 53 | 54 | 55 | let checker: Box = Box::new(CheckerTexture { color1: ColorRGBA::white(), color2: ColorRGBA::new_rgb(0.15, 0.11, 0.1), scale: 1.0 }); 56 | 57 | let stone = CookTorranceMaterial { k_a: 0.1, k_d: 0.8, k_s: 0.2, k_sg: 0.0, k_tg: 0.0, gauss_constant: 25.0, roughness: 1.0, glossiness: 0.0, ior: 1.5, ambient: Vec3 { x: 0.88, y: 0.83, z: 0.77 }, diffuse: Vec3 { x: 0.88, y: 0.83, z: 0.77 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 58 | let ground = CookTorranceMaterial { k_a: 0.03, k_d: 0.9, k_s: 0.3, k_sg: 0.5, k_tg: 0.0, gauss_constant: 25.0, roughness: 0.1, glossiness: 0.0, ior: 0.5, ambient: Vec3::one(), diffuse: Vec3 { x: 0.38, y: 0.38, z: 0.5 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: Some(checker.clone()) }; 59 | 60 | let mut prims: Vec> = Vec::new(); 61 | prims.push(Box::new(Plane { a: 0.0, b: -1.0, c: 0.0, d: -14.9, material: Box::new(ground.clone()) })); 62 | 63 | let sibenik = ::util::import::from_obj(stone, false, "./docs/assets/models/sibenik.obj").ok().expect("failed to load obj model");; 64 | for triangle in sibenik.triangles.into_iter() { prims.push(triangle); } 65 | 66 | println!("Generating octree..."); 67 | let octree = prims.into_iter().collect(); 68 | println!("Octree generated..."); 69 | 70 | Scene { 71 | lights: lights, 72 | octree: octree, 73 | background: Vec3 { x: 0.5, y: 0.5, z: 0.5 }, 74 | skybox: None 75 | } 76 | } 77 | 78 | 79 | pub struct SibenikConfig; 80 | 81 | impl super::SceneConfig for SibenikConfig { 82 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 83 | get_camera(image_width, image_height, fov) 84 | } 85 | 86 | fn get_animation_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 87 | get_animation_camera(image_width, image_height, fov) 88 | } 89 | 90 | fn get_scene(&self) -> Scene { 91 | get_scene() 92 | } 93 | } -------------------------------------------------------------------------------- /src/my_scene/sphere.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use raytracer::animator::easing::Easing; 12 | use scene::{Camera, Scene}; 13 | use vec3::Vec3; 14 | 15 | // Skybox test scene 16 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 17 | let up = Vec3 { x: 0.0, y: 1.0, z: 0.0 }; // y-up 18 | Camera::new( 19 | Vec3 { x: 0.0, y: 0.0, z: 10.0 }, 20 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 21 | up, 22 | fov, 23 | image_width, 24 | image_height 25 | ) 26 | 27 | // let up = Vec3 { x: 0.0, y: 0.0, z: 1.0 }; // z-up 28 | // Camera::new( 29 | // Vec3 { x: 0.0, y: 10.0, z: 0.0 }, 30 | // Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 31 | // up, 32 | // fov, 33 | // image_width, 34 | // image_height 35 | // ) 36 | } 37 | 38 | pub fn get_animation_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 39 | // State at time t=0 40 | // A keyframe at time t=0 is automatically created when insert_keyframes is called 41 | let camera = Camera::new_with_keyframes( 42 | Vec3 { x: 0.0, y: 0.0, z: 10.0 }, 43 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 44 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 45 | fov, 46 | image_width, 47 | image_height, 48 | vec![ 49 | CameraKeyframe { 50 | time: 2.5, 51 | position: Vec3 { x: 10.0, y: 0.0, z: 0.0 }, 52 | look_at: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 53 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 54 | easing: Easing::linear() 55 | }, 56 | CameraKeyframe { 57 | time: 5.0, 58 | position: Vec3 { x: 0.0, y: 0.0, z: -10.0 }, 59 | look_at: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 60 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 61 | easing: Easing::linear() 62 | }, 63 | CameraKeyframe { 64 | time: 7.5, 65 | position: Vec3 { x: -10.0, y: 0.0, z: 0.0 }, 66 | look_at: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 67 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 68 | easing: Easing::linear() 69 | }, 70 | CameraKeyframe { 71 | time: 10.0, 72 | position: Vec3 { x: 0.0, y: 0.0, z: 10.0 }, 73 | look_at: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 74 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 75 | easing: Easing::linear() 76 | }, 77 | ] 78 | ); 79 | 80 | camera 81 | } 82 | 83 | pub fn get_scene() -> Scene { 84 | let mut lights: Vec> = Vec::new(); 85 | lights.push(Box::new(SphereLight { position: Vec3 { x: 3.0, y: 10.0, z: 6.0 }, color: Vec3::one(), radius: 5.0 })); 86 | 87 | let mut prims: Vec> = Vec::new(); 88 | let shiny = CookTorranceMaterial { k_a: 0.0, k_d: 0.2, k_s: 1.0, k_sg: 1.0, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 0.05, ambient: Vec3::one(), diffuse: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None }; 89 | prims.push(Box::new(Sphere { center: Vec3::zero(), radius: 2.0, material: Box::new(shiny) })); 90 | 91 | println!("Generating octree..."); 92 | let octree = prims.into_iter().collect(); 93 | println!("Octree generated..."); 94 | 95 | // For y as up 96 | Scene { 97 | lights: lights, 98 | background: Vec3 { x: 0.3, y: 0.5, z: 0.8 }, 99 | octree: octree, 100 | skybox: Some(CubeMap::load( 101 | "./docs/assets/textures/skyboxes/storm_y_up/left.png", 102 | "./docs/assets/textures/skyboxes/storm_y_up/right.png", 103 | "./docs/assets/textures/skyboxes/storm_y_up/down.png", 104 | "./docs/assets/textures/skyboxes/storm_y_up/up.png", 105 | "./docs/assets/textures/skyboxes/storm_y_up/front.png", 106 | "./docs/assets/textures/skyboxes/storm_y_up/back.png" 107 | )) 108 | } 109 | } 110 | 111 | 112 | pub struct SphereConfig; 113 | 114 | impl super::SceneConfig for SphereConfig { 115 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 116 | get_camera(image_width, image_height, fov) 117 | } 118 | 119 | fn get_animation_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 120 | get_animation_camera(image_width, image_height, fov) 121 | } 122 | 123 | fn get_scene(&self) -> Scene { 124 | get_scene() 125 | } 126 | } -------------------------------------------------------------------------------- /src/my_scene/sponza.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use raytracer::compositor::ColorRGBA; 12 | use scene::{Camera, Scene}; 13 | use vec3::Vec3; 14 | 15 | // ~28000 triangles, complex scene with 2 lights 16 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 17 | Camera::new( 18 | Vec3 { x: 800.0, y: 30.0, z: 90.0 }, 19 | Vec3 { x: -500.0, y: 1000.0, z: -100.0 }, 20 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 21 | fov, 22 | image_width, 23 | image_height 24 | ) 25 | } 26 | 27 | pub fn get_scene() -> Scene { 28 | let mut lights: Vec> = Vec::new(); 29 | lights.push(Box::new(SphereLight { position: Vec3 { x: 0.0, y: 3000.0, z: 1000.0 }, color: Vec3 { x: 1.0, y: 0.8, z: 0.4 }, radius: 50.0 })); 30 | lights.push(Box::new(SphereLight { position: Vec3 { x: 300.0, y: 300.0, z: 60.0 }, color: Vec3 { x: 0.38, y: 0.32, z: 0.28 }, radius: 20.0 })); 31 | 32 | let checker: Box = Box::new(CheckerTexture { color1: ColorRGBA::white(), color2: ColorRGBA::new_rgb(0.15, 0.11, 0.1), scale: 32.0 }); 33 | 34 | let stone = CookTorranceMaterial { k_a: 0.1, k_d: 0.8, k_s: 0.2, k_sg: 0.2, k_tg: 0.0, gauss_constant: 50.0, roughness: 1.0, glossiness: 0.0, ior: 1.5, ambient: Vec3 { x: 0.88, y: 0.83, z: 0.77 }, diffuse: Vec3 { x: 0.88, y: 0.83, z: 0.77 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 35 | let ground = CookTorranceMaterial { k_a: 0.03, k_d: 0.9, k_s: 0.3, k_sg: 0.5, k_tg: 0.0, gauss_constant: 25.0, roughness: 0.1, glossiness: 0.0, ior: 0.5, ambient: Vec3::one(), diffuse: Vec3 { x: 0.38, y: 0.38, z: 0.5 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: Some(checker.clone()) }; 36 | let cloth = CookTorranceMaterial { k_a: 0.03, k_d: 0.8, k_s: 0.1, k_sg: 0.05, k_tg: 0.0, gauss_constant: 40.0, roughness: 0.8, glossiness: 0.0, ior: 1.3, ambient: Vec3::one(), diffuse: Vec3 { x: 0.85, y: 0.05, z: 0.05 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 37 | let shrubbery = CookTorranceMaterial { k_a: 0.03, k_d: 0.8, k_s: 0.2, k_sg: 0.05, k_tg: 0.0, gauss_constant: 50.0, roughness: 0.2, glossiness: 0.0, ior: 1.2, ambient: Vec3::one(), diffuse: Vec3 { x: 0.16, y: 0.47, z: 0.11 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 38 | 39 | let mut prims: Vec> = Vec::new(); 40 | prims.push(Box::new(Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(ground) })); 41 | 42 | let sponza_other = ::util::import::from_obj(stone, false, "./docs/assets/models/sponza_other.obj").ok().expect("failed to load obj model");; 43 | for triangle in sponza_other.triangles.into_iter() { prims.push(triangle); } 44 | 45 | let sponza_column_shrubbery = ::util::import::from_obj(shrubbery, false, "./docs/assets/models/sponza_column_shrubbery.obj").ok().expect("failed to load obj model");; 46 | for triangle in sponza_column_shrubbery.triangles.into_iter() { prims.push(triangle); } 47 | 48 | let sponza_cloth = ::util::import::from_obj(cloth, false, "./docs/assets/models/sponza_cloth.obj").ok().expect("failed to load obj model");; 49 | for triangle in sponza_cloth.triangles.into_iter() { prims.push(triangle); } 50 | 51 | println!("Generating octree..."); 52 | let octree = prims.into_iter().collect(); 53 | println!("Octree generated..."); 54 | 55 | Scene { 56 | lights: lights, 57 | octree: octree, 58 | background: Vec3 { x: 0.84, y: 0.34, z: 0.0 }, 59 | skybox: Some(CubeMap::load( 60 | "./docs/assets/textures/skyboxes/storm_y_up/left.png", 61 | "./docs/assets/textures/skyboxes/storm_y_up/right.png", 62 | "./docs/assets/textures/skyboxes/storm_y_up/down.png", 63 | "./docs/assets/textures/skyboxes/storm_y_up/up.png", 64 | "./docs/assets/textures/skyboxes/storm_y_up/front.png", 65 | "./docs/assets/textures/skyboxes/storm_y_up/back.png" 66 | )) 67 | } 68 | } 69 | 70 | pub struct SponzaConfig; 71 | 72 | impl super::SceneConfig for SponzaConfig { 73 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 74 | get_camera(image_width, image_height, fov) 75 | } 76 | 77 | fn get_scene(&self) -> Scene { 78 | get_scene() 79 | } 80 | } -------------------------------------------------------------------------------- /src/my_scene/tachikoma.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use raytracer::animator::CameraKeyframe; 11 | use scene::{Camera, Scene}; 12 | use vec3::Vec3; 13 | 14 | pub fn get_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 15 | Camera::new( 16 | Vec3 { x: 100.0, y: 60.0, z: -150.0 }, 17 | Vec3 { x: 0.0, y: 50.0, z: 0.0 }, 18 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 19 | fov, 20 | image_width, 21 | image_height 22 | ) 23 | } 24 | 25 | pub fn get_scene() -> Scene { 26 | let mut lights: Vec> = Vec::new(); 27 | lights.push(Box::new(SphereLight { position: Vec3 { x: 0.0, y: 100.0, z: 0.0 }, color: Vec3 { x: 1.0, y: 1.0, z: 1.0 }, radius: 25.0 })); 28 | 29 | let blue = CookTorranceMaterial { k_a: 0.0, k_d: 0.9, k_s: 1.0, k_sg: 0.4, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.01, glossiness: 0.0, ior: 0.25, ambient: Vec3::one(), diffuse: Vec3 { x: 0.16, y: 0.29, z: 0.44 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 30 | let floor = CookTorranceMaterial { k_a: 0.0, k_d: 0.9, k_s: 1.0, k_sg: 1.0, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.3, glossiness: 0.0, ior: 1.0, ambient: Vec3::one(), diffuse: Vec3 { x: 0.58, y: 0.63, z: 0.44 }, specular: Vec3 { x: 0.9, y: 0.9, z: 0.9 }, transmission: Vec3::zero(), diffuse_texture: None }; 31 | 32 | let mut prims: Vec> = Vec::new(); 33 | prims.push(Box::new(Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(floor.clone()) })); // Bottom 34 | 35 | let tachikoma = ::util::import::from_obj(blue, false, "./docs/assets/models/tachikoma.obj").ok().expect("failed to load obj model");; 36 | for triangle in tachikoma.triangles.into_iter() { prims.push(triangle); } 37 | 38 | println!("Generating octree..."); 39 | let octree = prims.into_iter().collect(); 40 | println!("Octree generated..."); 41 | 42 | Scene { 43 | lights: lights, 44 | octree: octree, 45 | background: Vec3 { x: 0.2, y: 0.2, z: 0.2 }, 46 | // skybox: None 47 | skybox: Some(CubeMap::load( 48 | "./docs/assets/textures/skyboxes/city_y_up/left.png", 49 | "./docs/assets/textures/skyboxes/city_y_up/right.png", 50 | "./docs/assets/textures/skyboxes/city_y_up/down.png", 51 | "./docs/assets/textures/skyboxes/city_y_up/up.png", 52 | "./docs/assets/textures/skyboxes/city_y_up/front.png", 53 | "./docs/assets/textures/skyboxes/city_y_up/back.png" 54 | )) 55 | } 56 | } 57 | 58 | pub struct TachikomaConfig; 59 | 60 | impl super::SceneConfig for TachikomaConfig { 61 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 62 | get_camera(image_width, image_height, fov) 63 | } 64 | 65 | fn get_scene(&self) -> Scene { 66 | get_scene() 67 | } 68 | } -------------------------------------------------------------------------------- /src/my_scene/teapot.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use geometry::prim::{Prim}; 4 | use geometry::prims::{Plane, Sphere, Triangle}; 5 | use light::light::{Light}; 6 | use light::lights::{PointLight, SphereLight}; 7 | use material::materials::{CookTorranceMaterial, FlatMaterial, PhongMaterial}; 8 | use material::Texture; 9 | use material::textures::{CheckerTexture, CubeMap, UVTexture, ImageTexture}; 10 | use mat4::{Mat4, Transform}; 11 | use raytracer::animator::CameraKeyframe; 12 | use scene::{Camera, Scene}; 13 | use vec3::Vec3; 14 | 15 | // When using Fresnel, set k_sg and k_tg (if applicable) to 1.0 for easier material definition. 16 | // You can still manually tweak it if you wish (try reducing k_sg for metals) 17 | 18 | // 2500 polys, marginal improvement from an octree 19 | pub fn get_teapot_camera(image_width: u32, image_height: u32, fov: f64) -> Camera { 20 | Camera::new( 21 | Vec3 { x: -0.2, y: 1.0, z: 2.0 }, 22 | Vec3 { x: 0.0, y: 0.6, z: 0.0 }, 23 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 24 | fov, 25 | image_width, 26 | image_height 27 | ) 28 | } 29 | 30 | pub fn get_teapot_scene() -> Scene { 31 | let mut lights: Vec> = Vec::new(); 32 | lights.push(Box::new(SphereLight { position: Vec3 { x: 0.6, y: 2.0, z: 1.2 }, color: Vec3::one(), radius: 1.0 })); 33 | 34 | let porcelain = CookTorranceMaterial { k_a: 0.0, k_d: 0.9, k_s: 1.0, k_sg: 1.0, k_tg: 0.0, gauss_constant: 5.0, roughness: 0.1, glossiness: 0.0, ior: 1.1, ambient: Vec3::one(), diffuse: Vec3 { x: 0.9, y: 0.85, z: 0.7 }, specular: Vec3::one(), transmission: Vec3::zero(), diffuse_texture: None }; 35 | 36 | let mut prims: Vec> = Vec::new(); 37 | // prims.push(Box::new(Plane { a: 0.0, b: 1.0, c: 0.0, d: 0.0, material: Box::new(green) })); 38 | let mut teapot = ::util::import::from_obj(porcelain, false, "./docs/assets/models/teapot.obj").ok().expect("failed to load obj model");; 39 | let rotate = Transform::new(Mat4::rotate_x_deg_matrix(1.0)); 40 | teapot.mut_transform(&rotate); 41 | for triangle in teapot.triangles.into_iter() { prims.push(triangle); } 42 | 43 | println!("Generating octree..."); 44 | let octree = prims.into_iter().collect(); 45 | println!("Octree generated..."); 46 | 47 | Scene { 48 | lights: lights, 49 | octree: octree, 50 | background: Vec3 { x: 0.3, y: 0.5, z: 0.8 }, 51 | skybox: Some(CubeMap::load( 52 | "./docs/assets/textures/skyboxes/miramar_y_up/left.png", 53 | "./docs/assets/textures/skyboxes/miramar_y_up/right.png", 54 | "./docs/assets/textures/skyboxes/miramar_y_up/down.png", 55 | "./docs/assets/textures/skyboxes/miramar_y_up/up.png", 56 | "./docs/assets/textures/skyboxes/miramar_y_up/front.png", 57 | "./docs/assets/textures/skyboxes/miramar_y_up/back.png" 58 | )) 59 | } 60 | } 61 | 62 | 63 | pub struct TeapotConfig; 64 | 65 | impl super::SceneConfig for TeapotConfig { 66 | fn get_camera(&self, image_width: u32, image_height: u32, fov: f64) -> Camera { 67 | get_teapot_camera(image_width, image_height, fov) 68 | } 69 | 70 | fn get_scene(&self) -> Scene { 71 | get_teapot_scene() 72 | } 73 | } -------------------------------------------------------------------------------- /src/raytracer/animator/animator.rs: -------------------------------------------------------------------------------- 1 | use raytracer::animator::CameraKeyframe; 2 | use raytracer::Renderer; 3 | use scene::{Camera, Scene}; 4 | use std::sync::mpsc::sync_channel; 5 | use std::sync::Arc; 6 | use std::thread; 7 | use vec3::Vec3; 8 | 9 | pub struct Animator { 10 | pub fps: f64, 11 | pub animate_from: f64, // Number of frames is rounded down to nearest frame 12 | pub animate_to: f64, 13 | pub starting_frame_number: u32, // For filename 14 | pub renderer: Renderer 15 | } 16 | 17 | // TODO: Non-linear interpolation 18 | impl Animator { 19 | // TODO: make this a Surface iterator so both single frame and animation 20 | // process flows are similar 21 | pub fn animate(&self, camera: Camera, shared_scene: Arc, filename: &str) { 22 | let animate_start = ::time::get_time(); 23 | let length = self.animate_to - self.animate_from; 24 | let total_frames = (self.fps * length).floor() as u32; 25 | 26 | // Allow one frame to be renderered while the previous one is being written 27 | let (frame_tx, frame_rx) = sync_channel(0); 28 | let (exit_tx, exit_rx) = sync_channel(0); 29 | 30 | let starting_frame_number = self.starting_frame_number; 31 | 32 | let filename = filename.to_string(); 33 | thread::spawn(move || { 34 | for (frame_num, frame_data) in frame_rx.iter().enumerate() { 35 | let file_frame_number = starting_frame_number as usize + frame_num; 36 | 37 | let shared_name = format!("{}{:06}.ppm", filename, file_frame_number); 38 | ::util::export::to_ppm(&frame_data, &shared_name).expect("ppm write failure"); 39 | } 40 | 41 | exit_tx.send(()).unwrap(); 42 | }); 43 | 44 | for frame_number in 0..total_frames { 45 | let time = self.animate_from + f64::from(frame_number) / self.fps; 46 | let lerped_camera = Animator::lerp_camera(&camera, time); 47 | let frame_data = self.renderer.render(lerped_camera, Arc::clone(&shared_scene)); 48 | frame_tx.send(frame_data).unwrap(); 49 | 50 | ::util::print_progress("*** Frame", animate_start, frame_number as usize + 1usize, total_frames as usize); 51 | println!(""); 52 | } 53 | drop(frame_tx); 54 | 55 | exit_rx.recv().unwrap(); 56 | } 57 | 58 | fn get_neighbour_keyframes(keyframes: Vec, time: f64) 59 | -> (CameraKeyframe, CameraKeyframe, f64) { 60 | 61 | if keyframes.len() <= 1 { 62 | panic!("Not enough keyframes to interpolate: got: {} expected: >= 2", keyframes.len()); 63 | } 64 | 65 | // Get the two keyframes inbetween current time 66 | let mut first = &keyframes[0]; 67 | let mut second = &keyframes[1]; 68 | 69 | for keyframe in &keyframes { 70 | if keyframe.time <= time && time - keyframe.time >= first.time - time { 71 | first = keyframe; 72 | } 73 | 74 | if keyframe.time > time && 75 | (keyframe.time - time < second.time - time || second.time < time) { 76 | second = keyframe; 77 | } 78 | } 79 | 80 | let keyframe_length = second.time - first.time; 81 | 82 | let alpha = if keyframe_length == 0.0 { 83 | 0.0 84 | } else { 85 | second.easing.t((time - first.time) / keyframe_length) 86 | }; 87 | 88 | (first.clone(), second.clone(), alpha) 89 | } 90 | 91 | fn lerp_camera(camera: &Camera, time: f64) -> Camera { 92 | let keyframes = match camera.keyframes.clone() { 93 | Some(k) => k, 94 | None => panic!("Cannot lerp a camera with no keyframes!") 95 | }; 96 | 97 | let (first, second, alpha) = Animator::get_neighbour_keyframes(keyframes, time); 98 | 99 | let lerped_position = Vec3::lerp(&first.position, &second.position, alpha); 100 | let lerped_look_at = Vec3::lerp(&first.look_at, &second.look_at, alpha); 101 | let lerped_up = Vec3::lerp(&first.up, &second.up, alpha); 102 | 103 | let mut lerped_camera = Camera::new( 104 | lerped_position, 105 | lerped_look_at, 106 | lerped_up, 107 | camera.fov_deg, 108 | camera.image_width, 109 | camera.image_height, 110 | ); 111 | 112 | lerped_camera.keyframes = camera.keyframes.clone(); 113 | lerped_camera 114 | } 115 | } 116 | 117 | #[cfg(test)] 118 | use raytracer::animator::Easing; 119 | 120 | #[test] 121 | fn test_lerp_camera_position() { 122 | // Camera rotates 180 degrees 123 | let camera = Camera::new_with_keyframes( 124 | Vec3 { x: -1.0, y: -1.0, z: -1.0 }, 125 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 126 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 127 | 45.0, 128 | 10, 129 | 10, 130 | vec![ 131 | CameraKeyframe { 132 | time: 5.0, 133 | position: Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 134 | look_at: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 135 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 136 | easing: Easing::linear() 137 | }, 138 | CameraKeyframe { 139 | time: 10.0, 140 | position: Vec3 { x: 10.0, y: 0.0, z: 0.0 }, 141 | look_at: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 142 | up: Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 143 | easing: Easing::linear() 144 | }, 145 | ] 146 | ); 147 | 148 | let expected_position_0 = Vec3 { x: -1.0, y: -1.0, z: -1.0 }; 149 | assert_eq!(Animator::lerp_camera(&camera, 0.0).position, expected_position_0); 150 | 151 | let expected_position_5 = Vec3 { x: 0.0, y: 0.0, z: 0.0 }; 152 | assert_eq!(Animator::lerp_camera(&camera, 5.0).position, expected_position_5); 153 | 154 | let expected_position_7_5 = Vec3 { x: 5.0, y: 0.0, z: 0.0 }; 155 | assert_eq!(Animator::lerp_camera(&camera, 7.5).position, expected_position_7_5); 156 | 157 | let expected_position_10 = Vec3 { x: 10.0, y: 0.0, z: 0.0 }; 158 | assert_eq!(Animator::lerp_camera(&camera, 10.0).position, expected_position_10); 159 | } 160 | -------------------------------------------------------------------------------- /src/raytracer/animator/camerakeyframe.rs: -------------------------------------------------------------------------------- 1 | use vec3::Vec3; 2 | use raytracer::animator::Easing; 3 | 4 | #[derive(Clone)] 5 | pub struct CameraKeyframe { 6 | pub time: f64, 7 | pub position: Vec3, 8 | pub look_at: Vec3, 9 | pub up: Vec3, 10 | pub easing: Easing 11 | } 12 | -------------------------------------------------------------------------------- /src/raytracer/animator/easing.rs: -------------------------------------------------------------------------------- 1 | /// Tries to fit a curve where t is in the range [0, 1] and 2 | /// a is t=0, b is t=0.33.., c is t=0.66.., and d is t=1.0 3 | #[derive(Clone)] 4 | pub struct Easing { 5 | pub a: f64, 6 | pub b: f64, 7 | pub c: f64, 8 | pub d: f64 9 | } 10 | 11 | impl Easing { 12 | pub fn linear() -> Easing { 13 | Easing { 14 | a: 0.0, 15 | b: 1.0 / 3.0, 16 | c: 2.0 / 3.0, 17 | d: 1.0 18 | } 19 | } 20 | 21 | pub fn t(&self, t: f64) -> f64 { 22 | Easing::interpolate_cubic(self.a, self.b, self.c, self.d, t) 23 | } 24 | 25 | fn interpolate_cubic(a: f64, b: f64, c: f64, d: f64, t: f64) -> f64 { 26 | let abc = Easing::interpolate_quadratic(a, b, c, t); 27 | let bcd = Easing::interpolate_quadratic(b, c, d, t); 28 | 29 | Easing::interpolate_linear(abc, bcd, t) 30 | } 31 | 32 | fn interpolate_quadratic(a: f64, b: f64, c: f64, t: f64) -> f64 { 33 | let ab = Easing::interpolate_linear(a, b, t); 34 | let bc = Easing::interpolate_linear(b, c, t); 35 | 36 | Easing::interpolate_linear(ab, bc, t) 37 | } 38 | 39 | fn interpolate_linear(a: f64, b: f64, t: f64) -> f64 { 40 | (1.0 - t) * a + t * b 41 | } 42 | } 43 | 44 | #[test] 45 | fn test_easing() { 46 | // This also tests Bezier easing as linear easing 47 | // is implemented using Bezier easing. 48 | let linear_easing = Easing::linear();; 49 | assert_eq!(linear_easing.t(0.0), 0.0); 50 | assert_eq!(linear_easing.t(0.25), 0.25); 51 | assert_eq!(linear_easing.t(0.5), 0.5); 52 | assert_eq!(linear_easing.t(0.75), 0.75); 53 | assert_eq!(linear_easing.t(1.0), 1.0); 54 | } 55 | -------------------------------------------------------------------------------- /src/raytracer/animator/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::animator::Animator; 2 | pub use self::easing::Easing; 3 | pub use self::camerakeyframe::CameraKeyframe; 4 | 5 | pub mod animator; 6 | pub mod easing; 7 | pub mod camerakeyframe; 8 | -------------------------------------------------------------------------------- /src/raytracer/compositor/colorrgba.rs: -------------------------------------------------------------------------------- 1 | use num::{Float, ToPrimitive}; 2 | use std::cmp::{min, max, Ord}; 3 | use std::ops::{Add, Mul, Sub}; 4 | use vec3::Vec3; 5 | 6 | pub trait Channel: ToPrimitive { 7 | fn min_value() -> Self; 8 | fn max_value() -> Self; 9 | fn add(a: Self, b: Self) -> Self; 10 | fn sub(a: Self, b: Self) -> Self; 11 | } 12 | 13 | impl Channel for u8 { 14 | #[inline] 15 | fn min_value() -> u8 { u8::min_value() } 16 | 17 | #[inline] 18 | fn max_value() -> u8 { u8::max_value() } 19 | 20 | #[inline] 21 | fn add(a: u8, b: u8) -> u8 { a.saturating_add(b) } 22 | 23 | #[inline] 24 | fn sub(a: u8, b: u8) -> u8 { a.saturating_sub(b) } 25 | } 26 | 27 | impl Channel for f64 { 28 | #[inline] 29 | fn min_value() -> f64 { 0.0 } 30 | 31 | #[inline] 32 | fn max_value() -> f64 { 1.0 } 33 | 34 | #[inline] 35 | fn add(a: f64, b: f64) -> f64 { a + b } 36 | 37 | #[inline] 38 | fn sub(a: f64, b: f64) -> f64 { a - b } 39 | } 40 | 41 | 42 | #[derive(Copy)] 43 | pub struct ColorRGBA { 44 | pub r: T, 45 | pub g: T, 46 | pub b: T, 47 | pub a: T, 48 | } 49 | 50 | impl Clone for ColorRGBA { 51 | fn clone(&self) -> ColorRGBA { 52 | ColorRGBA { 53 | r: self.r.clone(), 54 | g: self.g.clone(), 55 | b: self.b.clone(), 56 | a: self.a.clone() 57 | } 58 | } 59 | } 60 | 61 | fn clamp(value: T, min_value: T, max_value: T) -> T { 62 | max(min(value, max_value), min_value) 63 | } 64 | 65 | // Maybe later?: ColorRGBA.quantize() -> ColorRGBA 66 | // How do we implement this more generally so that we may have ColorRGBA 67 | impl ColorRGBA { 68 | pub fn new_rgb_clamped(r: f64, g: f64, b: f64) -> ColorRGBA { 69 | let min_color: u8 = Channel::min_value(); 70 | let max_color: u8 = Channel::max_value(); 71 | 72 | ColorRGBA::new_rgb( 73 | clamp((r * max_color as f64).round() as i32, min_color as i32, max_color as i32) as u8, 74 | clamp((g * max_color as f64).round() as i32, min_color as i32, max_color as i32) as u8, 75 | clamp((b * max_color as f64).round() as i32, min_color as i32, max_color as i32) as u8) 76 | } 77 | } 78 | 79 | // Maybe later?: ColorRGBA.quantize() -> ColorRGBA 80 | // How do we implement this more generally so that we may have ColorRGBA 81 | impl ColorRGBA { 82 | #[allow(dead_code)] 83 | pub fn new_rgba(r: T, g: T, b: T, a: T) -> ColorRGBA { 84 | ColorRGBA { r: r, g: g, b: b, a: a } 85 | } 86 | 87 | #[allow(dead_code)] 88 | pub fn new_rgb(r: T, g: T, b: T) -> ColorRGBA { 89 | ColorRGBA { r: r, g: g, b: b, a: Channel::max_value() } 90 | } 91 | 92 | #[allow(dead_code)] 93 | pub fn black() -> ColorRGBA { 94 | ColorRGBA::new_rgb( 95 | Channel::min_value(), 96 | Channel::min_value(), 97 | Channel::min_value()) 98 | } 99 | 100 | #[allow(dead_code)] 101 | pub fn white() -> ColorRGBA { 102 | ColorRGBA::new_rgb( 103 | Channel::max_value(), 104 | Channel::max_value(), 105 | Channel::max_value()) 106 | } 107 | 108 | pub fn transparent() -> ColorRGBA { 109 | ColorRGBA::new_rgba( 110 | Channel::min_value(), 111 | Channel::min_value(), 112 | Channel::min_value(), 113 | Channel::min_value()) 114 | } 115 | 116 | pub fn channel_f64(&self) -> ColorRGBA { 117 | let max_val: T = Channel::max_value(); 118 | ColorRGBA { 119 | r: self.r.to_f64().unwrap() / max_val.to_f64().unwrap(), 120 | g: self.g.to_f64().unwrap() / max_val.to_f64().unwrap(), 121 | b: self.b.to_f64().unwrap() / max_val.to_f64().unwrap(), 122 | a: self.a.to_f64().unwrap() / max_val.to_f64().unwrap(), 123 | } 124 | } 125 | 126 | // Here until we have vec operations (add, mul) for color 127 | // We also need ColorRGBA 128 | pub fn to_vec3(&self) -> Vec3 { 129 | let color = self.channel_f64(); 130 | Vec3 { 131 | x: color.r, 132 | y: color.g, 133 | z: color.b, 134 | } 135 | } 136 | } 137 | 138 | impl Add for ColorRGBA { 139 | type Output = ColorRGBA; 140 | 141 | fn add(self, other: ColorRGBA) -> ColorRGBA { 142 | ColorRGBA { 143 | r: Channel::add(self.r, other.r), 144 | g: Channel::add(self.g, other.g), 145 | b: Channel::add(self.b, other.b), 146 | a: Channel::add(self.a, other.a), 147 | } 148 | } 149 | } 150 | 151 | impl Sub for ColorRGBA { 152 | type Output = ColorRGBA; 153 | 154 | fn sub(self, other: ColorRGBA) -> ColorRGBA { 155 | ColorRGBA { 156 | r: Channel::sub(self.r, other.r), 157 | g: Channel::sub(self.g, other.g), 158 | b: Channel::sub(self.b, other.b), 159 | a: Channel::sub(self.a, other.a), 160 | } 161 | } 162 | } 163 | 164 | impl Mul for ColorRGBA { 165 | type Output = ColorRGBA; 166 | 167 | fn mul(self, other: ColorRGBA) -> ColorRGBA { 168 | ColorRGBA { 169 | r: self.r * other.r, 170 | g: self.g * other.g, 171 | b: self.b * other.b, 172 | a: self.a * other.a 173 | } 174 | } 175 | } 176 | 177 | // Scalar multiplication 178 | impl Mul for ColorRGBA { 179 | type Output = ColorRGBA; 180 | 181 | fn mul(self, other: T) -> ColorRGBA { 182 | ColorRGBA { 183 | r: self.r * other, 184 | g: self.g * other, 185 | b: self.b * other, 186 | a: self.a 187 | } 188 | } 189 | } 190 | 191 | #[test] 192 | fn color_add() { 193 | let foo_color: ColorRGBA = ColorRGBA::new_rgba(1, 1, 1, 1) + 194 | ColorRGBA::new_rgba(2, 2, 2, 2); 195 | assert_eq!(foo_color.r, 3); 196 | assert_eq!(foo_color.g, 3); 197 | assert_eq!(foo_color.b, 3); 198 | assert_eq!(foo_color.a, 3); 199 | 200 | let foo_color: ColorRGBA = ColorRGBA::new_rgba(200, 1, 1, 1) + 201 | ColorRGBA::new_rgba(200, 2, 2, 2); 202 | assert_eq!(foo_color.r, 255); 203 | assert_eq!(foo_color.g, 3); 204 | assert_eq!(foo_color.b, 3); 205 | assert_eq!(foo_color.a, 3); 206 | } 207 | 208 | #[test] 209 | fn color_sub() { 210 | let foo_color: ColorRGBA = ColorRGBA::new_rgba(7, 7, 7, 7) - 211 | ColorRGBA::new_rgba(2, 2, 2, 2); 212 | assert_eq!(foo_color.r, 5); 213 | assert_eq!(foo_color.g, 5); 214 | assert_eq!(foo_color.b, 5); 215 | assert_eq!(foo_color.a, 5); 216 | } 217 | 218 | #[test] 219 | fn color_mul() { 220 | let foo_color = ColorRGBA::::new_rgb(0.5, 0.0, 0.0) * 2.0; 221 | 222 | assert_eq!(foo_color.r, 1.0); 223 | assert_eq!(foo_color.g, 0.0); 224 | assert_eq!(foo_color.b, 0.0); 225 | assert_eq!(foo_color.a, 1.0); 226 | } 227 | 228 | -------------------------------------------------------------------------------- /src/raytracer/compositor/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::colorrgba::{Channel, ColorRGBA}; 2 | pub use self::surface::Surface; 3 | pub use self::surfacefactory::SurfaceFactory; 4 | pub use self::surfaceiterator::SurfaceIterator; 5 | 6 | pub mod colorrgba; 7 | pub mod surface; 8 | pub mod surfacefactory; 9 | pub mod surfaceiterator; 10 | -------------------------------------------------------------------------------- /src/raytracer/compositor/surface.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::min; 2 | use std::iter::repeat; 3 | use std::ops::{Index, IndexMut}; 4 | 5 | use raytracer::compositor::{ColorRGBA, SurfaceFactory}; 6 | 7 | pub struct IterPixelMut<'a, T: 'a>(::std::slice::IterMut<'a, ColorRGBA>); 8 | 9 | impl<'a, T> Iterator for IterPixelMut<'a, T> where T: 'a { 10 | type Item = &'a mut ColorRGBA; 11 | 12 | fn next(&mut self) -> Option<&'a mut ColorRGBA> { 13 | self.0.next() 14 | } 15 | } 16 | 17 | #[derive(Clone)] 18 | pub struct Surface { 19 | pub width: usize, 20 | pub height: usize, 21 | pub x_off: usize, 22 | pub y_off: usize, 23 | pub background: ColorRGBA, 24 | pub buffer: Vec>, 25 | } 26 | 27 | 28 | #[allow(dead_code)] 29 | impl Surface { 30 | pub fn new(width: usize, height: usize, background: ColorRGBA) -> Surface { 31 | Surface { 32 | width: width, 33 | height: height, 34 | x_off: 0, 35 | y_off: 0, 36 | background: background, 37 | buffer: repeat(background).take(width * height).collect() 38 | } 39 | } 40 | 41 | pub fn with_offset(width: usize, height: usize, x_off: usize, y_off: usize, 42 | background: ColorRGBA) -> Surface { 43 | Surface { 44 | width: width, 45 | height: height, 46 | x_off: x_off, 47 | y_off: y_off, 48 | background: background, 49 | buffer: repeat(background).take(width * height).collect() 50 | } 51 | } 52 | 53 | pub fn divide(&self, tile_width: usize, tile_height: usize) -> SubsurfaceIterator { 54 | SubsurfaceIterator { 55 | parent_width: self.width, 56 | parent_height: self.height, 57 | background: self.background, 58 | x_delta: tile_width, 59 | y_delta: tile_height, 60 | x_off: 0, 61 | y_off: 0, 62 | } 63 | } 64 | 65 | pub fn overrender_size(&self, tile_width: usize, tile_height: usize) -> (usize, usize) { 66 | let mut width = self.width; 67 | let width_partial_tile = width % tile_width; 68 | if width_partial_tile > 0 { 69 | width -= width_partial_tile; 70 | width += tile_width; 71 | } 72 | 73 | let mut height = self.height; 74 | let height_partial_tile = height % tile_height; 75 | if height_partial_tile > 0 { 76 | height -= height_partial_tile; 77 | height += tile_height; 78 | } 79 | 80 | (width, height) 81 | } 82 | 83 | pub fn merge(&mut self, tile: &Surface) { 84 | let x_len: usize = min(tile.width, self.width - tile.x_off); 85 | let y_len: usize = min(tile.height, self.height - tile.y_off); 86 | 87 | for src_y in 0..y_len { 88 | let dst_y = tile.y_off + src_y; 89 | for src_x in 0..x_len { 90 | let dst_x = tile.x_off + src_x; 91 | self[(dst_x, dst_y)] = tile[(src_x, src_y)] 92 | } 93 | } 94 | } 95 | 96 | #[inline] 97 | pub fn pixel_count(&self) -> usize { 98 | self.buffer.len() 99 | } 100 | 101 | #[inline] 102 | fn get_idx(&self, x: usize, y: usize) -> usize { 103 | if self.width <= x { 104 | panic!("`x` out of bounds (0 <= {} < {}", x, self.width); 105 | } 106 | if self.height <= y { 107 | panic!("`y` out of bounds (0 <= {} < {}", y, self.height); 108 | } 109 | self.width * y + x 110 | } 111 | 112 | pub fn iter_pixels_mut<'a>(&'a mut self) -> IterPixelMut<'a, u8> { 113 | IterPixelMut(self.buffer.iter_mut()) 114 | } 115 | } 116 | 117 | impl Index<(usize, usize)> for Surface { 118 | type Output = ColorRGBA; 119 | 120 | fn index<'a>(&'a self, index: (usize, usize)) -> &'a ColorRGBA { 121 | let (x, y) = index; 122 | let idx = self.get_idx(x, y); 123 | &self.buffer[idx] 124 | } 125 | } 126 | 127 | impl IndexMut<(usize, usize)> for Surface { 128 | fn index_mut<'a>(&'a mut self, index: (usize, usize)) -> &'a mut ColorRGBA { 129 | let (x, y) = index; 130 | let idx = self.get_idx(x, y); 131 | &mut self.buffer[idx] 132 | } 133 | } 134 | 135 | pub struct SubsurfaceIterator { 136 | x_delta: usize, 137 | x_off: usize, 138 | y_delta: usize, 139 | y_off: usize, 140 | parent_width: usize, 141 | parent_height: usize, 142 | background: ColorRGBA, 143 | } 144 | 145 | 146 | impl SubsurfaceIterator { 147 | fn incr_tile(&mut self) { 148 | if self.x_off + self.x_delta < self.parent_width { 149 | self.x_off += self.x_delta; 150 | } else { 151 | self.x_off = 0; 152 | self.y_off += self.y_delta; 153 | } 154 | } 155 | 156 | fn current_tile(&self) -> Option { 157 | if self.x_off < self.parent_width && self.y_off < self.parent_height { 158 | Some(SurfaceFactory::new( 159 | self.x_delta, 160 | self.y_delta, 161 | self.x_off, 162 | self.y_off, 163 | self.background 164 | )) 165 | } else { 166 | None 167 | } 168 | } 169 | } 170 | 171 | impl Iterator for SubsurfaceIterator { 172 | type Item = SurfaceFactory; 173 | 174 | fn next(&mut self) -> Option { 175 | let tile = self.current_tile(); 176 | self.incr_tile(); 177 | tile 178 | } 179 | } 180 | 181 | 182 | #[test] 183 | fn test_measurement() { 184 | let width = 800; 185 | let height = 600; 186 | let width_tile = 128; 187 | let height_tile = 8; 188 | 189 | let background: ColorRGBA = ColorRGBA::new_rgb(0, 0, 0); 190 | let surf: Surface = Surface::new(width, height, background); 191 | 192 | let mut total_pixels = 0; 193 | 194 | for tile_factory in surf.divide(width_tile, height_tile) { 195 | total_pixels += tile_factory.create().pixel_count(); 196 | } 197 | 198 | let (or_width, or_height) = surf.overrender_size(width_tile, height_tile); 199 | 200 | assert_eq!(or_width * or_height, total_pixels); 201 | } 202 | 203 | #[test] 204 | fn test_paint_it_red() { 205 | let width = 800; 206 | let height = 600; 207 | let width_tile = 128; 208 | let height_tile = 8; 209 | 210 | let background: ColorRGBA = ColorRGBA::new_rgb(0, 0, 0); 211 | let mut surf: Surface = Surface::new(width, height, background); 212 | 213 | for tile_factory in surf.divide(width_tile, height_tile) { 214 | let mut tile = tile_factory.create(); 215 | for y in 0..tile.height { 216 | for x in 0..tile.width { 217 | tile[(x, y)] = ColorRGBA::new_rgb(255, 0, 0); 218 | } 219 | } 220 | for y in 0..tile.height { 221 | for x in 0..tile.width { 222 | assert_eq!(tile[(x, y)].r, 255); 223 | assert_eq!(tile[(x, y)].g, 0); 224 | assert_eq!(tile[(x, y)].b, 0); 225 | } 226 | } 227 | surf.merge(&tile); 228 | } 229 | 230 | for y in 0..surf.height { 231 | for x in 0..surf.width { 232 | let color = surf[(x, y)]; 233 | if color.r != 255 { 234 | panic!("wrong pixel at {}x{}", x, y); 235 | } 236 | if color.g != 0 { 237 | panic!("wrong pixel at {}x{}", x, y); 238 | } 239 | if color.b != 0 { 240 | panic!("wrong pixel at {}x{}", x, y); 241 | } 242 | } 243 | } 244 | 245 | // Check the iterator too 246 | for color in surf.buffer.iter() { 247 | assert_eq!(color.r, 255); 248 | assert_eq!(color.g, 0); 249 | assert_eq!(color.b, 0); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/raytracer/compositor/surfacefactory.rs: -------------------------------------------------------------------------------- 1 | use raytracer::compositor::{ColorRGBA, Surface}; 2 | 3 | 4 | pub struct SurfaceFactory { 5 | pub width: usize, 6 | pub height: usize, 7 | pub x_off: usize, 8 | pub y_off: usize, 9 | pub background: ColorRGBA 10 | } 11 | 12 | 13 | impl SurfaceFactory { 14 | pub fn new(width: usize, height: usize, x_off: usize, y_off: usize, 15 | background: ColorRGBA) -> SurfaceFactory { 16 | SurfaceFactory { 17 | width: width, 18 | height: height, 19 | x_off: x_off, 20 | y_off: y_off, 21 | background: background 22 | } 23 | } 24 | 25 | #[allow(dead_code)] 26 | pub fn create(&self) -> Surface { 27 | Surface::with_offset(self.width, self.height, self.x_off, self.y_off, self.background) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/raytracer/compositor/surfaceiterator.rs: -------------------------------------------------------------------------------- 1 | use raytracer::compositor::{ColorRGBA, SurfaceFactory}; 2 | 3 | 4 | pub struct SurfaceIterator { 5 | x_delta: usize, 6 | x_off: usize, 7 | y_delta: usize, 8 | y_off: usize, 9 | parent_width: usize, 10 | parent_height: usize, 11 | background: ColorRGBA, 12 | } 13 | 14 | 15 | impl SurfaceIterator { 16 | fn incr_tile(&mut self) { 17 | if self.x_off + self.x_delta < self.parent_width { 18 | self.x_off += self.x_delta; 19 | } else { 20 | self.x_off = 0; 21 | self.y_off += self.y_delta; 22 | } 23 | } 24 | 25 | fn current_tile(&self) -> Option { 26 | if self.x_off < self.parent_width && self.y_off < self.parent_height { 27 | Some(SurfaceFactory::new( 28 | self.x_delta, 29 | self.y_delta, 30 | self.x_off, 31 | self.y_off, 32 | self.background 33 | )) 34 | } else { 35 | None 36 | } 37 | } 38 | } 39 | 40 | impl Iterator for SurfaceIterator { 41 | type Item = SurfaceFactory; 42 | 43 | fn next(&mut self) -> Option { 44 | let tile = self.current_tile(); 45 | self.incr_tile(); 46 | tile 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/raytracer/intersection.rs: -------------------------------------------------------------------------------- 1 | use material::Material; 2 | use vec3::Vec3; 3 | 4 | pub struct Intersection<'a> { 5 | pub n: Vec3, 6 | pub t: f64, 7 | pub u: f64, 8 | pub v: f64, 9 | pub position: Vec3, 10 | pub material: &'a Box 11 | } 12 | -------------------------------------------------------------------------------- /src/raytracer/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::animator::{Animator, CameraKeyframe}; 2 | pub use self::intersection::Intersection; 3 | pub use self::ray::Ray; 4 | pub use self::octree::Octree; 5 | pub use self::renderer::{Renderer, RenderOptions}; 6 | 7 | pub mod animator; 8 | pub mod compositor; 9 | pub mod intersection; 10 | pub mod octree; 11 | pub mod ray; 12 | pub mod renderer; 13 | -------------------------------------------------------------------------------- /src/raytracer/octree.rs: -------------------------------------------------------------------------------- 1 | use std::slice::Iter; 2 | use std::iter::FromIterator; 3 | use geometry::{BBox, PartialBoundingBox}; 4 | use raytracer::Ray; 5 | use vec3::Vec3; 6 | 7 | // 8 | 9 | pub struct Octree where T: PartialBoundingBox { 10 | prims: Vec, 11 | infinites: Vec, // for infinite prims (planes) 12 | root: OctreeNode, 13 | } 14 | 15 | impl FromIterator for Octree where T: PartialBoundingBox { 16 | fn from_iter(iterator: I) -> Self where I: IntoIterator { 17 | let iterator = iterator.into_iter(); 18 | 19 | let (finites, infinites): (Vec, Vec) = 20 | iterator.partition(|item| item.partial_bounding_box().is_some()); 21 | 22 | // TODO(sell): why do we need to map here? &T isn't PartialBoundingBox, 23 | // but we need to find out how to make it so. 24 | let bounds = BBox::from_union(finites.iter().map(|i| i.partial_bounding_box())) 25 | .unwrap_or(BBox::zero()); 26 | 27 | // pbrt recommended max depth for a k-d tree (though, we're using an octree) 28 | // For a k-d tree: 8 + 1.3 * log2(N) 29 | let depth = (1.2 * (finites.len() as f64).log(8.0)).round() as i32; 30 | 31 | println!("Octree maximum depth {}", depth); 32 | let mut root_node = OctreeNode::new(bounds, depth); 33 | for (i, prim) in finites.iter().enumerate() { 34 | root_node.insert(i, prim.partial_bounding_box().unwrap()); 35 | } 36 | 37 | Octree { 38 | prims: finites, 39 | infinites: infinites, 40 | root: root_node, 41 | } 42 | } 43 | } 44 | 45 | impl Octree where T: PartialBoundingBox { 46 | pub fn intersect_iter<'a>(&'a self, ray: &'a Ray) -> OctreeIterator<'a, T> { 47 | OctreeIterator::new(self, ray) 48 | } 49 | } 50 | 51 | pub struct OctreeNode { 52 | bbox: BBox, 53 | depth: i32, 54 | children: Vec, 55 | leaf_data: Vec, 56 | } 57 | 58 | #[derive(Clone, Copy)] 59 | struct OctreeData { 60 | pub bbox: BBox, 61 | pub index: usize 62 | } 63 | 64 | impl OctreeNode { 65 | #[allow(dead_code)] 66 | pub fn new(bbox: BBox, depth: i32) -> OctreeNode { 67 | OctreeNode { 68 | bbox: bbox, 69 | depth: depth, 70 | children: Vec::new(), 71 | leaf_data: Vec::new(), 72 | } 73 | } 74 | 75 | fn subdivide(&mut self) { 76 | for x in 0u32..2 { 77 | for y in 0u32..2 { 78 | for z in 0u32..2 { 79 | let len = self.bbox.len(); 80 | 81 | let child_bbox = BBox { 82 | min: Vec3 { 83 | x: self.bbox.min.x + x as f64 * len.x / 2.0, 84 | y: self.bbox.min.y + y as f64 * len.y / 2.0, 85 | z: self.bbox.min.z + z as f64 * len.z / 2.0 86 | }, 87 | max: Vec3 { 88 | x: self.bbox.max.x - (1 - x) as f64 * len.x / 2.0, 89 | y: self.bbox.max.y - (1 - y) as f64 * len.y / 2.0, 90 | z: self.bbox.max.z - (1 - z) as f64 * len.z / 2.0, 91 | } 92 | }; 93 | 94 | self.children.push(OctreeNode::new(child_bbox, self.depth - 1)); 95 | } 96 | } 97 | } 98 | } 99 | 100 | #[allow(dead_code)] 101 | pub fn insert(&mut self, index: usize, object_bbox: BBox) -> () { 102 | // Max depth 103 | if self.depth <= 0 { 104 | self.leaf_data.push(OctreeData { index: index, bbox: object_bbox }); 105 | return; 106 | } 107 | 108 | // Empty leaf node 109 | if self.is_leaf() && self.leaf_data.len() == 0 { 110 | self.leaf_data.push(OctreeData { index: index, bbox: object_bbox }); 111 | return; 112 | } 113 | 114 | // Occupied leaf node and not max depth: subdivide node 115 | if self.is_leaf() && self.leaf_data.len() == 1 { 116 | self.subdivide(); 117 | let old = self.leaf_data.remove(0); 118 | // Reinsert old node and then fall through to insert current object 119 | self.insert(old.index, old.bbox); 120 | } 121 | 122 | // Interior node (has children) 123 | for child in self.children.iter_mut() { 124 | if child.bbox.overlaps(&object_bbox) { 125 | child.insert(index, object_bbox); 126 | } 127 | } 128 | } 129 | 130 | fn is_leaf(&self) -> bool { 131 | self.children.len() == 0 132 | } 133 | } 134 | 135 | pub struct OctreeIterator<'a, T:'a> { 136 | prims: &'a [T], 137 | stack: Vec<&'a OctreeNode>, 138 | leaf_iter: Option>, 139 | ray: &'a Ray, 140 | infinites: Iter<'a, T>, 141 | just_infinites: bool 142 | } 143 | 144 | 145 | impl<'a, T> OctreeIterator<'a, T> where T: PartialBoundingBox { 146 | fn new<'b>(octree: &'b Octree, ray: &'b Ray) -> OctreeIterator<'b, T> { 147 | OctreeIterator { 148 | prims: &octree.prims[..], 149 | stack: vec![&octree.root], 150 | leaf_iter: None, 151 | ray: ray, 152 | infinites: octree.infinites.iter(), 153 | just_infinites: false 154 | } 155 | } 156 | } 157 | 158 | 159 | impl<'a, T> Iterator for OctreeIterator<'a, T> where T: PartialBoundingBox { 160 | type Item = &'a T; 161 | 162 | fn next(&mut self) -> Option<&'a T> { 163 | if self.just_infinites { 164 | return self.infinites.next(); 165 | } 166 | 167 | loop { 168 | let ray = self.ray; 169 | if let Some(leaf_iter) = self.leaf_iter.as_mut() { 170 | if let Some(val) = leaf_iter.filter(|x| x.bbox.intersects(ray)).next() { 171 | return Some(&self.prims[val.index]); 172 | } 173 | // iterator went empty, so we'll pop from the stack and 174 | // iterate on the next node's children now, 175 | } 176 | 177 | if let Some(node) = self.stack.pop() { 178 | for child in node.children.iter() { 179 | if child.bbox.intersects(self.ray) { 180 | self.stack.push(child); 181 | } 182 | } 183 | self.leaf_iter = Some(node.leaf_data.iter()); 184 | } else { 185 | self.just_infinites = true; 186 | return self.infinites.next() 187 | } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/raytracer/ray.rs: -------------------------------------------------------------------------------- 1 | use std::f64::INFINITY; 2 | use raytracer::Intersection; 3 | use scene::Scene; 4 | use vec3::Vec3; 5 | 6 | #[cfg(test)] 7 | use geometry::prim::Prim; 8 | #[cfg(test)] 9 | use geometry::prims::Sphere; 10 | #[cfg(test)] 11 | use light::light::Light; 12 | #[cfg(test)] 13 | use material::materials::FlatMaterial; 14 | 15 | pub struct Ray { 16 | pub origin: Vec3, 17 | pub direction: Vec3, 18 | pub inverse_dir: Vec3, // This is used to optimise ray-bbox intersection checks 19 | pub signs: [bool; 3], // Handle degenerate case in bbox intersection 20 | } 21 | 22 | impl Ray { 23 | pub fn new(origin: Vec3, direction: Vec3) -> Ray { 24 | let inv_x = 1.0 / direction.x; 25 | let inv_y = 1.0 / direction.y; 26 | let inv_z = 1.0 / direction.z; 27 | 28 | Ray { 29 | origin: origin, 30 | direction: direction, 31 | inverse_dir: Vec3 { 32 | x: inv_x, 33 | y: inv_y, 34 | z: inv_z 35 | }, 36 | signs: [ 37 | inv_x > 0.0, 38 | inv_y > 0.0, 39 | inv_z > 0.0 40 | ] 41 | } 42 | } 43 | 44 | pub fn get_nearest_hit<'a>(&'a self, scene: &'a Scene) -> Option> { 45 | let t_min = 0.000001; 46 | let mut nearest_hit = None; 47 | let mut nearest_t = INFINITY; 48 | 49 | for prim in scene.octree.intersect_iter(self) { 50 | let intersection = prim.intersects(self, t_min, nearest_t); 51 | 52 | nearest_hit = match intersection { 53 | Some(intersection) => { 54 | if intersection.t > t_min && intersection.t < nearest_t { 55 | nearest_t = intersection.t; 56 | Some(intersection) 57 | } else { 58 | nearest_hit 59 | } 60 | }, 61 | None => nearest_hit 62 | }; 63 | } 64 | 65 | nearest_hit 66 | } 67 | 68 | pub fn perturb(&self, magnitude: f64) -> Ray { 69 | let rand_vec = Vec3::random() * magnitude; 70 | 71 | // Force random vectors to be in same direction as original vector 72 | let corrected_rand_vec = if rand_vec.dot(&self.direction) < 0.0 { 73 | rand_vec * -1.0 74 | } else { 75 | rand_vec 76 | }; 77 | 78 | let direction = (corrected_rand_vec + self.direction).unit(); 79 | 80 | Ray::new(self.origin, direction) 81 | } 82 | } 83 | 84 | #[test] 85 | fn it_gets_the_nearest_hit() { 86 | let lights: Vec> = Vec::new(); 87 | 88 | let mut prims: Vec> = Vec::new(); 89 | let mat = FlatMaterial { color: Vec3::one() }; 90 | let sphere_top = Sphere { 91 | center: Vec3::zero(), 92 | radius: 1.0, 93 | material: Box::new(mat.clone()), 94 | }; 95 | let sphere_mid = Sphere { 96 | center: Vec3 { x: -1.0, y: 0.0, z: 0.0 }, 97 | radius: 1.0, 98 | material: Box::new(mat.clone()), 99 | }; 100 | let sphere_bot = Sphere { 101 | center: Vec3 { x: -2.0, y: 0.0, z: 0.0 }, 102 | radius: 1.0, 103 | material: Box::new(mat.clone()), 104 | }; 105 | prims.push(Box::new(sphere_top)); 106 | prims.push(Box::new(sphere_mid)); 107 | prims.push(Box::new(sphere_bot)); 108 | 109 | println!("Generating octree..."); 110 | let octree = prims.into_iter().collect(); 111 | println!("Octree generated..."); 112 | 113 | let scene = Scene { 114 | lights: lights, 115 | background: Vec3::one(), 116 | octree: octree, 117 | skybox: None 118 | }; 119 | 120 | let intersecting_ray = Ray::new( 121 | Vec3 { x: 10.0, y: 0.0, z: 0.0 }, 122 | Vec3 { x: -1.0, y: 0.0, z: 0.0 } 123 | ); 124 | 125 | let intersection = intersecting_ray.get_nearest_hit(&scene); 126 | assert_eq!(1.0, intersection.unwrap().position.x); 127 | 128 | let non_intersecting_ray = Ray::new( 129 | Vec3 { x: 10.0, y: 0.0, z: 0.0 }, 130 | Vec3 { x: 1.0, y: 0.0, z: 0.0 }); 131 | 132 | let non_intersection = non_intersecting_ray.get_nearest_hit(&scene); 133 | assert!(non_intersection.is_none()); 134 | } 135 | -------------------------------------------------------------------------------- /src/raytracer/renderer.rs: -------------------------------------------------------------------------------- 1 | use light::Light; 2 | use raytracer::compositor::{ColorRGBA, Surface, SurfaceFactory}; 3 | use raytracer::{Intersection, Ray}; 4 | use scene::{Camera, Scene}; 5 | use std::ops::Deref; 6 | use std::sync::Arc; 7 | use std::sync::mpsc::channel; 8 | use vec3::Vec3; 9 | use rand::{thread_rng, Rng}; 10 | use threadpool::ThreadPool; 11 | 12 | pub static EPSILON: f64 = ::std::f64::EPSILON * 10000.0; 13 | 14 | #[derive(Clone, Copy)] 15 | pub struct RenderOptions { 16 | pub reflect_depth: u32, // Maximum reflection recursions. 17 | pub refract_depth: u32, // Maximum refraction recursions. A sphere takes up 2 recursions. 18 | pub shadow_samples: u32, // Number of samples for soft shadows and area lights. 19 | pub gloss_samples: u32, // Number of samples for glossy reflections. 20 | pub pixel_samples: u32, // The square of this is the number of samples per pixel. 21 | } 22 | 23 | #[derive(Clone)] 24 | pub struct Renderer { 25 | pub tasks: usize, // Minimum number of tasks to spawn. 26 | pub options: RenderOptions, 27 | } 28 | 29 | impl Renderer { 30 | pub fn render(&self, camera: Camera, shared_scene: Arc) -> Surface { 31 | 32 | let mut surface = Surface::new(camera.image_width as usize, 33 | camera.image_height as usize, 34 | ColorRGBA::new_rgb(0, 0, 0)); 35 | 36 | let pool = ThreadPool::new(self.tasks); 37 | 38 | let (tx, rx) = channel(); 39 | 40 | let mut jobs = 0; 41 | 42 | for subsurface_factory in surface.divide(128, 8) { 43 | jobs += 1; 44 | 45 | let renderer_opts = self.options.clone(); 46 | let child_tx = tx.clone(); 47 | let scene_local = shared_scene.clone(); 48 | let camera_local = camera.clone(); 49 | 50 | pool.execute(move || { 51 | let scene = scene_local.deref(); 52 | let _ = child_tx.send(Renderer::render_tile(camera_local, scene, 53 | renderer_opts, subsurface_factory)).unwrap(); 54 | }); 55 | } 56 | drop(tx); 57 | 58 | let start_time = ::time::get_time(); 59 | 60 | for (i, subsurface) in rx.iter().enumerate() { 61 | surface.merge(&subsurface); 62 | ::util::print_progress("Tile", start_time.clone(), (i + 1) as usize, jobs); 63 | } 64 | surface 65 | } 66 | 67 | fn render_tile(camera: Camera, scene: &Scene, options: RenderOptions, tile_factory: SurfaceFactory) -> Surface { 68 | let mut tile = tile_factory.create(); 69 | let mut rng = thread_rng(); 70 | let pixel_samples = options.pixel_samples; 71 | 72 | for rel_y in 0usize..tile.height { 73 | let abs_y = camera.image_height as usize - (tile.y_off + rel_y) - 1; 74 | for rel_x in 0usize..tile.width { 75 | let abs_x = tile.x_off + rel_x; 76 | 77 | // Supersampling, jitter algorithm 78 | let pixel_width = 1.0 / pixel_samples as f64; 79 | let mut color = Vec3::zero(); 80 | 81 | for y_subpixel in 0u32..pixel_samples { 82 | for x_subpixel in 0u32..pixel_samples { 83 | // Don't jitter if not antialiasing 84 | let (j_x, j_y) = if pixel_samples > 1 { 85 | (x_subpixel as f64 * pixel_width + rng.gen::() * pixel_width, 86 | y_subpixel as f64 * pixel_width + rng.gen::() * pixel_width) 87 | } else { 88 | (0.0, 0.0) 89 | }; 90 | 91 | let ray = camera.get_ray(abs_x as f64 + j_x, abs_y as f64 + j_y); 92 | let result = Renderer::trace(scene, &ray, options, false); 93 | // Clamp subpixels for now to avoid intense aliasing when combined value is clamped later 94 | // Should think of a better way to handle this 95 | color = color + result.clamp(0.0, 1.0).scale(1.0 / (pixel_samples * pixel_samples) as f64); 96 | } 97 | } 98 | tile[(rel_x, rel_y)] = ColorRGBA::new_rgb_clamped(color.x, color.y, color.z); 99 | } 100 | } 101 | 102 | tile 103 | } 104 | 105 | fn trace(scene: &Scene, ray: &Ray, options: RenderOptions, inside: bool) -> Vec3 { 106 | if options.reflect_depth <= 0 || options.refract_depth <= 0 { return Vec3::zero() } 107 | 108 | match ray.get_nearest_hit(scene) { 109 | Some(hit) => { 110 | let n = hit.n.unit(); 111 | let i = (-ray.direction).unit(); 112 | 113 | // Local lighting computation: surface shading, shadows 114 | let mut result = scene.lights.iter().fold(Vec3::zero(), |color_acc, light| { 115 | let shadow = Renderer::shadow_intensity(scene, &hit, light, options.shadow_samples); 116 | let l = (light.center() - hit.position).unit(); 117 | 118 | color_acc + light.color() * hit.material.sample(n, i, l, hit.u, hit.v) * shadow 119 | }); 120 | 121 | // Global lighting computation: reflections, refractions 122 | if hit.material.is_reflective() || hit.material.is_refractive() { 123 | let reflect_fresnel = Renderer::fresnel_reflect(hit.material.ior(), &i, &n, inside); 124 | let refract_fresnel = 1.0 - reflect_fresnel; 125 | 126 | if hit.material.is_reflective() { 127 | result = result + Renderer::global_reflection(scene, &hit, options, inside, 128 | &i, &n, reflect_fresnel); 129 | } 130 | 131 | if hit.material.is_refractive() { 132 | result = result + Renderer::global_transmission(scene, &hit, options, inside, 133 | &i, &n, refract_fresnel); 134 | } 135 | } 136 | 137 | result 138 | }, 139 | None => { 140 | match scene.skybox { 141 | Some(ref skybox) => skybox.color(ray.direction), 142 | None => scene.background 143 | } 144 | } 145 | } 146 | } 147 | 148 | fn global_reflection(scene: &Scene, hit: &Intersection, options: RenderOptions, inside: bool, 149 | i: &Vec3, n: &Vec3, reflect_fresnel: f64) -> Vec3 { 150 | 151 | let r = Vec3::reflect(&i, &n); 152 | let reflect_ray = Ray::new(hit.position, r); 153 | let next_reflect_options = RenderOptions { reflect_depth: options.reflect_depth - 1, ..options }; 154 | 155 | let reflection = if hit.material.is_glossy() { 156 | // For glossy materials, average multiple perturbed reflection rays 157 | // Potential overflow by scaling after everything is done instead of scaling every iteration? 158 | (0..options.gloss_samples).fold(Vec3::zero(), |acc, _| { 159 | let gloss_reflect_ray = reflect_ray.perturb(hit.material.glossiness()); 160 | acc + Renderer::trace(scene, &gloss_reflect_ray, next_reflect_options, inside) 161 | }).scale(1.0 / options.gloss_samples as f64) 162 | } else { 163 | // For mirror-like materials just shoot a perfectly reflected ray instead 164 | Renderer::trace(scene, &reflect_ray, next_reflect_options, inside) 165 | }; 166 | 167 | hit.material.global_specular(&reflection).scale(reflect_fresnel) 168 | } 169 | 170 | fn global_transmission(scene: &Scene, hit: &Intersection, options: RenderOptions, inside: bool, 171 | i: &Vec3, n: &Vec3, refract_fresnel: f64) -> Vec3 { 172 | 173 | let (t, actual_refract_fresnel) = match Vec3::refract(&i, &n, hit.material.ior(), inside) { 174 | Some(ref t) => (*t, refract_fresnel), 175 | None => { 176 | (Vec3::reflect(&i, &n), 1.0) // Fresnel of 1.0 = total internal reflection (TODO: verify) 177 | } 178 | }; 179 | 180 | // Offset ray origin by EPSILON * direction to avoid hitting self when refracting 181 | let refract_ray = Ray::new(hit.position + t.scale(EPSILON), t); 182 | let next_refract_options = RenderOptions { refract_depth: options.refract_depth - 1, ..options }; 183 | let refraction = Renderer::trace(scene, &refract_ray, next_refract_options, !inside); 184 | 185 | hit.material.global_transmissive(&refraction).scale(actual_refract_fresnel) 186 | } 187 | 188 | fn shadow_intensity(scene: &Scene, hit: &Intersection, 189 | light: &Box, shadow_samples: u32) -> Vec3 { 190 | 191 | if shadow_samples <= 0 { return Vec3::one() } 192 | 193 | // Point light speedup (no point in sampling a point light multiple times) 194 | let shadow_sample_tries = if light.is_point() { 1 } else { shadow_samples }; 195 | let mut shadow = Vec3::zero(); 196 | 197 | // Take average shadow color after jittering/sampling light position 198 | for _ in 0..shadow_sample_tries { 199 | // L has to be a unit vector for t_max 1:1 correspondence to 200 | // distance to light to work. Shadow feelers only search up 201 | // until light source. 202 | let sampled_light_position = light.position(); 203 | let shadow_l = (sampled_light_position - hit.position).unit(); 204 | let shadow_ray = Ray::new(hit.position, shadow_l); 205 | let distance_to_light = (sampled_light_position - hit.position).len(); 206 | 207 | // Check against candidate primitives in scene for occlusion 208 | // and multiply shadow color by occluders' shadow colors 209 | let candidate_nodes = scene.octree.intersect_iter(&shadow_ray); 210 | 211 | shadow = shadow + candidate_nodes.fold(Vec3::one(), |shadow_acc, prim| { 212 | let occlusion = prim.intersects(&shadow_ray, EPSILON, distance_to_light); 213 | match occlusion { 214 | Some(occlusion) => shadow_acc * occlusion.material.transmission(), 215 | None => shadow_acc 216 | } 217 | }); 218 | } 219 | 220 | shadow.scale(1.0 / shadow_sample_tries as f64) 221 | } 222 | 223 | /// Calculates the fresnel (reflectivity) given the index of refraction and the cos_angle 224 | /// This uses Schlick's approximation. cos_angle is normal_dot_incoming 225 | /// http://graphics.stanford.edu/courses/cs148-10-summer/docs/2006--degreve--reflection_refraction.pdf 226 | fn fresnel_reflect(ior: f64, i: &Vec3, n: &Vec3, inside: bool) -> f64 { 227 | let (n1, n2) = if inside { (ior, 1.0) } else { (1.0, ior) }; 228 | let actual_n = if inside { -*n } else { *n }; 229 | 230 | let r0_sqrt = (n1 - n2) / (n1 + n2); 231 | let r0 = r0_sqrt * r0_sqrt; 232 | 233 | let cos_angle = if n1 <= n2 { 234 | i.dot(&actual_n) 235 | } else { 236 | let t = match Vec3::refract(i, &-actual_n, ior, inside) { 237 | Some(x) => x, 238 | None => return 1.0 // n1 > n2 && TIR 239 | }; 240 | 241 | -actual_n.dot(&t) // n1 > n2 && !TIR 242 | }; 243 | 244 | let cos_term = 1.0 - cos_angle; 245 | 246 | (r0 + ((1.0 - r0) * cos_term * cos_term * cos_term * cos_term * cos_term)).max(0.0).min(1.0) 247 | } 248 | } 249 | 250 | #[test] 251 | fn it_renders_the_background_of_an_empty_scene() { 252 | let camera = Camera::new( 253 | Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 254 | Vec3 { x: 0.0, y: 1.0, z: 0.0 }, 255 | Vec3 { x: 0.0, y: 0.0, z: 1.0 }, 256 | 45.0, 257 | 32, 258 | 32 259 | ); 260 | 261 | let test_scene = Scene { 262 | lights: vec!(), 263 | octree: vec!().into_iter().collect(), 264 | background: Vec3 { x: 1.0, y: 0.0, z: 0.0 }, 265 | skybox: None 266 | }; 267 | 268 | let shared_scene = Arc::new(test_scene); 269 | 270 | let render_options = RenderOptions { 271 | reflect_depth: 1, 272 | refract_depth: 1, 273 | shadow_samples: 1, 274 | gloss_samples: 1, 275 | pixel_samples: 1, 276 | }; 277 | 278 | 279 | let renderer = Renderer { 280 | options: render_options, 281 | tasks: 2, 282 | }; 283 | 284 | let image_data = renderer.render(camera, shared_scene); 285 | 286 | for color in image_data.buffer.iter() { 287 | assert_eq!(color.r, 255); 288 | assert_eq!(color.g, 0); 289 | assert_eq!(color.b, 0); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/scene/camera.rs: -------------------------------------------------------------------------------- 1 | use raytracer::Ray; 2 | use raytracer::animator::CameraKeyframe; 3 | use raytracer::animator::easing::Easing; 4 | use vec3::Vec3; 5 | 6 | #[derive(Clone)] 7 | pub struct Camera { 8 | pub position: Vec3, 9 | pub look_at: Vec3, 10 | pub up: Vec3, 11 | pub fov_deg: f64, 12 | pub image_width: u32, 13 | pub image_height: u32, 14 | 15 | pub eye: Vec3, 16 | pub right: Vec3, 17 | pub half_width: f64, 18 | pub half_height: f64, 19 | pub pixel_width: f64, 20 | pub pixel_height: f64, 21 | 22 | pub keyframes: Option> 23 | } 24 | 25 | impl Camera { 26 | pub fn new(position: Vec3, look_at: Vec3, up: Vec3, fov_deg: f64, 27 | image_width: u32, image_height: u32) 28 | -> Camera { 29 | 30 | let mut camera = Camera { 31 | position: position, 32 | look_at: look_at, 33 | up: up, 34 | fov_deg: fov_deg, 35 | image_width: image_width, 36 | image_height: image_height, 37 | eye: Vec3::zero(), 38 | right: Vec3::zero(), 39 | half_width: 0.0, 40 | half_height: 0.0, 41 | pixel_width: 0.0, 42 | pixel_height: 0.0, 43 | keyframes: None 44 | }; 45 | 46 | camera.update_eye_vector(); 47 | camera.update_internal_sizes(); 48 | 49 | camera 50 | } 51 | 52 | #[allow(dead_code)] 53 | pub fn new_with_keyframes(position: Vec3, look_at: Vec3, up: Vec3, fov_deg: f64, 54 | image_width: u32, image_height: u32, keyframes: Vec) 55 | -> Camera { 56 | 57 | let mut camera = Camera::new(position, look_at, up, fov_deg, image_width, image_height); 58 | camera.insert_keyframes(keyframes); 59 | camera 60 | } 61 | 62 | pub fn get_ray(&self, x: f64, y: f64) -> Ray { 63 | Ray::new( 64 | self.position, 65 | (self.eye + self.right.scale(x * self.pixel_width - self.half_width) + 66 | self.up.scale(y * self.pixel_height - self.half_height)).unit() 67 | ) 68 | } 69 | 70 | /// Add additional keyframes to the camera. The current state of the camera 71 | /// is treated as t=0, and a new keyframe at t=0 is created and added. 72 | #[allow(dead_code)] 73 | pub fn insert_keyframes(&mut self, additional_keyframes: Vec) { 74 | let t0_keyframe = CameraKeyframe { 75 | time: 0.0, 76 | position: self.position, 77 | look_at: self.look_at, 78 | up: self.up, 79 | easing: Easing::linear() 80 | }; 81 | 82 | let mut keyframes = vec![t0_keyframe]; 83 | keyframes.extend(additional_keyframes); 84 | 85 | self.keyframes = Some(keyframes); 86 | } 87 | 88 | fn update_eye_vector(&mut self) { 89 | self.eye = (self.look_at - self.position).unit(); 90 | self.right = self.eye.cross(&self.up); 91 | } 92 | 93 | fn update_internal_sizes(&mut self) { 94 | let fov_rad = self.fov_deg.to_radians(); 95 | let ratio = self.image_height as f64 / self.image_width as f64; 96 | 97 | self.half_width = fov_rad.tan(); 98 | self.half_height = self.half_width * ratio; 99 | 100 | let camera_width = self.half_width * 2.0; 101 | let camera_height = self.half_height * 2.0; 102 | 103 | self.pixel_width = camera_width / (self.image_width - 1) as f64; 104 | self.pixel_height = camera_height / (self.image_height - 1) as f64; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/scene/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::camera::Camera; 2 | pub use self::scene::Scene; 3 | 4 | pub mod camera; 5 | pub mod scene; 6 | -------------------------------------------------------------------------------- /src/scene/scene.rs: -------------------------------------------------------------------------------- 1 | use light::Light; 2 | use material::textures::CubeMap; 3 | use geometry::Prim; 4 | use raytracer::Octree; 5 | use vec3::Vec3; 6 | 7 | pub struct Scene { 8 | pub lights: Vec>, 9 | pub octree: Octree>, 10 | pub background: Vec3, 11 | pub skybox: Option 12 | } 13 | -------------------------------------------------------------------------------- /src/util/export.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{self, Write}; 3 | use raytracer::compositor::{Surface, Channel}; 4 | 5 | pub fn to_ppm(surface: &Surface, filename: &str) -> io::Result<()> { 6 | let channel_max: u8 = Channel::max_value(); 7 | let header = format!( 8 | "P3 {} {} {}\n", surface.width, surface.height, 9 | channel_max); 10 | 11 | let mut f = File::create(filename)?; 12 | 13 | f.write_all(header.as_bytes())?; 14 | for pixel in &surface.buffer { 15 | f.write_all(format!("{} {} {} ", pixel.r, pixel.g, pixel.b).as_bytes())?; 16 | } 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /src/util/import.rs: -------------------------------------------------------------------------------- 1 | use geometry::prims::TriangleOptions; 2 | use geometry::{Mesh, Prim}; 3 | use material::materials::CookTorranceMaterial; 4 | use raytracer::compositor::{Surface, ColorRGBA}; 5 | use std::fs::File; 6 | use std::path::Path; 7 | use std::io::{BufRead, BufReader}; 8 | use vec3::Vec3; 9 | 10 | /// This is limited to only CookTorranceMaterials, as I couldn't get a Box to clone 11 | /// a new material for each triangle primitive in the object model. 12 | pub fn from_obj(material: CookTorranceMaterial, flip_normals: bool, filename: &str) -> Result { 13 | let file_handle = match File::open(&filename) { 14 | Ok(f) => f, 15 | Err(err) => return Err(format!("{}", err)) 16 | }; 17 | 18 | let total_bytes = match file_handle.metadata() { 19 | Ok(metadata) => metadata.len(), 20 | Err(err) => return Err(format!("{}", err)) 21 | }; 22 | 23 | let file = BufReader::new(file_handle); 24 | 25 | let start_time = ::time::get_time(); 26 | let print_every = 2048; 27 | let mut processed_bytes = 0; 28 | 29 | let mut vertices: Vec = Vec::new(); 30 | let mut normals : Vec = Vec::new(); 31 | let mut triangles: Vec> = Vec::new(); 32 | let mut tex_coords: Vec> = Vec::new(); 33 | let mut current_line = 0; 34 | let normal_scale = if flip_normals { -1.0 } else { 1.0 }; 35 | 36 | for line_res in file.lines() { 37 | let line = line_res.unwrap(); 38 | let tokens: Vec<&str> = line[..].split_whitespace().collect(); 39 | if tokens.is_empty() { 40 | continue; 41 | } 42 | 43 | match tokens[0] { 44 | "v" => { 45 | vertices.push(Vec3 { 46 | x: tokens[1].parse().unwrap(), 47 | y: tokens[2].parse().unwrap(), 48 | z: tokens[3].parse().unwrap() 49 | }); 50 | }, 51 | "vt" => { 52 | tex_coords.push(vec![ 53 | tokens[1].parse().unwrap(), 54 | tokens[2].parse().unwrap() 55 | ]); 56 | }, 57 | "vn" => { 58 | normals.push(Vec3 { 59 | x: tokens[1].parse::().unwrap() * normal_scale, 60 | y: tokens[2].parse::().unwrap() * normal_scale, 61 | z: tokens[3].parse::().unwrap() * normal_scale 62 | }); 63 | }, 64 | "f" => { 65 | // ["f", "1/2/3", "2/2/2", "12//4"] => [[1, 2, 3], [2, 2, 2], [12, -1u, 4]] 66 | let tail = match tokens.split_first() { 67 | Some((_, tail)) => tail, 68 | None => return Err("Face syntax of OBJ not supported or malformed".to_owned()) 69 | }; 70 | 71 | let pairs: Vec> = tail.iter().map( |token| { 72 | let str_tokens: Vec<&str> = token.split('/').collect(); 73 | str_tokens.iter().map( |str_tok| { 74 | match str_tok.parse::().ok() { 75 | Some(usize_tok) => usize_tok - 1, // Have to offset as OBJ is 1-indexed 76 | None => !0 // No data available/not supplied (eg. `//` as a token) 77 | } 78 | }).collect() 79 | }).collect(); 80 | 81 | // If no texture coordinates were supplied, default to zero. 82 | // We stored nothing supplied as !0 83 | let (u, v) = if pairs[0][1] != !0 { 84 | (vec![ 85 | tex_coords[pairs[0][1]][0], 86 | tex_coords[pairs[1][1]][0], 87 | tex_coords[pairs[2][1]][0] 88 | ], 89 | vec![ 90 | tex_coords[pairs[0][1]][1], 91 | tex_coords[pairs[1][1]][1], 92 | tex_coords[pairs[2][1]][1] 93 | ]) 94 | } else { 95 | (vec![0.0, 0.0, 0.0], 96 | vec![0.0, 0.0, 0.0]) 97 | }; 98 | 99 | let mut triopts = TriangleOptions::new( 100 | vertices[pairs[0][0]], 101 | vertices[pairs[1][0]], 102 | vertices[pairs[2][0]]); 103 | 104 | triopts.material(Box::new(material.clone())); 105 | triopts.normals([ 106 | normals[pairs[0][2]], 107 | normals[pairs[1][2]], 108 | normals[pairs[2][2]], 109 | ]); 110 | triopts.texinfo([(u[0], v[0]), (u[1], v[1]), (u[2], v[2])]); 111 | 112 | triangles.push(Box::new(triopts.build())); 113 | }, 114 | _ => {} 115 | } 116 | 117 | current_line += 1; 118 | processed_bytes += line.as_bytes().len(); 119 | if current_line % print_every == 0 { 120 | ::util::print_progress("Bytes", start_time, processed_bytes, total_bytes as usize); 121 | } 122 | } 123 | 124 | // Cheat the progress meter 125 | ::util::print_progress("Bytes", start_time, total_bytes as usize, total_bytes as usize); 126 | 127 | Ok(Mesh { triangles: triangles }) 128 | } 129 | 130 | pub fn from_image>(path: P) -> Result { 131 | let image = match ::image::open(path) { 132 | Ok(image) => image.to_rgba(), 133 | Err(err) => return Err(format!("{}", err)) 134 | }; 135 | 136 | let mut surface = Surface::new(image.width() as usize, 137 | image.height() as usize, 138 | ColorRGBA::transparent()); 139 | 140 | for (src, dst_pixel) in image.pixels().zip(surface.iter_pixels_mut()) { 141 | *dst_pixel = ColorRGBA::new_rgba(src[0], src[1], src[2], src[3]); 142 | } 143 | 144 | Ok(surface) 145 | } 146 | 147 | #[test] 148 | pub fn test_obj_loads_correct_number_of_triangles() { 149 | let material: CookTorranceMaterial = Default::default(); 150 | let mesh = from_obj(material, false, "test/res/cube.obj") 151 | .ok().expect("failed to laod test obj `test/res/cube.obj`"); 152 | 153 | assert_eq!(mesh.triangles.len(), 12); 154 | } 155 | 156 | #[test] 157 | pub fn test_from_png24() { 158 | let surface = from_image("test/res/png24.png") 159 | .ok().expect("failed to load test image `test/res/png24.png`"); 160 | 161 | let expected_image: [[(u8, u8, u8, u8); 10]; 2] = [[ 162 | (0, 0, 0, 255), (1, 1, 1, 255), (2, 2, 2, 255), 163 | (3, 3, 3, 255), (4, 4, 4, 255), (5, 5, 5, 255), 164 | (6, 6, 6, 255), (7, 7, 7, 255), (8, 8, 8, 255), 165 | (9, 9, 9, 255) 166 | ], [ 167 | (255, 0, 0, 255), (255, 0, 0, 127), (255, 0, 0, 0), 168 | (0, 255, 0, 255), (0, 255, 0, 127), (0, 255, 0, 0), 169 | (0, 0, 255, 255), (0, 0, 255, 127), (0, 0, 255, 0), 170 | (0, 0, 0, 0) 171 | ]]; 172 | 173 | for y in 0..1 { 174 | for x in 0..9 { 175 | let pixel = surface[(x, y)]; 176 | let expected = expected_image[y][x]; 177 | assert_eq!(expected, (pixel.r, pixel.g, pixel.b, pixel.a)); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | use std::iter::repeat; 2 | use std::io::Write; 3 | 4 | pub mod export; 5 | pub mod import; 6 | 7 | pub fn print_progress(noun: &str, start_time: ::time::Timespec, done: usize, total: usize) { 8 | let remaining_jobs = total - done; 9 | let progress: f64 = 100f64 * done as f64 / total as f64; 10 | let current_time = ::time::get_time().sec; 11 | let time_per_job = (current_time - start_time.sec) as f64 / done as f64; 12 | let remaining_time = time_per_job * remaining_jobs as f64; 13 | 14 | print!("\r{} {}/{} complete\t{:.2}% [{}]", 15 | noun, done, total, progress, 16 | ::util::make_progress_bar(progress / 100.0, 20) 17 | ); 18 | 19 | if remaining_jobs == 0 { 20 | println!(" (took {:.2} min) ", (current_time - start_time.sec) as f64 / 60.0); 21 | } else { 22 | print!(" ETA {:.2} min ", remaining_time / 60.0); 23 | ::std::io::stdout().flush().expect("failed to flush io"); 24 | } 25 | } 26 | 27 | fn make_progress_bar(ratio: f64, length: usize) -> String { 28 | let filled = (ratio * length as f64).round() as usize; 29 | let mut pbar: String = repeat('|').take(filled).collect(); 30 | 31 | for _ in 0..(length - filled) { 32 | pbar.push('-'); 33 | } 34 | 35 | pbar 36 | } 37 | -------------------------------------------------------------------------------- /src/vec3.rs: -------------------------------------------------------------------------------- 1 | use rand::{thread_rng, Rng}; 2 | use std::cmp; 3 | use std::fmt; 4 | use std::ops::{Add, Div, Mul, Neg, Sub}; 5 | 6 | #[derive(Clone, Copy, Default)] 7 | pub struct Vec3 { 8 | pub x: f64, 9 | pub y: f64, 10 | pub z: f64, 11 | } 12 | 13 | impl Vec3 { 14 | pub fn zero() -> Vec3 { 15 | Vec3 { 16 | x: 0.0, 17 | y: 0.0, 18 | z: 0.0, 19 | } 20 | } 21 | 22 | pub fn one() -> Vec3 { 23 | Vec3 { 24 | x: 1.0, 25 | y: 1.0, 26 | z: 1.0, 27 | } 28 | } 29 | 30 | pub fn len(&self) -> f64 { 31 | (self.x * self.x + self.y * self.y + self.z * self.z).sqrt() 32 | } 33 | 34 | pub fn dot(&self, other: &Vec3) -> f64 { 35 | self.x * other.x + self.y * other.y + self.z * other.z 36 | } 37 | 38 | pub fn cross(&self, other: &Vec3) -> Vec3 { 39 | Vec3 { 40 | x: self.y * other.z - self.z * other.y, 41 | y: self.z * other.x - self.x * other.z, 42 | z: self.x * other.y - self.y * other.x, 43 | } 44 | } 45 | 46 | pub fn unit(&self) -> Vec3 { 47 | let len = self.len(); 48 | 49 | Vec3 { 50 | x: self.x / len, 51 | y: self.y / len, 52 | z: self.z / len, 53 | } 54 | } 55 | 56 | pub fn scale(&self, scalar: f64) -> Vec3 { 57 | Vec3 { 58 | x: self.x * scalar, 59 | y: self.y * scalar, 60 | z: self.z * scalar, 61 | } 62 | } 63 | 64 | /// V, N should be unit vectors 65 | /// 66 | /// ^ ^ 67 | /// V \ | N 68 | /// \| 69 | /// ========= 70 | pub fn reflect(v: &Vec3, n: &Vec3) -> Vec3 { 71 | n.scale(2.0 * (n.dot(v))) - *v 72 | } 73 | 74 | /// V, N should be unit vectors 75 | /// ior: Refractive index 76 | /// inside: Is the ray inside an object (ie. going out of an object)? 77 | pub fn refract(v: &Vec3, n: &Vec3, ior: f64, inside: bool) -> Option { 78 | let (n1, n2, n_dot_v, nn): (f64, f64, _, _) = if !inside { 79 | (1.0, ior, n.dot(v), *n) 80 | } else { 81 | (ior, 1.0, -n.dot(v), -*n) 82 | }; 83 | 84 | let ratio = n1 / n2; 85 | let disc = 1.0 - ((ratio * ratio) * (1.0 - n_dot_v * n_dot_v)); 86 | 87 | if disc < 0.0 { 88 | None // Total internal reflection 89 | } else { 90 | Some(v.scale(-ratio) + nn.scale(ratio * n_dot_v - disc.sqrt())) 91 | } 92 | } 93 | 94 | pub fn lerp(v1: &Vec3, v2: &Vec3, alpha: f64) -> Vec3 { 95 | Vec3 { 96 | x: v1.x + (v2.x - v1.x) * alpha, 97 | y: v1.y + (v2.y - v1.y) * alpha, 98 | z: v1.z + (v2.z - v1.z) * alpha, 99 | } 100 | } 101 | 102 | pub fn clamp(&self, min: f64, max: f64) -> Vec3 { 103 | Vec3 { 104 | x: self.x.max(min).min(max), 105 | y: self.y.max(min).min(max), 106 | z: self.z.max(min).min(max), 107 | } 108 | } 109 | 110 | /// Generates a random vector across a uniform distribution using the answer found in 111 | /// http://stackoverflow.com/questions/5408276/python-uniform-spherical-distribution 112 | pub fn random() -> Vec3 { 113 | let mut rng = thread_rng(); 114 | let phi: f64 = rng.gen_range(0.0, 2.0 * ::std::f64::consts::PI); 115 | let costheta: f64 = rng.gen_range(-1.0, 1.0); 116 | let u: f64 = rng.gen_range(0.0, 1.0); 117 | 118 | let theta = costheta.acos(); 119 | let r = u.powf(1.0 / 3.0); 120 | 121 | Vec3 { 122 | x: r * theta.sin() * phi.cos(), 123 | y: r * theta.sin() * phi.sin(), 124 | z: r * theta.cos(), 125 | } 126 | } 127 | } 128 | 129 | impl Add for Vec3 { 130 | type Output = Vec3; 131 | 132 | fn add(self, other: Vec3) -> Vec3 { 133 | Vec3 { 134 | x: self.x + other.x, 135 | y: self.y + other.y, 136 | z: self.z + other.z, 137 | } 138 | } 139 | } 140 | 141 | impl Add for Vec3 { 142 | type Output = Vec3; 143 | 144 | fn add(self, other: f64) -> Vec3 { 145 | Vec3 { 146 | x: self.x + other, 147 | y: self.y + other, 148 | z: self.z + other, 149 | } 150 | } 151 | } 152 | 153 | impl Sub for Vec3 { 154 | type Output = Vec3; 155 | 156 | fn sub(self, other: Vec3) -> Vec3 { 157 | Vec3 { 158 | x: self.x - other.x, 159 | y: self.y - other.y, 160 | z: self.z - other.z, 161 | } 162 | } 163 | } 164 | 165 | impl Sub for Vec3 { 166 | type Output = Vec3; 167 | 168 | fn sub(self, other: f64) -> Vec3 { 169 | Vec3 { 170 | x: self.x - other, 171 | y: self.y - other, 172 | z: self.z - other, 173 | } 174 | } 175 | } 176 | 177 | impl Mul for Vec3 { 178 | type Output = Vec3; 179 | 180 | fn mul(self, other: Vec3) -> Vec3 { 181 | Vec3 { 182 | x: self.x * other.x, 183 | y: self.y * other.y, 184 | z: self.z * other.z, 185 | } 186 | } 187 | } 188 | 189 | impl Mul for Vec3 { 190 | type Output = Vec3; 191 | 192 | fn mul(self, other: f64) -> Vec3 { 193 | Vec3 { 194 | x: self.x * other, 195 | y: self.y * other, 196 | z: self.z * other, 197 | } 198 | } 199 | } 200 | 201 | impl Div for Vec3 { 202 | type Output = Vec3; 203 | 204 | fn div(self, other: Vec3) -> Vec3 { 205 | Vec3 { 206 | x: self.x / other.x, 207 | y: self.y / other.y, 208 | z: self.z / other.z, 209 | } 210 | } 211 | } 212 | 213 | impl Div for Vec3 { 214 | type Output = Vec3; 215 | 216 | fn div(self, other: f64) -> Vec3 { 217 | Vec3 { 218 | x: self.x / other, 219 | y: self.y / other, 220 | z: self.z / other, 221 | } 222 | } 223 | } 224 | 225 | impl Neg for Vec3 { 226 | type Output = Vec3; 227 | 228 | fn neg(self) -> Vec3 { 229 | Vec3 { 230 | x: -self.x, 231 | y: -self.y, 232 | z: -self.z, 233 | } 234 | } 235 | } 236 | 237 | impl cmp::PartialEq for Vec3 { 238 | fn eq(&self, other: &Vec3) -> bool { 239 | self.x == other.x && self.y == other.y && self.z == other.z 240 | } 241 | } 242 | 243 | impl fmt::Debug for Vec3 { 244 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 245 | write!(f, "({}, {}, {})", self.x, self.y, self.z) 246 | } 247 | } 248 | 249 | #[test] 250 | fn it_implements_debug() { 251 | let vec = Vec3 { 252 | x: 0.0, 253 | y: 1.0, 254 | z: 1.3, 255 | }; 256 | let formatted_string = format!("{:?}", vec); 257 | let expected_string = "(0, 1, 1.3)"; 258 | assert_eq!(&formatted_string, expected_string); 259 | } 260 | 261 | #[test] 262 | fn it_does_cross_product() { 263 | assert_eq!( 264 | Vec3 { 265 | x: -1.0, 266 | y: 2.0, 267 | z: -1.0 268 | }, 269 | Vec3 { 270 | x: 1.0, 271 | y: 2.0, 272 | z: 3.0 273 | }.cross(&Vec3 { 274 | x: 2.0, 275 | y: 3.0, 276 | z: 4.0 277 | }) 278 | ); 279 | } 280 | 281 | #[test] 282 | fn it_does_dot_product() { 283 | assert_eq!( 284 | 5.0, 285 | Vec3 { 286 | x: 0.0, 287 | y: 1.0, 288 | z: 2.0 289 | }.dot(&Vec3 { 290 | x: 0.0, 291 | y: 1.0, 292 | z: 2.0 293 | }) 294 | ); 295 | } 296 | 297 | #[test] 298 | fn it_computes_length_of_a_vec3() { 299 | assert_eq!( 300 | Vec3 { 301 | x: -1.0, 302 | y: -1.0, 303 | z: -1.0 304 | }, 305 | -Vec3::one() 306 | ); 307 | assert_eq!( 308 | 29.0_f64.sqrt(), 309 | Vec3 { 310 | x: 2.0, 311 | y: 3.0, 312 | z: 4.0 313 | }.len() 314 | ); 315 | assert_eq!( 316 | 1.0, 317 | Vec3 { 318 | x: 10.0, 319 | y: 0.0, 320 | z: 0.0 321 | }.unit() 322 | .len() 323 | ); 324 | } 325 | 326 | #[test] 327 | fn it_has_vec3vec3_equality() { 328 | assert!(Vec3::zero() != Vec3::one()); 329 | assert!(Vec3::zero() == Vec3::zero()); 330 | } 331 | 332 | #[test] 333 | fn it_adds_vec3s_and_scalars() { 334 | assert_eq!( 335 | Vec3 { 336 | x: 2.0, 337 | y: 2.0, 338 | z: 2.0 339 | }, 340 | Vec3::one() + Vec3::one() 341 | ); 342 | assert_eq!( 343 | Vec3 { 344 | x: 2.0, 345 | y: 2.0, 346 | z: 2.0 347 | }, 348 | Vec3::one() + 1.0 349 | ); 350 | } 351 | 352 | #[test] 353 | fn it_subtracts_vec3s_and_scalars() { 354 | assert_eq!(Vec3::zero(), Vec3::one() - Vec3::one()); 355 | assert_eq!(Vec3::zero(), Vec3::one() - 1.0); 356 | } 357 | 358 | #[test] 359 | fn it_multiplies_vec3s_and_scalars_elementwise() { 360 | assert_eq!( 361 | Vec3 { 362 | x: 2.0, 363 | y: 2.0, 364 | z: 2.0 365 | }, 366 | Vec3::one().scale(2.0) 367 | ); 368 | assert_eq!( 369 | Vec3 { 370 | x: 2.0, 371 | y: 2.0, 372 | z: 2.0 373 | }, 374 | Vec3::one() * 2.0 375 | ); 376 | assert_eq!( 377 | Vec3 { 378 | x: 4.0, 379 | y: 9.0, 380 | z: -4.0 381 | }, 382 | Vec3 { 383 | x: 2.0, 384 | y: 3.0, 385 | z: 4.0 386 | } * Vec3 { 387 | x: 2.0, 388 | y: 3.0, 389 | z: -1.0 390 | } 391 | ); 392 | } 393 | 394 | #[test] 395 | fn it_divides_vec3s_and_scalars_elementwise() { 396 | assert_eq!( 397 | Vec3 { 398 | x: 0.5, 399 | y: 0.5, 400 | z: 0.5 401 | }, 402 | Vec3::one() / 2.0 403 | ); 404 | assert_eq!( 405 | Vec3 { 406 | x: 0.5, 407 | y: 0.5, 408 | z: 0.5 409 | }, 410 | Vec3::one() / Vec3 { 411 | x: 2.0, 412 | y: 2.0, 413 | z: 2.0 414 | } 415 | ); 416 | } 417 | 418 | #[test] 419 | fn it_linearly_interpolates() { 420 | assert_eq!(Vec3::zero(), Vec3::lerp(&Vec3::zero(), &Vec3::one(), 0.0)); 421 | assert_eq!( 422 | Vec3 { 423 | x: 0.5, 424 | y: 0.5, 425 | z: 0.5 426 | }, 427 | Vec3::lerp(&Vec3::zero(), &Vec3::one(), 0.5) 428 | ); 429 | assert_eq!(Vec3::one(), Vec3::lerp(&Vec3::zero(), &Vec3::one(), 1.0)); 430 | } 431 | -------------------------------------------------------------------------------- /test/res/cube.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.70 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | mtllib cube.mtl 4 | o Cube 5 | v 1.000000 -1.000000 -1.000000 6 | v 1.000000 -1.000000 1.000000 7 | v -1.000000 -1.000000 1.000000 8 | v -1.000000 -1.000000 -1.000000 9 | v 1.000000 1.000000 -1.000000 10 | v 1.000000 1.000000 1.000001 11 | v -1.000000 1.000000 1.000000 12 | v -1.000000 1.000000 -1.000000 13 | vn 0.000000 -1.000000 0.000000 14 | vn 0.000000 1.000000 -0.000000 15 | vn 1.000000 0.000000 0.000000 16 | vn -0.000000 -0.000000 1.000000 17 | vn -1.000000 -0.000000 -0.000000 18 | vn 0.000000 0.000000 -1.000000 19 | vn 1.000000 -0.000000 0.000000 20 | usemtl Material 21 | s off 22 | f 2//1 3//1 4//1 23 | f 5//2 8//2 7//2 24 | f 5//3 6//3 2//3 25 | f 2//4 6//4 7//4 26 | f 7//5 8//5 4//5 27 | f 1//6 4//6 8//6 28 | f 1//1 2//1 4//1 29 | f 6//2 5//2 7//2 30 | f 1//7 5//7 2//7 31 | f 3//4 2//4 7//4 32 | f 3//5 7//5 4//5 33 | f 5//6 1//6 8//6 34 | -------------------------------------------------------------------------------- /test/res/png24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gyng/rust-raytracer/855f2d053ac936a5fb2962d278d629f6269d6bf4/test/res/png24.png -------------------------------------------------------------------------------- /tools/bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for conf in cow bunny box teapot; do 4 | CONF_FILE="tools/conf/${conf}.json" 5 | test -e "$CONF_FILE" && { 6 | echo "=== $1 $conf ===" 7 | time "$1" "$CONF_FILE" 8 | } 9 | done 10 | 11 | -------------------------------------------------------------------------------- /tools/cbenchdec.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | from collections import OrderedDict 4 | 5 | 6 | render_vers = re.compile(r'=== ./rust-raytracer-(\S+) (\S+) ===') 7 | render_time = re.compile(r'Render done at \d+ \((\d+)s\)\.\.\.') 8 | 9 | 10 | def process(stream_in): 11 | mode = 'lfvers' 12 | (version, jobname) = (None, None) 13 | for line in stream_in: 14 | line = line.strip() 15 | if mode == 'lfvers': 16 | vers_match = render_vers.match(line) 17 | if vers_match: 18 | (version, jobname) = vers_match.groups() 19 | mode = 'lftime' 20 | elif mode == 'lftime': 21 | time_match = render_time.match(line) 22 | if time_match: 23 | (time, ) = time_match.groups() 24 | yield (version, jobname, int(time)) 25 | mode = 'lfvers' 26 | else: 27 | raise Exception() 28 | 29 | 30 | if __name__ == '__main__': 31 | output = OrderedDict() 32 | for (version, jobname, time) in process(sys.stdin): 33 | # print("{}\t{}\t{}".format(version, jobname, time)) 34 | if (version, jobname) not in output: 35 | add_to_list = list() 36 | output[(version, jobname)] = add_to_list 37 | else: 38 | add_to_list = output[(version, jobname)] 39 | add_to_list.append(time) 40 | 41 | for ((version, jobname), value) in output.items(): 42 | print("{version} {jobname}\t{rest}".format( 43 | version=version, 44 | jobname=jobname, 45 | rest=' '.join(map(str, value)))) 46 | -------------------------------------------------------------------------------- /tools/conf/box.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "box", 3 | "size": [512, 512], 4 | "fov": 30.0, 5 | "reflect_depth": 4, 6 | "refract_depth": 6, 7 | "shadow_samples": 64, 8 | "gloss_samples": 4, 9 | "pixel_samples": 2, 10 | "output_file": "test", 11 | "animating": false, 12 | "fps": 25.0, 13 | "time_slice": [0.0, 10.0], 14 | "starting_frame_number": 0 15 | } 16 | -------------------------------------------------------------------------------- /tools/conf/bunny.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bunny", 3 | "size": [512, 512], 4 | "fov": 30.0, 5 | "reflect_depth": 4, 6 | "refract_depth": 6, 7 | "shadow_samples": 64, 8 | "gloss_samples": 4, 9 | "pixel_samples": 2, 10 | "output_file": "test", 11 | "animating": false, 12 | "fps": 25.0, 13 | "time_slice": [0.0, 10.0], 14 | "starting_frame_number": 0 15 | } 16 | -------------------------------------------------------------------------------- /tools/conf/cow.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cow", 3 | "size": [512, 512], 4 | "fov": 30.0, 5 | "reflect_depth": 4, 6 | "refract_depth": 6, 7 | "shadow_samples": 64, 8 | "gloss_samples": 4, 9 | "pixel_samples": 2, 10 | "output_file": "test", 11 | "animating": false, 12 | "fps": 25.0, 13 | "time_slice": [0.0, 10.0], 14 | "starting_frame_number": 0 15 | } 16 | -------------------------------------------------------------------------------- /tools/conf/teapot.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "teapot", 3 | "size": [512, 512], 4 | "fov": 30.0, 5 | "reflect_depth": 4, 6 | "refract_depth": 6, 7 | "shadow_samples": 64, 8 | "gloss_samples": 4, 9 | "pixel_samples": 2, 10 | "output_file": "test", 11 | "animating": false, 12 | "fps": 25.0, 13 | "time_slice": [0.0, 10.0], 14 | "starting_frame_number": 0 15 | } 16 | --------------------------------------------------------------------------------