├── .gitignore ├── Cargo.toml ├── README.md ├── demo.png ├── demo_small.png ├── src ├── actor │ └── mod.rs ├── boundingbox │ └── mod.rs ├── camera │ ├── mod.rs │ └── perspective.rs ├── constants │ └── mod.rs ├── float │ └── mod.rs ├── hit │ └── mod.rs ├── hitable │ ├── mod.rs │ ├── primitive │ │ ├── cube.rs │ │ ├── group.rs │ │ ├── mod.rs │ │ ├── rectangle.rs │ │ └── sphere.rs │ └── transform │ │ ├── mod.rs │ │ └── translation.rs ├── lib.rs ├── material │ ├── dielectric.rs │ ├── lambertian.rs │ ├── metal.rs │ ├── mod.rs │ └── plain.rs ├── ray │ └── mod.rs ├── renderer │ └── mod.rs ├── scene │ └── mod.rs ├── texture │ ├── checker.rs │ ├── mod.rs │ └── uniform.rs ├── tree │ ├── binary.rs │ ├── linear.rs │ ├── mod.rs │ └── oct.rs ├── utils │ └── mod.rs └── vector │ └── mod.rs └── tests └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | *.ppm 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ray-tracer" 3 | version = "0.1.0" 4 | authors = ["Alessandro Genova "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | num-traits = { version = "0.2", default-features = false } 9 | rand = { version = "0.6", features = ["wasm-bindgen"] } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ray Tracer 2 | 3 | A toy ray tracer for educational purposes (i.e. my own education). 4 | 5 | - Written from scratch in Rust 6 | - Most of the theory from Peter Shirley's [Ray Tracing in One Weekend](https://github.com/petershirley/raytracinginoneweekend) and [Ray Tracing the Next Week](https://github.com/petershirley/raytracingthenextweek) 7 | 8 | ## Demo 9 | [![demo-img](./demo_small.png)](https://alesgenova.github.io/ray-tracer-app) 10 | 11 | ## Features 12 | 13 | - Geometries: 14 | - Sphere 15 | - Rectangle 16 | - Cube 17 | - Transformations: 18 | - Translation 19 | - Rotations (TODO) 20 | - Scale (TODO) 21 | - Sheer (TODO) 22 | - Materials: 23 | - Lambertian 24 | - Metal 25 | - Dielectric 26 | - Emitting 27 | - Textures: 28 | - Uniform 29 | - Checker 30 | - Gradient (TODO) 31 | - Image (TODO) 32 | - Perlin (TODO) 33 | - Cameras: 34 | - Perspective 35 | - Orthographic (TODO) 36 | - Ray / Actor hit search 37 | - Linear 38 | - Binary Tree 39 | - Octree 40 | 41 | ## Usage 42 | ```rust 43 | use ray_tracer::vector::Vec3; 44 | use ray_tracer::scene::Scene; 45 | use ray_tracer::hitable::primitive::Sphere; 46 | use ray_tracer::hitable::primitive::Rectangle; 47 | use ray_tracer::hitable::transform::Translation; 48 | use ray_tracer::camera::perspective::PerspectiveCamera; 49 | use ray_tracer::renderer::Renderer; 50 | use ray_tracer::material::Material; 51 | use ray_tracer::material::plain::PlainMaterial; 52 | use ray_tracer::material::lambertian::LambertianMaterial; 53 | use ray_tracer::material::metal::MetalMaterial; 54 | use ray_tracer::actor::Actor; 55 | use ray_tracer::texture::uniform::UniformTexture; 56 | use ray_tracer::constants::Axis; 57 | 58 | let mut scene = Scene::::new(); 59 | scene.set_background(Vec3::from_array([0.2, 0.2, 0.2])); 60 | 61 | // The floor 62 | let hitable = Box::new(Rectangle::new(100.0, Axis::X, 100.0, Axis::Y)); 63 | let texture = Box::new(UniformTexture::new(Vec3::from_array([0.8, 0.8, 0.8]))); 64 | let material = Box::new(LambertianMaterial::new(texture, 0.65)); 65 | let actor = Actor { hitable, material}; 66 | scene.add_actor(actor); 67 | 68 | // A sphere 69 | let hitable = Box::new(Sphere::new(1.5)); 70 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, 0.0, 1.5]))); 71 | let texture = Box::new(UniformTexture::new(Vec3::from_array([1.0, 0.2, 0.2]))); 72 | let material = Box::new(MetalMaterial::new(texture, 0.0)); 73 | let actor = Actor { hitable, material}; 74 | scene.add_actor(actor); 75 | 76 | // A light 77 | let hitable = Box::new(Sphere::new(2.5)); 78 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, -2.0, 12.5]))); 79 | let texture = Box::new(UniformTexture::new(Vec3::from_array([1.0, 1.0, 1.0]))); 80 | let material = Box::new(PlainMaterial::new(texture)); 81 | let actor = Actor { hitable, material}; 82 | scene.add_actor(actor); 83 | 84 | 85 | // Set up the camera 86 | let width = 320; 87 | let height = 180; 88 | let aspect = width as f64 / height as f64; 89 | 90 | let mut camera = PerspectiveCamera::::new(); 91 | camera.set_fov(0.37 * std::f64::consts::PI); 92 | camera.set_position(&[0.0, - 4.0, 1.5]); 93 | // camera.set_direction(&[0.0, 1.0, 0.0]); 94 | camera.set_lookat(&[0.0, 0.0, 1.5]); 95 | camera.set_up(&[0.0, 0.0, 1.0]); 96 | camera.set_fov(0.35 * std::f64::consts::PI); 97 | 98 | // Set up the renderer 99 | let samples = 256; 100 | let max_reflections = 8; 101 | let antialiasing = false 102 | let renderer = Renderer::new(width, height, samples, max_reflections, antialiasing); 103 | 104 | // Process the image 105 | let image = renderer.render(&scene, &camera); 106 | ``` 107 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesgenova/ray-tracer/8a6e5cf8fb65caf6a3dda783891a78204ceacbd1/demo.png -------------------------------------------------------------------------------- /demo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesgenova/ray-tracer/8a6e5cf8fb65caf6a3dda783891a78204ceacbd1/demo_small.png -------------------------------------------------------------------------------- /src/actor/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::hitable::Hitable; 3 | use crate::material::Material; 4 | 5 | pub struct Actor 6 | where T: Float 7 | { 8 | pub hitable: Box>, 9 | pub material: Box> 10 | } 11 | -------------------------------------------------------------------------------- /src/boundingbox/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use crate::ray::Ray; 4 | 5 | pub struct BoundingBox 6 | where T : Float 7 | { 8 | p0: Vec3, 9 | p1: Vec3 10 | } 11 | 12 | impl BoundingBox 13 | where T : Float 14 | { 15 | pub fn new(p0: Vec3, p1: Vec3) -> Self { 16 | let mut p0 = p0; 17 | let mut p1 = p1; 18 | // Ensure the min x,y,z is always in p0, and the max is always in p1 19 | let (min_x, max_x) = BoundingBox::::calculate_axis_bounds(&p0, &p1, 0); 20 | let (min_y, max_y) = BoundingBox::::calculate_axis_bounds(&p0, &p1, 1); 21 | let (min_z, max_z) = BoundingBox::::calculate_axis_bounds(&p0, &p1, 2); 22 | 23 | p0.set_data(&[min_x, min_y, min_z]); 24 | p1.set_data(&[max_x, max_y, max_z]); 25 | BoundingBox {p0, p1} 26 | } 27 | 28 | pub fn hit(&self, ray: &Ray, t_min: T, t_max: T) -> bool { 29 | let mut t_min = t_min; 30 | let mut t_max = t_max; 31 | for i in 0..3 { 32 | let inv_d = T::one() / ray.get_direction().get_data()[i]; 33 | let mut t0 = (self.p0.get_data()[i] - ray.get_origin().get_data()[i]) * inv_d; 34 | let mut t1 = (self.p1.get_data()[i] - ray.get_origin().get_data()[i]) * inv_d; 35 | if inv_d < T::zero() { 36 | let tmp = t0; 37 | t0 = t1; 38 | t1 = tmp; 39 | } 40 | t_min = if t0 > t_min { t0 } else { t_min }; 41 | t_max = if t1 < t_max { t1 } else { t_max }; 42 | if t_max <= t_min { 43 | return false; 44 | } 45 | } 46 | true 47 | } 48 | 49 | pub fn contains(&self, other: &BoundingBox) -> bool { 50 | for i in 0..3 { 51 | let (min_self, max_self) = self.get_axis_bounds(i); 52 | let (min_other, max_other) = other.get_axis_bounds(i); 53 | if min_other < min_self || max_other > max_self { 54 | return false; 55 | } 56 | } 57 | true 58 | } 59 | 60 | pub fn overlaps(&self, other: &BoundingBox) -> bool { 61 | for i in 0..3 { 62 | let (min_self, max_self) = self.get_axis_bounds(i); 63 | let (min_other, max_other) = other.get_axis_bounds(i); 64 | if max_other <= min_self || min_other >= max_self { 65 | return false; 66 | } 67 | } 68 | true 69 | } 70 | 71 | pub fn expand(&mut self, other: &BoundingBox) -> bool { 72 | let mut expanded = false; 73 | for i in 0..3 { 74 | let (min_self, max_self) = self.get_axis_bounds(i); 75 | let (min_other, max_other) = other.get_axis_bounds(i); 76 | if min_other < min_self { 77 | self.p0.get_data_mut()[i] = min_other; 78 | expanded = true; 79 | } 80 | if max_other > max_self { 81 | self.p1.get_data_mut()[i] = max_other; 82 | expanded = true; 83 | } 84 | } 85 | expanded 86 | } 87 | 88 | pub fn get_p0(&self) -> &Vec3 { 89 | &self.p0 90 | } 91 | 92 | pub fn get_p1(&self) -> &Vec3 { 93 | &self.p1 94 | } 95 | 96 | pub fn get_volume(&self) -> T { 97 | let mut volume = T::one(); 98 | 99 | for i in 0..3 { 100 | let (min, max) = self.get_axis_bounds(i); 101 | volume = volume * (max - min); 102 | } 103 | 104 | volume 105 | } 106 | 107 | pub fn get_axis_bounds(&self, axis: usize) -> (T, T) { 108 | let min = self.p0.get_data()[axis]; 109 | let max = self.p1.get_data()[axis]; 110 | (min, max) 111 | } 112 | 113 | pub fn get_axis_length(&self, axis: usize) -> T { 114 | let (min, max) = self.get_axis_bounds(axis); 115 | max - min 116 | } 117 | 118 | pub fn make_cube(&mut self) { 119 | let mut max_length = T::zero(); 120 | for i in 0..3 { 121 | let length = self.get_axis_length(i); 122 | if length > max_length { 123 | max_length = length; 124 | } 125 | } 126 | 127 | let half = T::from(0.5).unwrap(); 128 | 129 | for i in 0..3 { 130 | let length = self.get_axis_length(i); 131 | let pad = half * (max_length - length); 132 | if pad > T::zero() { 133 | self.pad_axis(pad, i); 134 | } 135 | } 136 | } 137 | 138 | pub fn pad_axis(&mut self, pad: T, axis: usize) { 139 | let (min, max) = self.get_axis_bounds(axis); 140 | self.p0.get_data_mut()[axis] = min - pad; 141 | self.p1.get_data_mut()[axis] = max + pad; 142 | } 143 | 144 | fn calculate_axis_bounds(p0: &Vec3, p1: &Vec3, axis: usize) -> (T, T) { 145 | let mut min = p0.get_data()[axis]; 146 | let mut max = p1.get_data()[axis]; 147 | if min > max { 148 | let tmp = min; 149 | min = max; 150 | max = tmp; 151 | } 152 | (min, max) 153 | } 154 | } 155 | 156 | #[cfg(test)] 157 | mod tests { 158 | use super::*; 159 | 160 | #[test] 161 | fn contains() { 162 | let p0 = Vec3::from_array([0.0, 0.0, 0.0]); 163 | let p1 = Vec3::from_array([5.0, 4.0, 3.0]); 164 | let box0 = BoundingBox::new(p0, p1); 165 | 166 | let p0 = Vec3::from_array([1.0, 1.0, 1.0]); 167 | let p1 = Vec3::from_array([2.0, 2.0, 2.0]); 168 | let box1 = BoundingBox::new(p0, p1); 169 | 170 | assert!(box0.contains(&box1)); 171 | assert!(!box1.contains(&box0)); 172 | 173 | let p0 = Vec3::from_array([0.0, 0.0, 0.0]); 174 | let p1 = Vec3::from_array([5.0, 4.0, 3.0]); 175 | let box0 = BoundingBox::new(p1, p0); 176 | 177 | let p0 = Vec3::from_array([1.0, 1.0, 1.0]); 178 | let p1 = Vec3::from_array([2.0, 2.0, 2.0]); 179 | let box1 = BoundingBox::new(p1, p0); 180 | 181 | assert!(box0.contains(&box1)); 182 | assert!(!box1.contains(&box0)); 183 | 184 | let p0 = Vec3::from_array([0.0, 0.0, 0.0]); 185 | let p1 = Vec3::from_array([5.0, 4.0, 3.0]); 186 | let box0 = BoundingBox::new(p0, p1); 187 | 188 | let p0 = Vec3::from_array([-1.0, 1.0, 1.0]); 189 | let p1 = Vec3::from_array([2.0, 2.0, 2.0]); 190 | let box1 = BoundingBox::new(p0, p1); 191 | 192 | assert!(!box0.contains(&box1)); 193 | assert!(!box1.contains(&box0)); 194 | } 195 | 196 | #[test] 197 | fn overlaps() { 198 | let p0 = Vec3::from_array([0.0, 0.0, 0.0]); 199 | let p1 = Vec3::from_array([5.0, 4.0, 3.0]); 200 | let box0 = BoundingBox::new(p0, p1); 201 | 202 | let p0 = Vec3::from_array([1.0, 1.0, 1.0]); 203 | let p1 = Vec3::from_array([2.0, 2.0, 2.0]); 204 | let box1 = BoundingBox::new(p0, p1); 205 | 206 | assert!(box0.overlaps(&box1)); 207 | assert!(box1.overlaps(&box0)); 208 | 209 | let p0 = Vec3::from_array([0.0, 0.0, 0.0]); 210 | let p1 = Vec3::from_array([5.0, 4.0, 3.0]); 211 | let box0 = BoundingBox::new(p1, p0); 212 | 213 | let p0 = Vec3::from_array([-1.0, -1.0, -1.0]); 214 | let p1 = Vec3::from_array([2.0, 2.0, 2.0]); 215 | let box1 = BoundingBox::new(p1, p0); 216 | 217 | assert!(box0.overlaps(&box1)); 218 | assert!(box1.overlaps(&box0)); 219 | 220 | let p0 = Vec3::from_array([0.0, 0.0, 0.0]); 221 | let p1 = Vec3::from_array([5.0, 4.0, 3.0]); 222 | let box0 = BoundingBox::new(p0, p1); 223 | 224 | let p0 = Vec3::from_array([-2.0, -2.0, -2.0]); 225 | let p1 = Vec3::from_array([-1.0, -1.0, -1.0]); 226 | let box1 = BoundingBox::new(p0, p1); 227 | 228 | assert!(!box0.overlaps(&box1)); 229 | assert!(!box1.overlaps(&box0)); 230 | } 231 | 232 | #[test] 233 | fn hit() { 234 | let half_size = Vec3::from_array([2.0, 1.0, 3.0]); 235 | let box0 = BoundingBox::new(&half_size * (-1.0), &half_size * 1.0); 236 | 237 | for i in 0..3 { 238 | let mut origin = Vec3::new(); 239 | origin.get_data_mut()[i] = - 2.0 * half_size.get_data()[i]; 240 | let mut direction = Vec3::new(); 241 | origin.get_data_mut()[i] = 1.0; 242 | let ray = Ray::from_vec(origin, direction); 243 | assert!(box0.hit(&ray, 0.0, 100.0)); 244 | 245 | let mut origin = Vec3::new(); 246 | origin.get_data_mut()[i] = - 2.0 * half_size.get_data()[i]; 247 | let mut direction = Vec3::new(); 248 | origin.get_data_mut()[(i + 1) % 3] = 1.0; 249 | let ray = Ray::from_vec(origin, direction); 250 | assert!(!box0.hit(&ray, 0.0, 100.0)); 251 | } 252 | } 253 | 254 | #[test] 255 | fn volume() { 256 | let p0 = Vec3::from_array([-1.0, 2.0, -4.0]); 257 | let p1 = Vec3::from_array([5.0, 4.0, 3.0]); 258 | let box0 = BoundingBox::new(p0, p1); 259 | assert_eq!(box0.get_volume(), 6.0 * 2.0 * 7.0); 260 | } 261 | 262 | #[test] 263 | fn expand() { 264 | let p0 = Vec3::from_array([0.0, 0.0, 0.0]); 265 | let p1 = Vec3::from_array([5.0, 4.0, 3.0]); 266 | let mut box0 = BoundingBox::new(p0, p1); 267 | 268 | let p0 = Vec3::from_array([1.0, 1.0, 1.0]); 269 | let p1 = Vec3::from_array([2.0, 2.0, 2.0]); 270 | let box1 = BoundingBox::new(p0, p1); 271 | 272 | assert!(box0.contains(&box1)); 273 | assert!(!box0.expand(&box1)); 274 | assert!(box0.contains(&box1)); 275 | 276 | let p0 = Vec3::from_array([0.0, 0.0, 0.0]); 277 | let p1 = Vec3::from_array([5.0, 4.0, 3.0]); 278 | let mut box0 = BoundingBox::new(p0, p1); 279 | 280 | let p0 = Vec3::from_array([-1.0, -1.0, -1.0]); 281 | let p1 = Vec3::from_array([2.0, 2.0, 2.0]); 282 | let box1 = BoundingBox::new(p1, p0); 283 | 284 | assert!(!box0.contains(&box1)); 285 | assert!(box0.expand(&box1)); 286 | assert!(box0.contains(&box1)); 287 | } 288 | 289 | #[test] 290 | fn pad() { 291 | let p0 = Vec3::from_array([0.0, 0.0, 0.0]); 292 | let p1 = Vec3::from_array([5.0, 4.0, 3.0]); 293 | let box0 = BoundingBox::new(p0, p1); 294 | 295 | let p0 = Vec3::from_array([0.0, 0.0, 0.0]); 296 | let p1 = Vec3::from_array([5.0, 4.0, 3.0]); 297 | let mut box1 = BoundingBox::new(p0, p1); 298 | 299 | assert!(box0.contains(&box1)); 300 | assert!(box1.contains(&box0)); 301 | 302 | box1.pad_axis(1.0, 0); 303 | box1.pad_axis(2.0, 1); 304 | box1.pad_axis(3.0, 2); 305 | assert!(!box0.contains(&box1)); 306 | assert!(box1.contains(&box0)); 307 | assert_eq!(box1.get_axis_length(0), 7.0); 308 | assert_eq!(box1.get_axis_length(1), 8.0); 309 | assert_eq!(box1.get_axis_length(2), 9.0); 310 | } 311 | 312 | #[test] 313 | fn make_cube() { 314 | let p0 = Vec3::from_array([0.0, 0.0, 0.0]); 315 | let p1 = Vec3::from_array([5.0, 4.0, 3.0]); 316 | let box0 = BoundingBox::new(p0, p1); 317 | 318 | let p0 = Vec3::from_array([0.0, 0.0, 0.0]); 319 | let p1 = Vec3::from_array([5.0, 4.0, 3.0]); 320 | let mut box1 = BoundingBox::new(p0, p1); 321 | 322 | assert!(box0.contains(&box1)); 323 | assert!(box1.contains(&box0)); 324 | 325 | box1.make_cube(); 326 | assert!(!box0.contains(&box1)); 327 | assert!(box1.contains(&box0)); 328 | assert_eq!(box1.get_axis_length(0), 5.0); 329 | assert_eq!(box1.get_axis_length(1), 5.0); 330 | assert_eq!(box1.get_axis_length(2), 5.0); 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /src/camera/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use crate::ray::Ray; 4 | 5 | pub mod perspective; 6 | 7 | pub enum CameraLock { 8 | Direction, 9 | LookAt 10 | } 11 | 12 | pub trait Camera 13 | where T: Float 14 | { 15 | fn get_position(&self) -> &Vec3; 16 | fn set_position(&mut self, position: &[T]); 17 | 18 | fn get_direction(&self) -> &Vec3; 19 | fn set_direction(&mut self, direction: &[T]); 20 | 21 | fn get_lookat(&self) -> &Vec3; 22 | fn set_lookat(&mut self, lookat: &[T]); 23 | 24 | fn get_up(&self) -> &Vec3; 25 | fn set_up(&mut self, up: &[T]); 26 | 27 | fn get_aperture(&self) -> T; 28 | fn set_aperture(&mut self, aperture: T); 29 | 30 | fn get_focus(&self) -> T; 31 | fn set_focus(&mut self, focus: T); 32 | 33 | fn get_aspect(&self) -> T; 34 | fn set_aspect(&mut self, aspect: T); 35 | 36 | fn get_fov(&self) -> T; 37 | fn set_fov(&mut self, fov: T); 38 | 39 | fn get_ray(&self, r: T, s: T) -> Ray; 40 | } 41 | -------------------------------------------------------------------------------- /src/camera/perspective.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use crate::ray::Ray; 4 | use crate::camera::{Camera, CameraLock}; 5 | use crate::utils::random_point_in_circle; 6 | 7 | pub struct PerspectiveCamera 8 | where T: Float 9 | { 10 | position: Vec3, 11 | direction: Vec3, 12 | lookat: Vec3, 13 | center: Vec3, 14 | up: Vec3, 15 | u: Vec3, 16 | v: Vec3, 17 | w: Vec3, 18 | aspect: T, 19 | fov: T, 20 | half_height: T, 21 | half_width: T, 22 | aperture: T, 23 | focus: T, 24 | lock: CameraLock 25 | } 26 | 27 | impl PerspectiveCamera 28 | where T: Float 29 | { 30 | pub fn new() -> Self { 31 | let mut camera = PerspectiveCamera { 32 | position: Vec3::::from_array([T::zero(), T::zero(), T::zero()]), 33 | direction: Vec3::::from_array([T::zero(), T::zero(), -T::one()]), 34 | lookat: Vec3::::from_array([T::zero(), T::zero(), -T::one()]), 35 | center: Vec3::::from_array([T::zero(), T::zero(), -T::one()]), 36 | up: Vec3::::from_array([T::zero(), T::one(), T::zero()]), 37 | u: Vec3::::new(), 38 | v: Vec3::::new(), 39 | w: Vec3::::new(), 40 | aspect: T::one(), 41 | half_height: T::one(), 42 | half_width: T::one(), 43 | aperture: T::zero(), 44 | focus: T::one(), 45 | fov: T::from(0.5 * 3.1415).unwrap(), 46 | lock: CameraLock::Direction 47 | }; 48 | camera.update(); 49 | camera 50 | } 51 | 52 | pub fn update(&mut self) { 53 | let direction = match self.lock { 54 | CameraLock::Direction => { 55 | self.get_direction() * T::one() 56 | }, 57 | CameraLock::LookAt => { 58 | self.get_lookat() - self.get_position() 59 | } 60 | }; 61 | self.w.set_data(direction.get_data()); 62 | self.w.normalize(); 63 | self.u.set_data(self.w.cross(&self.up).get_data()); 64 | self.u.normalize(); 65 | self.v.set_data(self.w.cross(&self.u).get_data()); 66 | self.v.normalize(); 67 | self.center = &self.position + &self.w * self.focus; 68 | self.half_height = ( T::from(0.5).unwrap() * self.fov ).tan() * self.focus; 69 | self.half_width = self.aspect * self.half_height; 70 | } 71 | } 72 | 73 | impl Camera for PerspectiveCamera 74 | where T: Float 75 | { 76 | fn get_position(&self) -> &Vec3 { 77 | &self.position 78 | } 79 | 80 | fn set_position(&mut self, position: &[T]) { 81 | self.position.set_data(position); 82 | } 83 | 84 | fn get_direction(&self) -> &Vec3 { 85 | &self.direction 86 | } 87 | 88 | fn set_direction(&mut self, direction: &[T]) { 89 | self.direction.set_data(direction); 90 | self.lock = CameraLock::Direction; 91 | self.update(); 92 | } 93 | 94 | fn get_lookat(&self) -> &Vec3 { 95 | &self.lookat 96 | } 97 | 98 | fn set_lookat(&mut self, lookat: &[T]) { 99 | self.lookat.set_data(lookat); 100 | self.lock = CameraLock::LookAt; 101 | self.update(); 102 | } 103 | 104 | fn get_up(&self) -> &Vec3 { 105 | &self.up 106 | } 107 | 108 | fn set_up(&mut self, up: &[T]) { 109 | self.up.set_data(up); 110 | self.update(); 111 | } 112 | 113 | fn get_aperture(&self) -> T { 114 | self.aperture 115 | } 116 | 117 | fn set_aperture(&mut self, aperture: T) { 118 | self.aperture = aperture; 119 | self.update(); 120 | } 121 | 122 | fn get_focus(&self) -> T { 123 | self.focus 124 | } 125 | 126 | fn set_focus(&mut self, focus: T) { 127 | self.focus = focus; 128 | self.update(); 129 | } 130 | 131 | fn get_aspect(&self) -> T { 132 | self.aspect 133 | } 134 | 135 | fn set_aspect(&mut self, aspect: T) { 136 | self.aspect = aspect; 137 | self.update(); 138 | } 139 | 140 | fn get_fov(&self) -> T { 141 | self.fov 142 | } 143 | 144 | fn set_fov(&mut self, fov: T) { 145 | self.fov = fov; 146 | self.update(); 147 | } 148 | 149 | fn get_ray(&self, r: T, s: T) -> Ray { 150 | let offset = if self.aperture > T::zero() { 151 | random_point_in_circle(self.aperture * T::from(0.5).unwrap()) 152 | } else { 153 | Vec3::::new() 154 | }; 155 | let mut ray_direction = &self.center + &self.u * r * self.half_width + &self.v * s * self.half_height - &self.position - &offset; 156 | ray_direction.normalize(); 157 | let origin = &self.position + &offset; 158 | Ray::::from_slice(origin.get_data(), ray_direction.get_data()) 159 | } 160 | } 161 | 162 | #[cfg(test)] 163 | mod tests { 164 | use super::*; 165 | 166 | fn check_camera(camera: &PerspectiveCamera) 167 | where T: Float + std::fmt::Debug 168 | { 169 | assert_eq!(camera.u.norm(), T::one()); 170 | assert_eq!(camera.v.norm(), T::one()); 171 | assert_eq!(camera.w.norm(), T::one()); 172 | assert_eq!(camera.u.dot(&camera.v), T::zero()); 173 | assert_eq!(camera.u.dot(&camera.w), T::zero()); 174 | assert_eq!(camera.v.dot(&camera.w), T::zero()); 175 | } 176 | 177 | #[test] 178 | fn init() { 179 | let camera = PerspectiveCamera::::new(); 180 | check_camera(&camera); 181 | } 182 | 183 | #[test] 184 | fn set() { 185 | let mut camera = PerspectiveCamera::::new(); 186 | let position = [-2.0, 0.0, 0.0]; 187 | camera.set_position(&position); 188 | assert_eq!(camera.get_position().get_data(), position); 189 | check_camera(&camera); 190 | 191 | let direction = [2.0, 0.0, 0.0]; 192 | camera.set_direction(&direction); 193 | assert_eq!(camera.get_direction().get_data(), direction); 194 | check_camera(&camera); 195 | 196 | let up = [2.0, 4.0, 0.0]; 197 | camera.set_up(&up); 198 | assert_eq!(camera.get_up().get_data(), up); 199 | check_camera(&camera); 200 | 201 | assert_eq!(camera.w.get_data(), [1.0, 0.0, 0.0]); 202 | assert_eq!(camera.u.get_data(), [0.0, 0.0, 1.0]); 203 | assert_eq!(camera.v.get_data(), [0.0, -1.0, 0.0]); 204 | } 205 | 206 | #[test] 207 | fn rays() { 208 | let mut camera = PerspectiveCamera::::new(); 209 | camera.set_fov(0.5 * std::f64::consts::PI); 210 | camera.set_aspect(2.0); 211 | camera.set_position(&[0., 0., -10.]); 212 | 213 | let ray = camera.get_ray(0.0, 1.0); 214 | assert_eq!(ray.get_origin().get_data(), camera.get_position().get_data()); 215 | let ray = camera.get_ray(0.0, -1.0); 216 | assert_eq!(ray.get_origin().get_data(), camera.get_position().get_data()); 217 | let ray = camera.get_ray(1.0, 0.0); 218 | assert_eq!(ray.get_origin().get_data(), camera.get_position().get_data()); 219 | let ray = camera.get_ray(-1.0, 0.0); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/constants/mod.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone)] 2 | pub enum Axis { 3 | X, 4 | Y, 5 | Z 6 | } 7 | -------------------------------------------------------------------------------- /src/float/mod.rs: -------------------------------------------------------------------------------- 1 | use num_traits::float::{FloatCore as NumFloat}; 2 | 3 | pub trait Number { 4 | fn sqrt(&self) -> Self; 5 | fn tan(&self) -> Self; 6 | } 7 | 8 | impl Number for f64 { 9 | fn sqrt(&self) -> Self { 10 | f64::sqrt(*self) 11 | } 12 | 13 | fn tan(&self) -> Self { 14 | f64::tan(*self) 15 | } 16 | } 17 | impl Number for f32 { 18 | fn sqrt(&self) -> Self { 19 | f32::sqrt(*self) 20 | } 21 | 22 | fn tan(&self) -> Self { 23 | f32::tan(*self) 24 | } 25 | } 26 | 27 | pub trait Float : 'static + NumFloat + Number {} 28 | 29 | impl Float for f64 {} 30 | impl Float for f32 {} 31 | -------------------------------------------------------------------------------- /src/hit/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use crate::ray::Ray; 4 | use crate::actor::Actor; 5 | 6 | pub struct Hit 7 | where T: Float 8 | { 9 | pub point: Vec3, 10 | pub normal: Vec3, 11 | pub t: T 12 | } 13 | -------------------------------------------------------------------------------- /src/hitable/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use crate::ray::Ray; 4 | use crate::hit::Hit; 5 | use crate::boundingbox::BoundingBox; 6 | 7 | pub mod primitive; 8 | pub mod transform; 9 | 10 | pub trait Hitable 11 | where T: Float 12 | { 13 | fn hit(&self, ray: &Ray, t_min: T, t_max: T) -> Option>; 14 | fn get_bounds(&self) -> &BoundingBox; 15 | fn unwrap(self: Box) -> Box>; 16 | fn is_primitive(&self) -> bool { 17 | // Primitives (i.e. spheres, boxes, rectangles) return true, 18 | // Decorators (i.e. translations, rotations) return false 19 | true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/hitable/primitive/cube.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use crate::ray::Ray; 4 | use crate::hit::Hit; 5 | use crate::hitable::Hitable; 6 | use crate::hitable::primitive::Rectangle; 7 | use crate::hitable::transform::Translation; 8 | use crate::hitable::primitive::Group; 9 | use crate::boundingbox::BoundingBox; 10 | use crate::constants::Axis; 11 | use crate::utils::axis_to_index; 12 | 13 | pub struct Cube 14 | where T: Float 15 | { 16 | length: T, 17 | width: T, 18 | height: T, 19 | faces: Group 20 | } 21 | 22 | impl Cube 23 | where T: Float 24 | { 25 | pub fn new(length: T, width: T, height: T) -> Self { 26 | let axes = [ 27 | (Axis::X, Axis::Y), 28 | (Axis::Y, Axis::X), 29 | (Axis::Y, Axis::Z), 30 | (Axis::Z, Axis::Y), 31 | (Axis::Z, Axis::X), 32 | (Axis::X, Axis::Z) 33 | ]; 34 | 35 | let lengths = [ 36 | (length, width, height), 37 | (width, length, height), 38 | (width, height, length), 39 | (height, width, length), 40 | (height, length, width), 41 | (length, height, width) 42 | ]; 43 | 44 | let half = T::from(0.5).unwrap(); 45 | let mut faces = Group::::new(); 46 | 47 | for i in 0..6 { 48 | let (width, height, depth) = lengths[i]; 49 | let (width_axis, height_axis) = axes[i]; 50 | let face = Box::new(Rectangle::::new(width, width_axis, height, height_axis)); 51 | let translation = face.get_normal() * depth * half; 52 | let face : Box> = Box::new(Translation::new(face, translation)); 53 | faces.add_hitable(face); 54 | }; 55 | 56 | Cube { 57 | length, 58 | width, 59 | height, 60 | faces 61 | } 62 | } 63 | } 64 | 65 | impl Hitable for Cube 66 | where T: Float 67 | { 68 | fn hit(&self, ray: &Ray, t_min: T, t_max: T) -> Option> { 69 | self.faces.hit(ray, t_min, t_max) 70 | } 71 | 72 | fn get_bounds(&self) -> &BoundingBox { 73 | self.faces.get_bounds() 74 | } 75 | 76 | fn unwrap(self: Box) -> Box> { 77 | self 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use super::*; 84 | 85 | #[test] 86 | fn init() { 87 | let length = 2.0; 88 | let width = 2.0; 89 | let height = 4.0; 90 | let cube = Cube::::new(length, width, height); 91 | } 92 | 93 | #[test] 94 | fn hit() { 95 | let length = 2.0; 96 | let width = 4.0; 97 | let height = 6.0; 98 | let cube = Cube::::new(length, width, height); 99 | 100 | // Hit YZ faces 101 | let origin = [-8.0, 0.0, 0.0]; 102 | let direction = [2.0, 0.0, 0.0]; 103 | let ray = Ray::from_array(origin, direction); 104 | let hit = cube.hit(&ray, 0.0, 100.0); 105 | 106 | match hit { 107 | Some(hit) => { 108 | assert_eq!(hit.point.get_data(), [-1.0, 0.0, 0.0]); 109 | assert_eq!(hit.normal.get_data(), [-1.0, 0.0, 0.0]); 110 | assert_eq!(hit.t, 3.5); 111 | }, 112 | None => { 113 | assert!(false); 114 | } 115 | } 116 | 117 | let origin = [8.0, 0.0, 0.0]; 118 | let direction = [-2.0, 0.0, 0.0]; 119 | let ray = Ray::from_array(origin, direction); 120 | let hit = cube.hit(&ray, 0.0, 100.0); 121 | 122 | match hit { 123 | Some(hit) => { 124 | assert_eq!(hit.point.get_data(), [1.0, 0.0, 0.0]); 125 | assert_eq!(hit.normal.get_data(), [1.0, 0.0, 0.0]); 126 | assert_eq!(hit.t, 3.5); 127 | }, 128 | None => { 129 | assert!(false); 130 | } 131 | } 132 | 133 | // Hit XZ faces 134 | let origin = [0.0, -8.0, 0.0]; 135 | let direction = [0.0, 2.0, 0.0]; 136 | let ray = Ray::from_array(origin, direction); 137 | let hit = cube.hit(&ray, 0.0, 100.0); 138 | 139 | match hit { 140 | Some(hit) => { 141 | assert_eq!(hit.point.get_data(), [0.0, -2.0, 0.0]); 142 | assert_eq!(hit.normal.get_data(), [0.0, -1.0, 0.0]); 143 | assert_eq!(hit.t, 3.0); 144 | }, 145 | None => { 146 | assert!(false); 147 | } 148 | } 149 | 150 | let origin = [0.0, 8.0, 0.0]; 151 | let direction = [0.0, -2.0, 0.0]; 152 | let ray = Ray::from_array(origin, direction); 153 | let hit = cube.hit(&ray, 0.0, 100.0); 154 | 155 | match hit { 156 | Some(hit) => { 157 | assert_eq!(hit.point.get_data(), [0.0, 2.0, 0.0]); 158 | assert_eq!(hit.normal.get_data(), [0.0, 1.0, 0.0]); 159 | assert_eq!(hit.t, 3.0); 160 | }, 161 | None => { 162 | assert!(false); 163 | } 164 | } 165 | 166 | // Hit XY faces 167 | let origin = [0.0, 0.0, 8.0]; 168 | let direction = [0.0, 0.0, -2.0]; 169 | let ray = Ray::from_array(origin, direction); 170 | let hit = cube.hit(&ray, 0.0, 100.0); 171 | 172 | match hit { 173 | Some(hit) => { 174 | assert_eq!(hit.point.get_data(), [0.0, 0.0, 3.0]); 175 | assert_eq!(hit.normal.get_data(), [0.0, 0.0, 1.0]); 176 | assert_eq!(hit.t, 2.5); 177 | }, 178 | None => { 179 | assert!(false); 180 | } 181 | } 182 | 183 | let origin = [0.0, 0.0, -8.0]; 184 | let direction = [0.0, 0.0, 2.0]; 185 | let ray = Ray::from_array(origin, direction); 186 | let hit = cube.hit(&ray, 0.0, 100.0); 187 | 188 | match hit { 189 | Some(hit) => { 190 | assert_eq!(hit.point.get_data(), [0.0, 0.0, -3.0]); 191 | assert_eq!(hit.normal.get_data(), [0.0, 0.0, -1.0]); 192 | assert_eq!(hit.t, 2.5); 193 | }, 194 | None => { 195 | assert!(false); 196 | } 197 | } 198 | 199 | // Hit nothing 200 | let origin = [-8.0, 0.0, 0.0]; 201 | let direction = [-2.0, 0.0, 0.0]; 202 | let ray = Ray::from_array(origin, direction); 203 | let hit = cube.hit(&ray, 0.0, 100.0); 204 | 205 | if let Some(_hit) = hit { 206 | assert!(false); 207 | } 208 | 209 | let origin = [-8.0, 2.001, 3.001]; 210 | let direction = [2.0, 0.0, 0.0]; 211 | let ray = Ray::from_array(origin, direction); 212 | let hit = cube.hit(&ray, 0.0, 100.0); 213 | 214 | if let Some(_hit) = hit { 215 | assert!(false); 216 | } 217 | } 218 | 219 | #[test] 220 | fn bounds() { 221 | let length = 2.0; 222 | let width = 4.0; 223 | let height = 6.0; 224 | let cube = Cube::::new(length, width, height); 225 | let bounds = cube.get_bounds(); 226 | assert_eq!(bounds.get_p0().get_data(), [-1.0, -2.0, -3.0]); 227 | assert_eq!(bounds.get_p1().get_data(), [1.0, 2.0, 3.0]); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/hitable/primitive/group.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use crate::ray::Ray; 4 | use crate::hit::Hit; 5 | use crate::hitable::Hitable; 6 | use crate::boundingbox::BoundingBox; 7 | 8 | pub struct Group 9 | where T: Float 10 | { 11 | hitables: Vec>>, 12 | bounds: BoundingBox 13 | } 14 | 15 | impl Group 16 | where T: Float 17 | { 18 | pub fn new() -> Self { 19 | Group { 20 | hitables: vec![], 21 | bounds: BoundingBox::::new(Vec3::::new(), Vec3::::new()) 22 | } 23 | } 24 | 25 | pub fn add_hitable(&mut self, hitable: Box>) { 26 | self.bounds.expand(hitable.get_bounds()); 27 | self.hitables.push(hitable); 28 | } 29 | } 30 | 31 | impl Hitable for Group 32 | where T: Float 33 | { 34 | fn hit(&self, ray: &Ray, t_min: T, t_max: T) -> Option> { 35 | let mut t_max = t_max; 36 | let mut result : Option> = None; 37 | 38 | for i in 0..self.hitables.len() { 39 | if let Some(hit) = self.hitables[i].hit(ray, t_min, t_max) { 40 | t_max = hit.t; 41 | result = Some(hit); 42 | } 43 | } 44 | result 45 | } 46 | 47 | fn get_bounds(&self) -> &BoundingBox { 48 | &self.bounds 49 | } 50 | 51 | fn unwrap(self: Box) -> Box> { 52 | self 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/hitable/primitive/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod sphere; 2 | pub mod rectangle; 3 | pub mod cube; 4 | pub mod group; 5 | 6 | pub use sphere::Sphere; 7 | pub use rectangle::Rectangle; 8 | pub use cube::Cube; 9 | pub use group::Group; 10 | -------------------------------------------------------------------------------- /src/hitable/primitive/rectangle.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use crate::ray::Ray; 4 | use crate::hit::Hit; 5 | use crate::hitable::Hitable; 6 | use crate::boundingbox::BoundingBox; 7 | use crate::constants::Axis; 8 | use crate::utils::axis_to_index; 9 | 10 | pub struct Rectangle 11 | where T: Float 12 | { 13 | width: T, 14 | width_axis: Axis, 15 | height: T, 16 | height_axis: Axis, 17 | normal_axis: Axis, 18 | normal: Vec3, 19 | bounds: BoundingBox 20 | } 21 | 22 | impl Rectangle 23 | where T: Float 24 | { 25 | pub fn new(width: T, width_axis: Axis, height: T, height_axis: Axis) -> Self { 26 | let (normal_axis, normal_direction) = Rectangle::::normal_axis(&width_axis, &height_axis); 27 | let half = T::from(0.5).unwrap(); 28 | let p0 = Rectangle::::length_to_point(- width * half, &width_axis); 29 | let p0 = p0 + Rectangle::::length_to_point(- height * half, &height_axis); 30 | 31 | let p1 = Rectangle::::length_to_point(width * half, &width_axis); 32 | let p1 = p1 + Rectangle::::length_to_point(height * half, &height_axis); 33 | 34 | let normal = Rectangle::::length_to_point(normal_direction, &normal_axis); 35 | let bounds = BoundingBox::::new(p0, p1); 36 | 37 | Rectangle { 38 | width, 39 | width_axis, 40 | height, 41 | height_axis, 42 | normal_axis, 43 | normal, 44 | bounds 45 | } 46 | } 47 | 48 | pub fn get_normal(&self) -> &Vec3 { 49 | &self.normal 50 | } 51 | 52 | fn length_to_point(length: T, axis: &Axis) -> Vec3 { 53 | match axis { 54 | Axis::X => Vec3::::from_array([length, T::zero(), T::zero()]), 55 | Axis::Y => Vec3::::from_array([T::zero(), length, T::zero()]), 56 | Axis::Z => Vec3::::from_array([T::zero(), T::zero(), length]) 57 | } 58 | } 59 | 60 | fn normal_axis(width_axis: &Axis, height_axis: &Axis) -> (Axis, T) { 61 | match (width_axis, height_axis) { 62 | (Axis::X, Axis::Y) => (Axis::Z, T::one()), 63 | (Axis::Y, Axis::X) => (Axis::Z, -T::one()), 64 | 65 | (Axis::Y, Axis::Z) => (Axis::X, T::one()), 66 | (Axis::Z, Axis::Y) => (Axis::X, -T::one()), 67 | 68 | (Axis::Z, Axis::X) => (Axis::Y, T::one()), 69 | (Axis::X, Axis::Z) => (Axis::Y, -T::one()), 70 | 71 | _ => panic!("Rectangle cannot have width and height along the same axis") 72 | } 73 | } 74 | } 75 | 76 | impl Hitable for Rectangle 77 | where T: Float 78 | { 79 | fn hit(&self, ray: &Ray, t_min: T, t_max: T) -> Option> { 80 | let oc = ray.get_origin(); 81 | let direction = ray.get_direction(); 82 | 83 | let normal_index = axis_to_index(&self.normal_axis); 84 | 85 | let t = - oc.get_data()[normal_index] / direction.get_data()[normal_index]; 86 | if t <= t_min || t > t_max { 87 | return None; 88 | } 89 | 90 | let width_index = axis_to_index(&self.width_axis); 91 | let height_index = axis_to_index(&self.height_axis); 92 | let width = oc.get_data()[width_index] + t * direction.get_data()[width_index]; 93 | let height = oc.get_data()[height_index] + t * direction.get_data()[height_index]; 94 | 95 | let w0 = self.bounds.get_p0().get_data()[width_index]; 96 | let w1 = self.bounds.get_p1().get_data()[width_index]; 97 | 98 | let h0 = self.bounds.get_p0().get_data()[height_index]; 99 | let h1 = self.bounds.get_p1().get_data()[height_index]; 100 | 101 | if width < w0 || width > w1 || height < h0 || height > h1 { 102 | return None; 103 | } 104 | 105 | let point = ray.get_point(t); 106 | let normal = &self.normal * T::one(); 107 | let hit = Hit { 108 | point, 109 | normal, 110 | t 111 | }; 112 | 113 | Some(hit) 114 | } 115 | 116 | fn get_bounds(&self) -> &BoundingBox { 117 | &self.bounds 118 | } 119 | 120 | fn unwrap(self: Box) -> Box> { 121 | self 122 | } 123 | } 124 | 125 | #[cfg(test)] 126 | mod tests { 127 | use super::*; 128 | 129 | #[test] 130 | fn init() { 131 | let width = 2.0; 132 | let width_axis = Axis::X; 133 | 134 | let height = 4.0; 135 | let height_axis = Axis::Y; 136 | 137 | let rectangle = Rectangle::::new(width, width_axis, height, height_axis); 138 | let normal = &rectangle.normal; 139 | assert_eq!(normal.get_data(), [0.0, 0.0, 1.0]); 140 | 141 | let width = 3.0; 142 | let width_axis = Axis::X; 143 | 144 | let height = 5.0; 145 | let height_axis = Axis::Z; 146 | 147 | let rectangle = Rectangle::::new(width, width_axis, height, height_axis); 148 | let normal = &rectangle.normal; 149 | assert_eq!(normal.get_data(), [0.0, -1.0, 0.0]); 150 | 151 | let width = 2.0; 152 | let width_axis = Axis::Y; 153 | 154 | let height = 4.0; 155 | let height_axis = Axis::Z; 156 | 157 | let rectangle = Rectangle::::new(width, width_axis, height, height_axis); 158 | let normal = &rectangle.normal; 159 | assert_eq!(normal.get_data(), [1.0, 0.0, 0.0]); 160 | } 161 | 162 | #[test] 163 | fn hit() { 164 | let width = 2.0; 165 | let width_axis = Axis::X; 166 | let height = 4.0; 167 | let height_axis = Axis::Y; 168 | let rectangle = Rectangle::::new(width, width_axis, height, height_axis); 169 | 170 | let origin = [0.0, 0.0, 8.0]; 171 | let direction = [0.0, 0.0, -2.0]; 172 | let ray = Ray::from_array(origin, direction); 173 | let hit = rectangle.hit(&ray, 0.0, 100.0); 174 | 175 | match hit { 176 | Some(hit) => { 177 | assert_eq!(hit.point.get_data(), [0.0, 0.0, 0.0]); 178 | assert_eq!(hit.normal.get_data(), [0.0, 0.0, 1.0]); 179 | assert_eq!(hit.t, 4.0); 180 | }, 181 | None => { 182 | assert!(false); 183 | } 184 | } 185 | 186 | let origin = [0.5, 1.5, -8.0]; 187 | let direction = [0.0, 0.0, 2.0]; 188 | let ray = Ray::from_array(origin, direction); 189 | let hit = rectangle.hit(&ray, 0.0, 100.0); 190 | 191 | match hit { 192 | Some(hit) => { 193 | assert_eq!(hit.point.get_data(), [0.5, 1.5, 0.0]); 194 | assert_eq!(hit.normal.get_data(), [0.0, 0.0, 1.0]); 195 | assert_eq!(hit.t, 4.0); 196 | }, 197 | None => { 198 | assert!(false); 199 | } 200 | } 201 | 202 | let origin = [1.0001, 2.0001, 8.0]; 203 | let direction = [0.0, 0.0, -2.0]; 204 | let ray = Ray::from_array(origin, direction); 205 | 206 | if let Some(hit) = rectangle.hit(&ray, 0.0, 100.0) { 207 | assert!(false); 208 | }; 209 | } 210 | 211 | #[test] 212 | fn bounds() { 213 | let width = 2.0; 214 | let width_axis = Axis::X; 215 | 216 | let height = 4.0; 217 | let height_axis = Axis::Y; 218 | 219 | let rectangle = Rectangle::::new(width, width_axis, height, height_axis); 220 | let bounds = rectangle.get_bounds(); 221 | assert_eq!(bounds.get_p0().get_data(), [-1.0, -2.0, 0.0]); 222 | assert_eq!(bounds.get_p1().get_data(), [1.0, 2.0, 0.0]); 223 | 224 | let width = 3.0; 225 | let width_axis = Axis::X; 226 | 227 | let height = 5.0; 228 | let height_axis = Axis::Z; 229 | 230 | let rectangle = Rectangle::::new(width, width_axis, height, height_axis); 231 | let bounds = rectangle.get_bounds(); 232 | assert_eq!(bounds.get_p0().get_data(), [-1.5, 0.0, -2.5]); 233 | assert_eq!(bounds.get_p1().get_data(), [1.5, 0.0, 2.5]); 234 | 235 | let width = 2.0; 236 | let width_axis = Axis::Y; 237 | 238 | let height = 4.0; 239 | let height_axis = Axis::Z; 240 | 241 | let rectangle = Rectangle::::new(width, width_axis, height, height_axis); 242 | let bounds = rectangle.get_bounds(); 243 | assert_eq!(bounds.get_p0().get_data(), [0.0, -1.0, -2.0]); 244 | assert_eq!(bounds.get_p1().get_data(), [0.0, 1.0, 2.0]); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/hitable/primitive/sphere.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use crate::ray::Ray; 4 | use crate::hit::Hit; 5 | use crate::hitable::Hitable; 6 | use crate::boundingbox::BoundingBox; 7 | 8 | pub struct Sphere 9 | where T: Float 10 | { 11 | radius: T, 12 | bounds: BoundingBox 13 | } 14 | 15 | impl Sphere 16 | where T: Float 17 | { 18 | pub fn new(radius: T) -> Self { 19 | let mut sphere = Sphere { 20 | radius, 21 | bounds: BoundingBox::::new(Vec3::::new(), Vec3::::new()) 22 | }; 23 | sphere.update_bounds(); 24 | sphere 25 | } 26 | 27 | pub fn get_radius(&self) -> T { 28 | self.radius 29 | } 30 | 31 | pub fn set_radius(&mut self, radius: T) { 32 | self.radius = radius; 33 | self.update_bounds(); 34 | } 35 | 36 | fn update_bounds(&mut self) { 37 | let one = Vec3::::from_array([T::one(), T::one(), T::one()]); 38 | let p0 = &one * self.get_radius() * (- T::one()); 39 | let p1 = &one * self.get_radius(); 40 | self.bounds = BoundingBox::::new(p0, p1); 41 | } 42 | } 43 | 44 | impl Hitable for Sphere 45 | where T: Float 46 | { 47 | fn hit(&self, ray: &Ray, t_min: T, t_max: T) -> Option> { 48 | // Intersection of a line and a sphere: 49 | // 50 | // p(t) = origin + t * direction 51 | // 52 | // dot( (p(t) - center), (p(t) - center) ) 53 | // = radius * radius 54 | // 55 | // t * t * dot(direction, direction) 56 | // + 2 * t * dot(direction, origin - center) 57 | // + dot(origin - center, origin - center) 58 | // - radius * radius 59 | // = 0 60 | // 61 | // t = (- b +/- sqrt(b * b - 4 * a * c)) / (2 * a) 62 | // 63 | // drop 2s coming from b 64 | let oc = ray.get_origin(); 65 | let a = ray.get_direction().dot(ray.get_direction()); 66 | let b = ray.get_direction().dot(&oc); 67 | let c = oc.dot(&oc) - self.get_radius() * self.get_radius(); 68 | let discriminant = b * b - a * c; 69 | if discriminant <= T::zero() { 70 | return None; 71 | } 72 | let discriminant = discriminant.sqrt(); 73 | let t0 = (- b - discriminant) / a; 74 | let t1 = (- b + discriminant) / a; 75 | let t = if t0 >= t_min && t0 < t_max { t0 } 76 | else if t1 >= t_min && t1 < t_max { t1 } 77 | else { return None; }; 78 | 79 | let point = ray.get_point(t); 80 | let normal = (&point) / self.get_radius(); 81 | let hit = Hit { 82 | point, 83 | normal, 84 | t 85 | }; 86 | 87 | Some(hit) 88 | } 89 | 90 | fn get_bounds(&self) -> &BoundingBox { 91 | &self.bounds 92 | } 93 | 94 | fn unwrap(self: Box) -> Box> { 95 | self 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use super::*; 102 | 103 | #[test] 104 | fn init() { 105 | let sphere = Sphere::::new(1.0); 106 | assert_eq!(sphere.get_radius(), 1.0); 107 | 108 | let radius = 5.5; 109 | let sphere = Sphere::::new(radius); 110 | assert_eq!(sphere.get_radius(), 5.5); 111 | } 112 | 113 | #[test] 114 | fn set() { 115 | let mut sphere = Sphere::::new(1.0); 116 | let radius = 5.5; 117 | sphere.set_radius(radius); 118 | assert_eq!(sphere.get_radius(), 5.5); 119 | } 120 | 121 | #[test] 122 | fn hit() { 123 | let radius = 2.0; 124 | let sphere = Sphere::::new(radius); 125 | 126 | let origin = [-8.0, 0.0, 0.0]; 127 | let direction = [2.0, 0.0, 0.0]; 128 | let ray = Ray::from_array(origin, direction); 129 | let hit = sphere.hit(&ray, 0.0, 100.0); 130 | match hit { 131 | Some(hit) => { 132 | assert_eq!(hit.point.get_data(), [-2.0, 0.0, 0.0]); 133 | assert_eq!(hit.normal.get_data(), [-1.0, 0.0, 0.0]); 134 | assert_eq!(hit.t, 3.0); 135 | }, 136 | None => { 137 | assert!(false); 138 | } 139 | } 140 | 141 | let origin = [-8.0, 2.1, 0.0]; 142 | let direction = [2.0, 0.0, 0.0]; 143 | let ray = Ray::from_array(origin, direction); 144 | let hit = sphere.hit(&ray, 0.0, 100.0); 145 | match hit { 146 | Some(hit) => { 147 | assert!(false); 148 | }, 149 | None => {} 150 | } 151 | 152 | let origin = [-8.0, 0.0, 0.0]; 153 | let direction = [0.0, 2.0, 0.0]; 154 | let ray = Ray::from_array(origin, direction); 155 | let hit = sphere.hit(&ray, 0.0, 100.0); 156 | match hit { 157 | Some(hit) => { 158 | assert!(false); 159 | }, 160 | None => {} 161 | } 162 | } 163 | 164 | #[test] 165 | fn bounds() { 166 | let radius = 2.5; 167 | let sphere = Sphere::::new(radius); 168 | let bounds = sphere.get_bounds(); 169 | assert_eq!(bounds.get_p0().get_data(), [-2.5, -2.5, -2.5]); 170 | assert_eq!(bounds.get_p1().get_data(), [2.5, 2.5, 2.5]); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/hitable/transform/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod translation; 2 | 3 | pub use translation::Translation; -------------------------------------------------------------------------------- /src/hitable/transform/translation.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use crate::ray::Ray; 4 | use crate::hit::Hit; 5 | use crate::hitable::Hitable; 6 | use crate::boundingbox::BoundingBox; 7 | 8 | pub struct Translation 9 | where T: Float 10 | { 11 | translation: Vec3, 12 | wrapped: Box>, 13 | bounds: BoundingBox 14 | } 15 | 16 | impl Translation 17 | where T: Float 18 | { 19 | pub fn new(wrapped: Box>, translation: Vec3) -> Self { 20 | let mut translation = Translation { 21 | wrapped, 22 | translation, 23 | bounds: BoundingBox::::new(Vec3::::new(), Vec3::::new()) 24 | }; 25 | translation.update_bounds(); 26 | translation 27 | } 28 | 29 | fn update_bounds(&mut self) { 30 | let bounds = self.wrapped.get_bounds(); 31 | 32 | let bounds = BoundingBox::new( 33 | bounds.get_p0() + &self.translation, 34 | bounds.get_p1() + &self.translation 35 | ); 36 | self.bounds = bounds; 37 | } 38 | 39 | pub fn compute_bounds(bounds: &BoundingBox, translation: &Vec3) -> BoundingBox { 40 | BoundingBox::new( 41 | bounds.get_p0() + translation, 42 | bounds.get_p1() + translation 43 | ) 44 | } 45 | } 46 | 47 | impl Hitable for Translation 48 | where T: Float 49 | { 50 | fn hit(&self, ray: &Ray, t_min: T, t_max: T) -> Option> { 51 | let origin = ray.get_origin() - &self.translation; 52 | let direction = Vec3::from_slice(ray.get_direction().get_data()); 53 | let translated_ray = Ray::from_vec(origin, direction); 54 | if let Some(mut hit) = self.wrapped.hit(&translated_ray, t_min, t_max) { 55 | hit.point = hit.point + &self.translation; 56 | return Some(hit); 57 | } 58 | None 59 | } 60 | 61 | fn get_bounds(&self) -> &BoundingBox { 62 | &self.bounds 63 | } 64 | 65 | fn unwrap(self: Box) -> Box> { 66 | self.wrapped 67 | } 68 | 69 | fn is_primitive(&self) -> bool { 70 | false 71 | } 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use super::*; 77 | use super::super::super::primitive::Sphere; 78 | 79 | #[test] 80 | fn init() { 81 | let hitable = Box::new(Sphere::new(2.0)); 82 | assert!(hitable.is_primitive()); 83 | 84 | let translation = Vec3::from_array([1.0, 2.0, 3.0]); 85 | let hitable = Translation::new(hitable, translation); 86 | assert!(!hitable.is_primitive()); 87 | } 88 | 89 | #[test] 90 | fn bounds() { 91 | let hitable = Box::new(Sphere::new(2.0)); 92 | { 93 | let bounds = hitable.get_bounds(); 94 | assert_eq!(bounds.get_p0().get_data(), &[-2.0, -2.0, -2.0]); 95 | assert_eq!(bounds.get_p1().get_data(), &[2.0, 2.0, 2.0]); 96 | } 97 | 98 | let translation = Vec3::from_array([1.0, 2.0, 3.0]); 99 | let hitable = Translation::new(hitable, translation); 100 | { 101 | let bounds = hitable.get_bounds(); 102 | assert_eq!(bounds.get_p0().get_data(), &[-1.0, 0.0, 1.0]); 103 | assert_eq!(bounds.get_p1().get_data(), &[3.0, 4.0, 5.0]); 104 | } 105 | } 106 | 107 | #[test] 108 | fn hit() { 109 | let origin = [3.0, 0.0, 10.0]; 110 | let direction = [0.0, 0.0, -1.0]; 111 | let ray = Ray::from_array(origin, direction); 112 | 113 | let hitable = Box::new(Sphere::new(2.0)); 114 | 115 | if let Some(_) = hitable.hit(&ray, 0.0, 1000.0) { 116 | assert!(false); 117 | } 118 | 119 | let translation = Vec3::from_array([3.0, 0.0, 2.0]); 120 | let hitable = Translation::new(hitable, translation); 121 | 122 | match hitable.hit(&ray, 0.0, 1000.0) { 123 | Some(hit) => { 124 | assert_eq!(hit.point.get_data(), &[3.0, 0.0, 4.0]); 125 | assert_eq!(hit.normal.get_data(), &[0.0, 0.0, 1.0]); 126 | assert_eq!(hit.t, 6.0); 127 | }, 128 | None => { 129 | assert!(false); 130 | } 131 | }; 132 | } 133 | 134 | #[test] 135 | fn unwrap() { 136 | let hitable = Box::new(Sphere::new(2.0)); 137 | assert!(hitable.is_primitive()); 138 | 139 | let translation = Vec3::from_array([1.0, 2.0, 3.0]); 140 | let hitable = Box::new(Translation::new(hitable, translation)); 141 | assert!(!hitable.is_primitive()); 142 | 143 | let translation = Vec3::from_array([-1.0, -2.0, -3.0]); 144 | let hitable = Box::new(Translation::new(hitable, translation)); 145 | assert!(!hitable.is_primitive()); 146 | 147 | { 148 | let bounds = hitable.get_bounds(); 149 | assert_eq!(bounds.get_p0().get_data(), &[-2.0, -2.0, -2.0]); 150 | assert_eq!(bounds.get_p1().get_data(), &[2.0, 2.0, 2.0]); 151 | } 152 | 153 | let hitable = hitable.unwrap(); 154 | assert!(!hitable.is_primitive()); 155 | 156 | let hitable = hitable.unwrap(); 157 | assert!(hitable.is_primitive()); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod float; 2 | pub mod vector; 3 | pub mod ray; 4 | pub mod hit; 5 | pub mod hitable; 6 | pub mod camera; 7 | pub mod scene; 8 | pub mod renderer; 9 | pub mod material; 10 | pub mod actor; 11 | pub mod boundingbox; 12 | pub mod texture; 13 | pub mod tree; 14 | pub mod constants; 15 | 16 | mod utils; 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/material/dielectric.rs: -------------------------------------------------------------------------------- 1 | use rand::prelude::*; 2 | 3 | use crate::float::Float; 4 | use crate::vector::Vec3; 5 | use crate::ray::Ray; 6 | use crate::hit::Hit; 7 | use crate::material::{Scatter, Material}; 8 | use crate::utils::refract; 9 | use crate::texture::Texture; 10 | 11 | pub struct DielectricMaterial 12 | where T: Float 13 | { 14 | texture: Box>, 15 | n: T 16 | } 17 | 18 | impl DielectricMaterial 19 | where T: Float 20 | { 21 | pub fn new(texture: Box>, n: T) -> Self { 22 | DielectricMaterial { 23 | texture, 24 | n 25 | } 26 | } 27 | } 28 | 29 | impl Material for DielectricMaterial 30 | where T: Float 31 | { 32 | fn scatter(&self, incident: &Ray, hit: &Hit) -> Scatter { 33 | let mut outward_normal = &hit.normal * (-T::one()); 34 | let mut n0 = self.n; 35 | let mut n1 = T::one(); 36 | let color = self.texture.get_color(T::zero(), T::zero(), &hit.point); 37 | let attenuation = Vec3::::from_slice(color.get_data()); 38 | let c = incident.get_direction().dot(&hit.normal); 39 | 40 | if c < T::zero() { 41 | outward_normal.set_data(hit.normal.get_data()); 42 | n1 = self.n; 43 | n0 = T::one(); 44 | } 45 | 46 | let mut direction = refract(incident.get_direction(), &outward_normal, n0, n1); 47 | let origin = Vec3::from_slice(hit.point.get_data()); 48 | direction.normalize(); 49 | 50 | let scattered = Some(Ray::::from_vec(origin, direction)); 51 | Scatter:: { 52 | attenuation, 53 | scattered 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/material/lambertian.rs: -------------------------------------------------------------------------------- 1 | use rand::prelude::*; 2 | 3 | use crate::float::Float; 4 | use crate::vector::Vec3; 5 | use crate::ray::Ray; 6 | use crate::hit::Hit; 7 | use crate::material::{Scatter, Material}; 8 | use crate::utils::random_point_in_sphere; 9 | use crate::texture::Texture; 10 | 11 | pub struct LambertianMaterial 12 | where T: Float 13 | { 14 | texture: Box>, 15 | dimming: T 16 | } 17 | 18 | impl LambertianMaterial 19 | where T: Float 20 | { 21 | pub fn new(texture: Box>, dimming: T) -> Self { 22 | LambertianMaterial { 23 | texture, 24 | dimming 25 | } 26 | } 27 | } 28 | 29 | impl Material for LambertianMaterial 30 | where T: Float 31 | { 32 | fn scatter(&self, incident: &Ray, hit: &Hit) -> Scatter { 33 | let color = self.texture.get_color(T::zero(), T::zero(), &hit.point); 34 | let attenuation = Vec3::::from_slice(color.get_data()) * self.dimming; 35 | let mut normal = Vec3::from_slice(hit.normal.get_data()); 36 | normal.normalize(); 37 | let origin = Vec3::from_slice(hit.point.get_data()); 38 | let mut direction = normal + random_point_in_sphere(T::one()); 39 | direction.normalize(); 40 | let scattered = Some(Ray::::from_vec(origin, direction)); 41 | Scatter:: { 42 | attenuation, 43 | scattered 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/material/metal.rs: -------------------------------------------------------------------------------- 1 | use rand::prelude::*; 2 | 3 | use crate::float::Float; 4 | use crate::vector::Vec3; 5 | use crate::ray::Ray; 6 | use crate::hit::Hit; 7 | use crate::material::{Scatter, Material}; 8 | use crate::utils::{random_point_in_sphere, reflect}; 9 | use crate::texture::Texture; 10 | 11 | pub struct MetalMaterial 12 | where T: Float 13 | { 14 | texture: Box>, 15 | fuzziness: T 16 | } 17 | 18 | impl MetalMaterial 19 | where T: Float 20 | { 21 | pub fn new(texture: Box>, fuzziness: T) -> Self { 22 | MetalMaterial { 23 | texture, 24 | fuzziness 25 | } 26 | } 27 | } 28 | 29 | impl Material for MetalMaterial 30 | where T: Float 31 | { 32 | fn scatter(&self, incident: &Ray, hit: &Hit) -> Scatter { 33 | let color = self.texture.get_color(T::zero(), T::zero(), &hit.point); 34 | let attenuation = Vec3::::from_slice(color.get_data()); 35 | let normal = &hit.normal; 36 | let origin = Vec3::from_slice(hit.point.get_data()); 37 | let mut direction = reflect(incident.get_direction(), &normal); 38 | direction.normalize(); 39 | if self.fuzziness > T::zero() { 40 | direction = direction + random_point_in_sphere(self.fuzziness); 41 | direction.normalize(); 42 | } 43 | let scattered = Some(Ray::::from_vec(origin, direction)); 44 | Scatter:: { 45 | attenuation, 46 | scattered 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/material/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use crate::ray::Ray; 4 | use crate::hit::Hit; 5 | 6 | pub mod plain; 7 | pub mod lambertian; 8 | pub mod metal; 9 | pub mod dielectric; 10 | 11 | pub struct Scatter 12 | where T: Float 13 | { 14 | pub attenuation: Vec3, 15 | pub scattered: Option> 16 | } 17 | 18 | pub trait Material 19 | where T: Float 20 | { 21 | fn scatter(&self, incident: &Ray, hit: &Hit) -> Scatter; 22 | } 23 | -------------------------------------------------------------------------------- /src/material/plain.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use crate::ray::Ray; 4 | use crate::hit::Hit; 5 | use crate::material::{Scatter, Material}; 6 | use crate::texture::Texture; 7 | 8 | pub struct PlainMaterial 9 | where T: Float 10 | { 11 | texture: Box> 12 | } 13 | 14 | impl PlainMaterial 15 | where T: Float 16 | { 17 | pub fn new(texture: Box>) -> Self { 18 | PlainMaterial { 19 | texture 20 | } 21 | } 22 | } 23 | 24 | impl Material for PlainMaterial 25 | where T: Float 26 | { 27 | fn scatter(&self, _incident: &Ray, hit: &Hit) -> Scatter { 28 | let color = self.texture.get_color(T::zero(), T::zero(), &hit.point); 29 | let attenuation = Vec3::::from_slice(color.get_data()); 30 | Scatter:: { 31 | attenuation, 32 | scattered: None 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ray/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | 4 | #[derive(Debug)] 5 | pub struct Ray 6 | where T: Float 7 | { 8 | origin: Vec3, 9 | direction: Vec3 10 | } 11 | 12 | impl Ray 13 | where T: Float 14 | { 15 | pub fn new() -> Self { 16 | let origin = [T::zero(); 3]; 17 | let direction = [T::zero(); 3]; 18 | Ray { 19 | origin: Vec3::from_array(origin), 20 | direction: Vec3::from_array(direction), 21 | } 22 | } 23 | 24 | pub fn from_vec(origin: Vec3, direction: Vec3) -> Self { 25 | Ray { 26 | origin, 27 | direction 28 | } 29 | } 30 | 31 | pub fn from_array(origin: [T; 3], direction: [T; 3]) -> Self { 32 | Ray { 33 | origin: Vec3::from_array(origin), 34 | direction: Vec3::from_array(direction), 35 | } 36 | } 37 | 38 | pub fn from_slice(origin: &[T], direction: &[T]) -> Self { 39 | Ray { 40 | origin: Vec3::from_slice(origin), 41 | direction: Vec3::from_slice(direction), 42 | } 43 | } 44 | 45 | pub fn from_ray(ray: &Ray) -> Self { 46 | Ray { 47 | origin: Vec3::from_slice(ray.get_origin().get_data()), 48 | direction: Vec3::from_slice(ray.get_direction().get_data()) 49 | } 50 | } 51 | 52 | pub fn get_origin(&self) -> &Vec3 { 53 | &self.origin 54 | } 55 | 56 | pub fn get_direction(&self) -> &Vec3 { 57 | &self.direction 58 | } 59 | 60 | pub fn get_point(&self, t: T) -> Vec3 { 61 | &self.origin + &self.direction * t 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::*; 68 | 69 | #[test] 70 | fn init() { 71 | let ray = Ray::::new(); 72 | assert_eq!(ray.get_origin().get_data(), [0.0, 0.0, 0.0]); 73 | assert_eq!(ray.get_direction().get_data(), [0.0, 0.0, 0.0]); 74 | 75 | let origin = Vec3::::from_array([0.0, -1.0, -2.0]); 76 | let direction = Vec3::::from_array([0.0, 0.0, 1.0]); 77 | let ray = Ray::from_vec(origin, direction); 78 | assert_eq!(ray.get_origin().get_data(), [0.0, -1.0, -2.0]); 79 | assert_eq!(ray.get_direction().get_data(), [0.0, 0.0, 1.0]); 80 | 81 | let origin = [0.0, 1.0, 2.0]; 82 | let direction = [0.0, 0.0, -1.0]; 83 | let ray = Ray::from_array(origin, direction); 84 | assert_eq!(ray.get_origin().get_data(), [0.0, 1.0, 2.0]); 85 | assert_eq!(ray.get_direction().get_data(), [0.0, 0.0, -1.0]); 86 | 87 | let origin = vec!(-1.0, 1.0, 2.0); 88 | let direction = vec!(-1.0, 0.0, -1.0); 89 | let ray = Ray::::from_slice(&origin, &direction); 90 | assert_eq!(ray.get_origin().get_data(), [-1.0, 1.0, 2.0]); 91 | assert_eq!(ray.get_direction().get_data(), [-1.0, 0.0, -1.0]); 92 | } 93 | 94 | #[test] 95 | fn point() { 96 | let origin = [0.0, 1.0, 2.0]; 97 | let direction = [1.0, 2.0, 3.0]; 98 | let ray = Ray::from_array(origin, direction); 99 | let t = -1.5; 100 | let p = ray.get_point(t); 101 | assert_eq!(p.get_data(), [-1.5, -2.0, -2.5]); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/renderer/mod.rs: -------------------------------------------------------------------------------- 1 | use rand::prelude::*; 2 | 3 | use std::u32; 4 | 5 | use crate::float::Float; 6 | use crate::vector::Vec3; 7 | use crate::ray::Ray; 8 | use crate::hitable::Hitable; 9 | use crate::camera::Camera; 10 | use crate::scene::Scene; 11 | 12 | pub struct Image 13 | where T: Float 14 | { 15 | pub width: usize, 16 | pub height: usize, 17 | pub data: Vec 18 | } 19 | 20 | impl Image 21 | where T: Float 22 | { 23 | pub fn new(width: usize, height: usize) -> Self { 24 | let data = vec![T::zero(); 3 * width * height]; 25 | Image:: { 26 | width, 27 | height, 28 | data 29 | } 30 | } 31 | } 32 | 33 | pub struct Renderer { 34 | width: usize, 35 | height: usize, 36 | sampling: usize, 37 | reflections: usize, 38 | antialiasing: bool 39 | } 40 | 41 | impl Renderer { 42 | pub fn new(width: usize, height: usize, sampling: usize, reflections: usize, antialiasing: bool) -> Self { 43 | Renderer { 44 | width, 45 | height, 46 | sampling, 47 | reflections, 48 | antialiasing 49 | } 50 | } 51 | 52 | pub fn render_pixel(&self, i: usize, j: usize, scene: &Scene, camera: &Camera) -> Vec3 53 | where T: Float 54 | { 55 | let two = T::from(2.0).unwrap(); 56 | let mut color = Vec3::::new(); 57 | 58 | let sampling = match self.sampling { 59 | 0 => 1, 60 | _ => self.sampling 61 | }; 62 | 63 | match self.antialiasing { 64 | false => { 65 | let ray = self.get_ray(i, j, camera, two); 66 | for _k in 0..sampling { 67 | color = color + scene.get_color(&ray, 0, self.reflections); 68 | } 69 | }, 70 | true => { 71 | for _k in 0..sampling { 72 | let ray = self.get_ray(i, j, camera, two); 73 | color = color + scene.get_color(&ray, 0, self.reflections); 74 | } 75 | } 76 | } 77 | 78 | let sampling = T::from(sampling).unwrap(); 79 | color = color / sampling; 80 | 81 | color 82 | } 83 | 84 | pub fn render(&self, scene: &Scene, camera: &Camera) -> Image 85 | where T: Float 86 | { 87 | let mut image = Image::::new(self.width, self.height); 88 | for j in 0..self.height { 89 | for i in 0..self.width { 90 | let color = self.render_pixel(i, j, scene, camera); 91 | let index = j * self.width + i; 92 | image.data[3 * index] = color.get_data()[0]; 93 | image.data[3 * index + 1] = color.get_data()[1]; 94 | image.data[3 * index + 2] = color.get_data()[2]; 95 | } 96 | } 97 | image 98 | } 99 | 100 | fn get_ray(&self, i: usize, j: usize, camera: &Camera, two: T) -> Ray 101 | where T: Float 102 | { 103 | let two = T::from(2.0).unwrap(); 104 | 105 | match self.antialiasing { 106 | // If antialiasing is disabled, the ray always hits the pixel in the same position 107 | false => { 108 | let v = two * (T::from(j).unwrap() / T::from(self.height).unwrap()) - T::one(); 109 | let u = two * (T::from(i).unwrap() / T::from(self.width).unwrap()) - T::one(); 110 | camera.get_ray(u, v) 111 | }, 112 | // If antializasing is enabled, the ray is randomly chosen in the vicinity of the pixel 113 | true => { 114 | let i : f64 = (i as f64) + random::(); 115 | let j : f64 = (j as f64) + random::(); 116 | let v = two * (T::from(j).unwrap() / T::from(self.height).unwrap()) - T::one(); 117 | let u = two * (T::from(i).unwrap() / T::from(self.width).unwrap()) - T::one(); 118 | camera.get_ray(u, v) 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/scene/mod.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::float::Float; 4 | use crate::vector::Vec3; 5 | use crate::ray::Ray; 6 | use crate::actor::Actor; 7 | use crate::boundingbox::BoundingBox; 8 | use crate::tree::{Tree, TreeType}; 9 | use crate::tree::linear::LinearTree; 10 | use crate::tree::binary::BinaryTree; 11 | use crate::tree::oct::Octree; 12 | 13 | 14 | pub struct Scene 15 | where T: Float 16 | { 17 | actors: Vec>>, 18 | background: Vec3, 19 | bounds: BoundingBox, 20 | tree: Box>, 21 | tree_type: TreeType 22 | } 23 | 24 | impl Scene 25 | where T: Float 26 | { 27 | pub fn new() -> Self { 28 | Scene { 29 | actors: vec!(), 30 | background: Vec3::::new(), 31 | bounds: BoundingBox::::new(Vec3::::new(), Vec3::::new()), 32 | tree: Box::new(LinearTree::new()), 33 | tree_type: TreeType::Linear 34 | } 35 | } 36 | 37 | pub fn set_background(&mut self, background: Vec3) { 38 | self.background = background; 39 | } 40 | 41 | pub fn add_actor(&mut self, actor: Actor) { 42 | let _expanded = self.bounds.expand(&actor.hitable.get_bounds()); 43 | let actor = Rc::new(actor); 44 | self.actors.push(Rc::clone(&actor)); 45 | let success = self.tree.add_actor(actor); 46 | 47 | if !success { 48 | self.rebuild_tree(); 49 | } 50 | } 51 | 52 | pub fn get_color(&self, ray: &Ray, reflection: usize, max_reflection: usize) -> Vec3 { 53 | let current_hit = self.tree.get_hit(ray, T::from(0.000000001).unwrap(), T::from(10000000000.0).unwrap()); 54 | 55 | match current_hit { 56 | Some((actor, hit)) => { 57 | // let actor = &self.actors[actor_idx]; 58 | let scatter = actor.material.scatter(ray, &hit); 59 | let attenuation = Vec3::::from_slice(scatter.attenuation.get_data()); 60 | let scattered_ray = scatter.scattered; 61 | match scattered_ray { 62 | Some(ray_out) => { 63 | if (reflection < max_reflection) { 64 | return attenuation * self.get_color(&ray_out, reflection + 1, max_reflection); 65 | } else { 66 | return attenuation; 67 | } 68 | }, 69 | None => { 70 | return attenuation; 71 | } 72 | } 73 | }, 74 | None => { 75 | return Vec3::::from_slice(self.background.get_data()); 76 | } 77 | } 78 | } 79 | 80 | pub fn set_tree_type(&mut self, tree_type: TreeType) { 81 | self.tree_type = tree_type; 82 | self.rebuild_tree(); 83 | } 84 | 85 | fn rebuild_tree(&mut self) { 86 | let mut tree: Box> = match self.tree_type { 87 | TreeType::Linear => { 88 | Box::new(LinearTree::new()) 89 | }, 90 | TreeType::Binary => { 91 | Box::new(BinaryTree::new()) 92 | } 93 | TreeType::Oct => { 94 | let mut tree_bounds = BoundingBox::::new( 95 | Vec3::::from_slice(self.bounds.get_p0().get_data()), 96 | Vec3::::from_slice(self.bounds.get_p1().get_data()) 97 | ); 98 | tree_bounds.make_cube(); 99 | let length = tree_bounds.get_axis_length(0); 100 | let pad = length * T::from(0.1).unwrap(); 101 | for i in 0..3 { 102 | tree_bounds.pad_axis(pad, i); 103 | } 104 | Box::new(Octree::::new(tree_bounds)) 105 | } 106 | }; 107 | 108 | for i in 0..self.actors.len() { 109 | let actor = Rc::clone(&self.actors[i]); 110 | tree.add_actor(actor); 111 | } 112 | 113 | self.tree = tree; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/texture/checker.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use super::Texture; 4 | 5 | pub struct CheckerTexture 6 | where T: Float 7 | { 8 | texture0: Box>, 9 | texture1: Box>, 10 | period: Vec3 11 | 12 | } 13 | 14 | impl CheckerTexture 15 | where T: Float 16 | { 17 | pub fn new(texture0: Box>, texture1: Box>) -> Self { 18 | let period = Vec3::::from_array([T::one(), T::one(), T::one()]); 19 | CheckerTexture { 20 | texture0, 21 | texture1, 22 | period 23 | } 24 | } 25 | 26 | pub fn set_period(&mut self, period: Vec3) { 27 | self.period = period; 28 | } 29 | } 30 | 31 | impl Texture for CheckerTexture 32 | where T: Float 33 | { 34 | fn get_color(&self, u: T, v: T, point: &Vec3) -> Vec3 { 35 | let two = T::from(2.0).unwrap(); 36 | let x = T::to_i32(&((point.get_data()[0] / self.period.get_data()[0]).floor())).unwrap().abs() % 2; 37 | let y = T::to_i32(&((point.get_data()[1] / self.period.get_data()[1]).floor())).unwrap().abs() % 2; 38 | let z = T::to_i32(&((point.get_data()[2] / self.period.get_data()[2]).floor())).unwrap().abs() % 2; 39 | let sign = (x * 2 - 1) * (y * 2 - 1) * (z * 2 - 1); 40 | if sign > 0 { 41 | return self.texture0.get_color(u, v, point); 42 | } else { 43 | return self.texture1.get_color(u, v, point); 44 | } 45 | // let y = T::to_i32(&point.get_data()[1]).unwrap(); 46 | // let z = T::to_i32(&point.get_data()[2]).unwrap(); 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/texture/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | 4 | pub mod uniform; 5 | pub mod checker; 6 | 7 | pub trait Texture 8 | where T: Float 9 | { 10 | fn get_color(&self, u: T, v: T, point: &Vec3) -> Vec3; 11 | } 12 | -------------------------------------------------------------------------------- /src/texture/uniform.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use crate::vector::Vec3; 3 | use super::Texture; 4 | 5 | pub struct UniformTexture 6 | where T: Float 7 | { 8 | color: Vec3 9 | } 10 | 11 | impl UniformTexture 12 | where T: Float 13 | { 14 | pub fn new(color: Vec3) -> Self { 15 | UniformTexture { 16 | color 17 | } 18 | } 19 | } 20 | 21 | impl Texture for UniformTexture 22 | where T: Float 23 | { 24 | fn get_color(&self, _u: T, _v: T, _point: &Vec3) -> Vec3 { 25 | Vec3::::from_slice(self.color.get_data()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/tree/binary.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::float::Float; 4 | use crate::vector::Vec3; 5 | use crate::ray::Ray; 6 | use crate::hit::Hit; 7 | use crate::boundingbox::BoundingBox; 8 | use crate::actor::Actor; 9 | use crate::tree::Tree; 10 | 11 | pub struct BinaryTree 12 | where T: Float 13 | { 14 | bounds: BoundingBox, // The bounds of this node 15 | pub children: [Option>>; 2], // The children binary trees 16 | pub actor: Option>> // The actor stored in the leaf nodes 17 | } 18 | 19 | impl BinaryTree 20 | where T: Float 21 | { 22 | pub fn new() -> Self { 23 | let bounds = BoundingBox::::new(Vec3::::new(), Vec3::::new()); 24 | let children: [Option>>; 2] = [None, None]; 25 | let actor = None; 26 | BinaryTree { 27 | bounds, 28 | children, 29 | actor 30 | } 31 | } 32 | 33 | pub fn get_bounds(&self) -> &BoundingBox { 34 | &self.bounds 35 | } 36 | } 37 | 38 | impl Tree for BinaryTree 39 | where T: Float 40 | { 41 | fn add_actor(&mut self, actor: Rc>) -> bool { 42 | let actor_bounds = actor.hitable.get_bounds(); 43 | 44 | // Expand the node bounds so the new actor is guaranteed to fit 45 | self.bounds.expand(&actor_bounds); 46 | 47 | let (left_child, rest) = self.children.split_first_mut().unwrap(); 48 | let (right_child, _) = rest.split_first_mut().unwrap(); 49 | 50 | match (&self.actor, left_child, right_child) { 51 | // If this is a newly created node (i.e. no children, no actor) 52 | // add the actor here and quit 53 | (None, None, None) => { 54 | self.actor = Some(actor); 55 | return true; 56 | }, 57 | // If this node is a leaf node (i.e. has and actor, but no children), 58 | // send the current actor to one child, and send the incoming actor to the other 59 | (Some(current_actor), None, None) => { 60 | let mut left = Box::new(BinaryTree::::new()); 61 | let mut right = Box::new(BinaryTree::::new()); 62 | left.add_actor(Rc::clone(current_actor)); 63 | right.add_actor(actor); 64 | self.actor = None; 65 | self.children[0] = Some(left); 66 | self.children[1] = Some(right); 67 | return true; 68 | }, 69 | // If both children are already initialized, add the actor to the child 70 | // whose bounding box wouldn't expand. 71 | // If they both would expand, add it to the child 72 | // whose resulting bounding box would be smaller. 73 | (None, Some(left), Some(right)) => { 74 | // if left.get_bounds().contains(actor.hitable.get_bounds()) && right.get_bounds().contains(actor.hitable.get_bounds()){ 75 | // if left.get_bounds().get_volume() < right.get_bounds().get_volume() { 76 | // left.add_actor(actor); 77 | // } else { 78 | // right.add_actor(actor); 79 | // } 80 | // return true; 81 | // } else if left.get_bounds().contains(actor.hitable.get_bounds()) { 82 | // left.add_actor(actor); 83 | // return true; 84 | // } else if right.get_bounds().contains(actor.hitable.get_bounds()) { 85 | // right.add_actor(actor); 86 | // return true; 87 | // } 88 | 89 | let mut left_bounds = BoundingBox::new( 90 | Vec3::from_slice(&left.get_bounds().get_p0().get_data()), 91 | Vec3::from_slice(&left.get_bounds().get_p1().get_data()) 92 | ); 93 | left_bounds.expand(&actor_bounds); 94 | 95 | let mut right_bounds = BoundingBox::new( 96 | Vec3::from_slice(&right.get_bounds().get_p0().get_data()), 97 | Vec3::from_slice(&right.get_bounds().get_p1().get_data()) 98 | ); 99 | right_bounds.expand(&actor_bounds); 100 | 101 | if left_bounds.get_volume() < right_bounds.get_volume() { 102 | left.add_actor(actor); 103 | } else { 104 | right.add_actor(actor); 105 | } 106 | return true; 107 | }, 108 | // Other cases should never happen 109 | _ => { 110 | panic!("Something went wrong while adding the actor to the binary tree."); 111 | } 112 | } 113 | } 114 | 115 | fn get_hit(&self, ray: &Ray, t_min: T, t_max: T) -> Option<(Rc>, Hit)> { 116 | if !self.get_bounds().hit(ray, t_min, t_max) { 117 | return None; 118 | } 119 | 120 | let mut t_max = t_max; 121 | let mut result : Option<(Rc>, Hit)> = None; 122 | 123 | if let Some(actor) = &self.actor { 124 | if let Some(hit) = actor.hitable.hit(ray, t_min, t_max) { 125 | t_max = hit.t; 126 | result = Some((Rc::clone(actor), hit)); 127 | } 128 | } 129 | 130 | for i in 0..2 { 131 | if let Some(child) = &self.children[i] { 132 | if let Some((actor, hit)) = child.get_hit(ray, t_min, t_max) { 133 | t_max = hit.t; 134 | result = Some((actor, hit)); 135 | } 136 | } 137 | } 138 | 139 | result 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/tree/linear.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::float::Float; 4 | use crate::vector::Vec3; 5 | use crate::ray::Ray; 6 | use crate::hit::Hit; 7 | use crate::boundingbox::BoundingBox; 8 | use crate::actor::Actor; 9 | use crate::tree::Tree; 10 | 11 | pub struct LinearTree 12 | where T: Float 13 | { 14 | actors: Vec>> 15 | } 16 | 17 | impl LinearTree 18 | where T: Float 19 | { 20 | pub fn new() -> Self { 21 | let actors = Vec::new(); 22 | LinearTree { 23 | actors 24 | } 25 | } 26 | } 27 | 28 | impl Tree for LinearTree 29 | where T: Float 30 | { 31 | fn add_actor(&mut self, actor: Rc>) -> bool { 32 | self.actors.push(actor); 33 | return true; 34 | } 35 | 36 | fn get_hit(&self, ray: &Ray, t_min: T, t_max: T) -> Option<(Rc>, Hit)> { 37 | let mut t_max = t_max; 38 | let mut result : Option<(Rc>, Hit)> = None; 39 | 40 | for i in 0..self.actors.len() { 41 | if let Some(hit) = self.actors[i].hitable.hit(ray, t_min, t_max) { 42 | t_max = hit.t; 43 | result = Some((Rc::clone(&self.actors[i]), hit)); 44 | } 45 | } 46 | 47 | result 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/tree/mod.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::float::Float; 4 | use crate::ray::Ray; 5 | use crate::hit::Hit; 6 | use crate::actor::Actor; 7 | 8 | pub mod linear; 9 | pub mod oct; 10 | pub mod binary; 11 | 12 | pub trait Tree 13 | where T: Float 14 | { 15 | fn add_actor(&mut self, actor: Rc>) -> bool; 16 | 17 | fn get_hit(&self, ray: &Ray, t_min: T, t_max: T) -> Option<(Rc>, Hit)>; 18 | } 19 | 20 | pub enum TreeType { 21 | Linear, 22 | Binary, 23 | Oct 24 | } 25 | -------------------------------------------------------------------------------- /src/tree/oct.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::float::Float; 4 | use crate::vector::Vec3; 5 | use crate::ray::Ray; 6 | use crate::hit::Hit; 7 | use crate::boundingbox::BoundingBox; 8 | use crate::actor::Actor; 9 | use crate::tree::Tree; 10 | 11 | pub struct Octree 12 | where T: Float 13 | { 14 | bounds: BoundingBox, // The bounds of this node 15 | pub children: [Option>>; 8], // The children octrees 16 | pub actors: Vec>> // The actors that are too large to be placed in the children nodes 17 | } 18 | 19 | impl Octree 20 | where T: Float 21 | { 22 | pub fn new(bounds: BoundingBox) -> Self { 23 | let children: [Option>>; 8] = [None, None, None, None, None, None, None, None]; 24 | let actors = Vec::new(); 25 | Octree { 26 | bounds, 27 | children, 28 | actors 29 | } 30 | } 31 | 32 | pub fn get_bounds(&self) -> &BoundingBox { 33 | &self.bounds 34 | } 35 | 36 | fn get_child_bounds(&self, child_index: usize) -> BoundingBox { 37 | let k = child_index % 2; 38 | let j = (child_index / 2) % 2; 39 | let i = (child_index / 4) % 2; 40 | 41 | let k = T::from(k).unwrap(); 42 | let j = T::from(j).unwrap(); 43 | let i = T::from(i).unwrap(); 44 | 45 | let half = T::from(0.5).unwrap(); 46 | 47 | let (min_x, max_x) = self.get_bounds().get_axis_bounds(0); 48 | let (min_y, max_y) = self.get_bounds().get_axis_bounds(1); 49 | let (min_z, max_z) = self.get_bounds().get_axis_bounds(2); 50 | let len = [half * (max_x - min_x), half * (max_y - min_y), half * (max_z - min_z)]; 51 | 52 | let offset = Vec3::::from_array([len[0] * i, len[1] * j, len[2] * k]); 53 | 54 | let p0 = Vec3::::from_array([min_x, min_y, min_z]) + &offset; 55 | let p1 = &p0 + Vec3::from_slice(&len); 56 | BoundingBox::::new(p0, p1) 57 | } 58 | } 59 | 60 | impl Tree for Octree 61 | where T: Float 62 | { 63 | fn add_actor(&mut self, actor: Rc>) -> bool { 64 | let actor_bounds = actor.hitable.get_bounds(); 65 | 66 | // If this node can't fully contain the actor, do nothing 67 | if !self.bounds.contains(actor_bounds) { 68 | return false; 69 | } 70 | 71 | // If one of the current/potential children of this node can fully 72 | // contain the actor, add it to that node 73 | for i in 0..8 { 74 | let child = &mut self.children[i]; 75 | match child { 76 | Some(node) => { 77 | if node.get_bounds().contains(actor_bounds) { 78 | node.add_actor(actor); 79 | return true; 80 | } 81 | }, 82 | None => { 83 | let child_bounds = self.get_child_bounds(i); 84 | if child_bounds.contains(actor_bounds) { 85 | let mut node = Octree::::new(child_bounds); 86 | node.add_actor(actor); 87 | self.children[i] = Some(Box::new(node)); 88 | return true; 89 | } 90 | } 91 | } 92 | } 93 | 94 | // If the actor is too large for any of the children, add it to this node. 95 | self.actors.push(actor); 96 | return true; 97 | } 98 | 99 | fn get_hit(&self, ray: &Ray, t_min: T, t_max: T) -> Option<(Rc>, Hit)> { 100 | if !self.get_bounds().hit(ray, t_min, t_max) { 101 | return None; 102 | } 103 | 104 | let mut t_max = t_max; 105 | let mut result : Option<(Rc>, Hit)> = None; 106 | 107 | for i in 0..self.actors.len() { 108 | if let Some(hit) = self.actors[i].hitable.hit(ray, t_min, t_max) { 109 | t_max = hit.t; 110 | result = Some((Rc::clone(&self.actors[i]), hit)); 111 | } 112 | } 113 | 114 | for i in 0..8 { 115 | if let Some(child) = &self.children[i] { 116 | if let Some((actor, hit)) = child.get_hit(ray, t_min, t_max) { 117 | t_max = hit.t; 118 | result = Some((actor, hit)); 119 | } 120 | } 121 | } 122 | 123 | result 124 | } 125 | } 126 | 127 | #[cfg(test)] 128 | mod tests { 129 | use super::*; 130 | 131 | #[test] 132 | fn child_bounds() { 133 | let p0 = Vec3::::from_array([0.,0.,0.]); 134 | let p1 = Vec3::::from_array([4.,6.,8.]); 135 | let bounds = BoundingBox::new(p0, p1); 136 | let node = Octree::new(bounds); 137 | 138 | let mut children_bounds = Vec::>::new(); 139 | 140 | // Ensure children are contained in the parent node 141 | for i in 0..8 { 142 | let child_bounds = node.get_child_bounds(i); 143 | assert!(node.get_bounds().contains(&child_bounds)); 144 | children_bounds.push(child_bounds); 145 | } 146 | 147 | // Ensure there is no overlap across children 148 | for i in 0..7 { 149 | for j in i + 1..8 { 150 | assert!(!children_bounds[i].overlaps(&children_bounds[j])); 151 | } 152 | } 153 | 154 | // Ensure children and parent span the same volume 155 | let mut children_volume = 0.0; 156 | for i in 0..8 { 157 | children_volume += children_bounds[i].get_volume(); 158 | } 159 | assert_eq!(node.get_bounds().get_volume(), children_volume); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use rand::prelude::*; 2 | 3 | use std::u32; 4 | 5 | use crate::float::Float; 6 | use crate::vector::Vec3; 7 | use crate::constants::Axis; 8 | 9 | pub fn random() -> T 10 | where T : Float 11 | { 12 | let mut rng = rand::rngs::OsRng::new().unwrap(); 13 | T::from(rng.next_u32()).unwrap() / T::from(u32::MAX).unwrap() 14 | } 15 | 16 | pub fn random_point_in_sphere(radius: T) -> Vec3 17 | where T: Float 18 | { 19 | let mut point = Vec3::::new(); 20 | let two = T::from(2.0).unwrap(); 21 | 22 | loop { 23 | let x = random::() * two - T::one(); 24 | let y = random::() * two - T::one(); 25 | let z = random::() * two - T::one(); 26 | 27 | let len = (x * x + y * y + z * z).sqrt(); 28 | 29 | if len < T::one() { 30 | point.set_data(&[x * radius, y * radius, z * radius]); 31 | break; 32 | } 33 | } 34 | 35 | point 36 | } 37 | 38 | pub fn random_point_in_circle(radius: T) -> Vec3 39 | where T: Float 40 | { 41 | let mut point = Vec3::::new(); 42 | let two = T::from(2.0).unwrap(); 43 | 44 | loop { 45 | let x = random::() * two - T::one(); 46 | let y = random::() * two - T::one(); 47 | 48 | let len = (x * x + y * y).sqrt(); 49 | 50 | if len < T::one() { 51 | point.set_data(&[x * radius, y * radius, T::zero()]); 52 | break; 53 | } 54 | } 55 | 56 | point 57 | } 58 | 59 | pub fn reflect(direction: &Vec3, normal: &Vec3) -> Vec3 60 | where T: Float 61 | { 62 | let two = T::from(2.0).unwrap(); 63 | let c = direction.dot(normal); 64 | let reflection = direction - normal * two * c; 65 | reflection 66 | } 67 | 68 | pub fn refract(direction: &Vec3, normal: &Vec3, n0: T, n1: T) -> Vec3 69 | where T: Float 70 | { 71 | let ratio = n0 / n1; 72 | let c = direction.dot(normal); 73 | let discriminant = T::one() - ratio * ratio * (T::one() - c * c); 74 | if discriminant > T::zero() { 75 | let prob = reflection_probability(direction, normal, n0); 76 | if random::() < prob { 77 | return reflect(direction, normal); 78 | } 79 | return (direction - normal * c) * ratio - normal * discriminant.sqrt(); 80 | } else { 81 | return reflect(direction, normal); 82 | } 83 | } 84 | 85 | pub fn reflection_probability(direction: &Vec3, normal: &Vec3, n: T) -> T 86 | where T: Float 87 | { 88 | let cosine = - n * direction.dot(normal); 89 | let mut r0 = (T::one() - n) / (T::one() + n); 90 | r0 = r0 * r0; 91 | let mut pow5 = T::one() - cosine; 92 | pow5 = pow5 * pow5 * pow5 * pow5 * pow5; 93 | r0 + (T::one() - r0) * pow5 94 | } 95 | 96 | pub fn axis_to_index(axis: &Axis) -> usize { 97 | match axis { 98 | Axis::X => 0, 99 | Axis::Y => 1, 100 | Axis::Z => 2 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/vector/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::float::Float; 2 | use std::ops; 3 | 4 | #[derive(Debug)] 5 | pub struct Vec3 6 | where T: Float 7 | { 8 | data: [T; 3] 9 | } 10 | 11 | impl Vec3 12 | where T: Float 13 | { 14 | pub fn new() -> Self { 15 | let data = [T::zero(); 3]; 16 | Vec3{data} 17 | } 18 | 19 | pub fn from_array(input: [T; 3]) -> Self { 20 | Vec3{data: input} 21 | } 22 | 23 | pub fn from_slice(input: &[T]) -> Self { 24 | assert!(input.len() >= 3); 25 | let mut data = [T::zero(); 3]; 26 | for i in 0..3 { 27 | data[i] = input[i]; 28 | } 29 | Vec3{data} 30 | } 31 | 32 | pub fn get_data(&self) -> &[T] { 33 | &self.data 34 | } 35 | 36 | pub fn get_data_mut(&mut self) -> &mut [T] { 37 | &mut self.data 38 | } 39 | 40 | pub fn set_data(&mut self, data: &[T]) { 41 | assert!(data.len() >= 3); 42 | for i in 0..3 { 43 | self.data[i] = data[i]; 44 | } 45 | } 46 | 47 | fn add(this: &Vec3, other: &Vec3, result: &mut Vec3) { 48 | let data = result.get_data_mut(); 49 | for i in 0..3 { 50 | data[i] = this.data[i] + other.data[i]; 51 | } 52 | } 53 | 54 | fn sub(this: &Vec3, other: &Vec3, result: &mut Vec3) { 55 | let data = result.get_data_mut(); 56 | for i in 0..3 { 57 | data[i] = this.data[i] - other.data[i]; 58 | } 59 | } 60 | 61 | fn mul_vec(this: &Vec3, other: &Vec3, result: &mut Vec3) { 62 | let data = result.get_data_mut(); 63 | for i in 0..3 { 64 | data[i] = this.data[i] * other.data[i]; 65 | } 66 | } 67 | 68 | fn mul(this: &Vec3, other: T, result: &mut Vec3) { 69 | let data = result.get_data_mut(); 70 | for i in 0..3 { 71 | data[i] = this.data[i] * other; 72 | } 73 | } 74 | 75 | fn div(this: &Vec3, other: T, result: &mut Vec3) { 76 | let data = result.get_data_mut(); 77 | for i in 0..3 { 78 | data[i] = this.data[i] / other; 79 | } 80 | } 81 | 82 | pub fn dot(&self, other: &Vec3) -> T { 83 | let mut result = T::zero(); 84 | for i in 0..3 { 85 | result = result + self.data[i] * other.data[i]; 86 | } 87 | result 88 | } 89 | 90 | pub fn cross(&self, other: &Vec3) -> Vec3 { 91 | let mut result = [T::zero(); 3]; 92 | for i in 0..3 { 93 | let j = (i + 1) % 3; 94 | let k = (i + 2) % 3; 95 | result[i] = self.get_data()[j] * other.get_data()[k] - self.get_data()[k] * other.get_data()[j]; 96 | } 97 | Vec3::from_array(result) 98 | } 99 | 100 | pub fn norm(&self) -> T { 101 | let n2 = self.dot(self); 102 | n2.sqrt() 103 | } 104 | 105 | pub fn normalize(&mut self) { 106 | let n = self.norm(); 107 | for i in 0..3 { 108 | self.data[i] = self.data[i] / n; 109 | } 110 | } 111 | } 112 | 113 | // Vec3 + Vec3 114 | impl ops::Add> for Vec3 115 | where T: Float 116 | { 117 | type Output = Vec3; 118 | 119 | fn add(self, other: Vec3) -> Vec3 { 120 | let mut result = Vec3::::new(); 121 | Vec3::::add(&self, &other, &mut result); 122 | result 123 | } 124 | } 125 | 126 | // Vec3 + &Vec3 127 | impl ops::Add<&Vec3> for Vec3 128 | where T: Float 129 | { 130 | type Output = Vec3; 131 | 132 | fn add(self, other: &Vec3) -> Vec3 { 133 | let mut result = Vec3::::new(); 134 | Vec3::::add(&self, other, &mut result); 135 | result 136 | } 137 | } 138 | 139 | // &Vec3 + Vec3 140 | impl ops::Add> for &Vec3 141 | where T: Float 142 | { 143 | type Output = Vec3; 144 | 145 | fn add(self, other: Vec3) -> Vec3 { 146 | let mut result = Vec3::::new(); 147 | Vec3::::add(self, &other, &mut result); 148 | result 149 | } 150 | } 151 | 152 | // &Vec3 + &Vec3 153 | impl ops::Add<&Vec3> for &Vec3 154 | where T: Float 155 | { 156 | type Output = Vec3; 157 | 158 | fn add(self, other: &Vec3) -> Vec3 { 159 | let mut result = Vec3::::new(); 160 | Vec3::::add(self, other, &mut result); 161 | result 162 | } 163 | } 164 | 165 | // Vec3 - Vec3 166 | impl ops::Sub> for Vec3 167 | where T: Float 168 | { 169 | type Output = Vec3; 170 | 171 | fn sub(self, other: Vec3) -> Vec3 { 172 | let mut result = Vec3::::new(); 173 | Vec3::::sub(&self, &other, &mut result); 174 | result 175 | } 176 | } 177 | 178 | // Vec3 - &Vec3 179 | impl ops::Sub<&Vec3> for Vec3 180 | where T: Float 181 | { 182 | type Output = Vec3; 183 | 184 | fn sub(self, other: &Vec3) -> Vec3 { 185 | let mut result = Vec3::::new(); 186 | Vec3::::sub(&self, other, &mut result); 187 | result 188 | } 189 | } 190 | 191 | // &Vec3 - Vec3 192 | impl ops::Sub> for &Vec3 193 | where T: Float 194 | { 195 | type Output = Vec3; 196 | 197 | fn sub(self, other: Vec3) -> Vec3 { 198 | let mut result = Vec3::::new(); 199 | Vec3::::sub(self, &other, &mut result); 200 | result 201 | } 202 | } 203 | 204 | // &Vec3 - &Vec3 205 | impl ops::Sub<&Vec3> for &Vec3 206 | where T: Float 207 | { 208 | type Output = Vec3; 209 | 210 | fn sub(self, other: &Vec3) -> Vec3 { 211 | let mut result = Vec3::::new(); 212 | Vec3::::sub(self, other, &mut result); 213 | result 214 | } 215 | } 216 | 217 | // Vec3 * Vec3 (multiply each item) 218 | impl ops::Mul> for Vec3 219 | where T: Float 220 | { 221 | type Output = Vec3; 222 | 223 | fn mul(self, other: Vec3) -> Vec3 { 224 | let mut result = Vec3::::new(); 225 | Vec3::::mul_vec(&self, &other, &mut result); 226 | result 227 | } 228 | } 229 | 230 | // Vec3 * &Vec3 231 | impl ops::Mul<&Vec3> for Vec3 232 | where T: Float 233 | { 234 | type Output = Vec3; 235 | 236 | fn mul(self, other: &Vec3) -> Vec3 { 237 | let mut result = Vec3::::new(); 238 | Vec3::::mul_vec(&self, other, &mut result); 239 | result 240 | } 241 | } 242 | 243 | // &Vec3 * Vec3 244 | impl ops::Mul> for &Vec3 245 | where T: Float 246 | { 247 | type Output = Vec3; 248 | 249 | fn mul(self, other: Vec3) -> Vec3 { 250 | let mut result = Vec3::::new(); 251 | Vec3::::mul_vec(self, &other, &mut result); 252 | result 253 | } 254 | } 255 | 256 | // &Vec3 * &Vec3 257 | impl ops::Mul<&Vec3> for &Vec3 258 | where T: Float 259 | { 260 | type Output = Vec3; 261 | 262 | fn mul(self, other: &Vec3) -> Vec3 { 263 | let mut result = Vec3::::new(); 264 | Vec3::::mul_vec(self, other, &mut result); 265 | result 266 | } 267 | } 268 | 269 | // Vec3 * T 270 | impl ops::Mul for Vec3 271 | where T: Float 272 | { 273 | type Output = Vec3; 274 | 275 | fn mul(self, other: T) -> Vec3 { 276 | let mut result = Vec3::::new(); 277 | Vec3::::mul(&self, other, &mut result); 278 | result 279 | } 280 | } 281 | 282 | // T * Vec3 283 | impl ops::Mul> for f64 284 | { 285 | type Output = Vec3; 286 | 287 | fn mul(self, other: Vec3) -> Vec3 { 288 | let mut result = Vec3::new(); 289 | Vec3::mul(&other, self, &mut result); 290 | result 291 | } 292 | } 293 | 294 | // T * Vec3 295 | impl ops::Mul> for f32 296 | { 297 | type Output = Vec3; 298 | 299 | fn mul(self, other: Vec3) -> Vec3 { 300 | let mut result = Vec3::new(); 301 | Vec3::mul(&other, self, &mut result); 302 | result 303 | } 304 | } 305 | 306 | // &Vec3 * T 307 | impl ops::Mul for &Vec3 308 | where T: Float 309 | { 310 | type Output = Vec3; 311 | 312 | fn mul(self, other: T) -> Vec3 { 313 | let mut result = Vec3::::new(); 314 | Vec3::::mul(self, other, &mut result); 315 | result 316 | } 317 | } 318 | 319 | // T * &Vec3 320 | impl ops::Mul<&Vec3> for f64 321 | { 322 | type Output = Vec3; 323 | 324 | fn mul(self, other: &Vec3) -> Vec3 { 325 | let mut result = Vec3::new(); 326 | Vec3::mul(other, self, &mut result); 327 | result 328 | } 329 | } 330 | 331 | // T * &Vec3 332 | impl ops::Mul<&Vec3> for f32 333 | { 334 | type Output = Vec3; 335 | 336 | fn mul(self, other: &Vec3) -> Vec3 { 337 | let mut result = Vec3::new(); 338 | Vec3::mul(other, self, &mut result); 339 | result 340 | } 341 | } 342 | 343 | // Vec3 / T 344 | impl ops::Div for Vec3 345 | where T: Float 346 | { 347 | type Output = Vec3; 348 | 349 | fn div(self, other: T) -> Vec3 { 350 | let mut result = Vec3::::new(); 351 | Vec3::::div(&self, other, &mut result); 352 | result 353 | } 354 | } 355 | 356 | // &Vec3 / T 357 | impl ops::Div for &Vec3 358 | where T: Float 359 | { 360 | type Output = Vec3; 361 | 362 | fn div(self, other: T) -> Vec3 { 363 | let mut result = Vec3::::new(); 364 | Vec3::::div(self, other, &mut result); 365 | result 366 | } 367 | } 368 | 369 | #[cfg(test)] 370 | mod tests { 371 | use super::*; 372 | 373 | #[test] 374 | fn init() { 375 | let v = Vec3::::new(); 376 | assert_eq!(v.get_data(), [0.0, 0.0, 0.0]); 377 | 378 | let data = vec!(1.0, 2.0, 3.0); 379 | let v = Vec3::::from_slice(&data); 380 | assert_eq!(v.get_data(), [1.0, 2.0, 3.0]); 381 | 382 | let data = [4.0, 5.0, 6.0]; 383 | let v = Vec3::::from_array(data); 384 | assert_eq!(v.get_data(), [4.0, 5.0, 6.0]); 385 | } 386 | 387 | #[test] 388 | fn add() { 389 | let data = vec!(1.0, 2.0, 3.0); 390 | let v1 = Vec3::::from_slice(&data); 391 | let data = vec!(2.0, 4.0, 6.0); 392 | let v2 = Vec3::::from_slice(&data); 393 | 394 | let v3 = &v1 + &v2; 395 | assert_eq!(v3.get_data(), [3.0, 6.0, 9.0]); 396 | 397 | let mut v4 = &v1 + &v2 + &v3; 398 | assert_eq!(v4.get_data(), [6.0, 12.0, 18.0]); 399 | 400 | v4 = &v1 + (&v2 + &v3); 401 | assert_eq!(v4.get_data(), [6.0, 12.0, 18.0]); 402 | 403 | v4 = (&v1 + &v2) + &v3; 404 | assert_eq!(v4.get_data(), [6.0, 12.0, 18.0]); 405 | 406 | v4 = &v1 + (&v2 + &v3) + &v1; 407 | assert_eq!(v4.get_data(), [7.0, 14.0, 21.0]); 408 | 409 | v4 = (&v1 + &v2) + (&v3 + &v1); 410 | assert_eq!(v4.get_data(), [7.0, 14.0, 21.0]); 411 | } 412 | 413 | #[test] 414 | fn sub() { 415 | let data = vec!(1.0, 2.0, 3.0); 416 | let v1 = Vec3::::from_slice(&data); 417 | let data = vec!(2.0, 4.0, 6.0); 418 | let v2 = Vec3::::from_slice(&data); 419 | 420 | let v3 = &v1 - &v2; 421 | assert_eq!(v3.get_data(), [-1.0, -2.0, -3.0]); 422 | 423 | let mut v4 = &v1 - &v2 - &v3; 424 | assert_eq!(v4.get_data(), [0.0, 0.0, 0.0]); 425 | 426 | v4 = &v1 - (&v2 - &v3); 427 | assert_eq!(v4.get_data(), [-2.0, -4.0, -6.0]); 428 | 429 | v4 = (&v1 - &v2) - &v3; 430 | assert_eq!(v4.get_data(), [0.0, 0.0, 0.0]); 431 | 432 | v4 = &v1 - (&v2 - &v3) - &v1; 433 | assert_eq!(v4.get_data(), [-3.0, -6.0, -9.0]); 434 | 435 | v4 = (&v1 - &v2) - (&v3 - &v1); 436 | assert_eq!(v4.get_data(), [1.0, 2.0, 3.0]); 437 | } 438 | 439 | #[test] 440 | fn mul() { 441 | let data = vec!(1.0, 2.0, 3.0); 442 | let v1 = Vec3::::from_slice(&data); 443 | let data = vec!(2.0, 4.0, 6.0); 444 | let v2 = Vec3::::from_slice(&data); 445 | 446 | let mut v3 = 3.0 * &v1; 447 | assert_eq!(v3.get_data(), [3.0, 6.0, 9.0]); 448 | 449 | v3 = &v1 * 3.0; 450 | assert_eq!(v3.get_data(), [3.0, 6.0, 9.0]); 451 | 452 | v3 = (&v1 * 2.0) * 3.0; 453 | assert_eq!(v3.get_data(), [6.0, 12.0, 18.0]); 454 | 455 | v3 = 3.0 * (&v1 * 2.0); 456 | assert_eq!(v3.get_data(), [6.0, 12.0, 18.0]); 457 | 458 | let mut v4 = &v2 * 4.0; 459 | assert_eq!(v4.get_data(), [8.0, 16.0, 24.0]); 460 | 461 | v4 = 4.0 * &v2; 462 | assert_eq!(v4.get_data(), [8.0, 16.0, 24.0]); 463 | 464 | v4 = (&v2 * 2.0) * 4.0; 465 | assert_eq!(v4.get_data(), [16.0, 32.0, 48.0]); 466 | 467 | v4 = 4.0 * (&v2 * 2.0); 468 | assert_eq!(v4.get_data(), [16.0, 32.0, 48.0]); 469 | } 470 | 471 | #[test] 472 | fn div() { 473 | let data = vec!(1.0, 2.0, 3.0); 474 | let v1 = Vec3::::from_slice(&data); 475 | let data = vec!(2.0, 4.0, 6.0); 476 | let v2 = Vec3::::from_slice(&data); 477 | 478 | let mut v3 = &v1 / 2.0; 479 | assert_eq!(v3.get_data(), [0.5, 1.0, 1.5]); 480 | 481 | v3 = &v2 / 4.0; 482 | assert_eq!(v3.get_data(), [0.5, 1.0, 1.5]); 483 | 484 | v3 = (&v2 / 2.0) / 2.0; 485 | assert_eq!(v3.get_data(), [0.5, 1.0, 1.5]); 486 | } 487 | 488 | #[test] 489 | fn ops() { 490 | let data = vec!(1.0, 2.0, 3.0); 491 | let v1 = Vec3::::from_slice(&data); 492 | let data = vec!(2.0, 4.0, 6.0); 493 | let v2 = Vec3::::from_slice(&data); 494 | 495 | let mut v3 = 4.0 * (&v1 * 2.0 + &v2 - &v1) / 2.0; 496 | assert_eq!(v3.get_data(), [6.0, 12.0, 18.0]); 497 | 498 | v3 = 4.0 * (&v1 * 2.0 + &v2 - &v1) * 2.0 / 2.0; 499 | assert_eq!(v3.get_data(), [12.0, 24.0, 36.0]); 500 | } 501 | 502 | #[test] 503 | fn dot() { 504 | let data = vec!(1.0, 2.0, 3.0); 505 | let v1 = Vec3::::from_slice(&data); 506 | let data = vec!(2.0, 4.0, 6.0); 507 | let v2 = Vec3::::from_slice(&data); 508 | 509 | let mut d = v1.dot(&v1); 510 | assert_eq!(d, 14.0); 511 | 512 | d = v2.dot(&v2); 513 | assert_eq!(d, 56.0); 514 | 515 | d = v1.dot(&v2); 516 | assert_eq!(d, 28.0); 517 | 518 | d = v2.dot(&v1); 519 | assert_eq!(d, 28.0); 520 | 521 | d = (&v1 * 0.5).dot(&v2); 522 | assert_eq!(d, 14.0); 523 | 524 | d = v1.dot(&(&v2 / 2.0)); 525 | assert_eq!(d, 14.0); 526 | } 527 | 528 | #[test] 529 | fn cross() { 530 | let data = vec!(1.0, 0.0, 0.0); 531 | let v1 = Vec3::::from_slice(&data); 532 | let data = vec!(0.0, 1.0, 0.0); 533 | let v2 = Vec3::::from_slice(&data); 534 | let v3 = v1.cross(&v2); 535 | assert_eq!(v3.get_data(), [0.0, 0.0, 1.0]); 536 | 537 | let data = vec!(7.0, 3.0, -4.0); 538 | let v1 = Vec3::::from_slice(&data); 539 | let data = vec!(1.0, 0.0, 6.0); 540 | let v2 = Vec3::::from_slice(&data); 541 | let v3 = v1.cross(&v2); 542 | assert_eq!(v3.get_data(), [18.0, -46.0, -3.0]); 543 | } 544 | 545 | #[test] 546 | fn norm() { 547 | let data = vec!(1.0, 2.0, 2.0); 548 | let mut v1 = Vec3::::from_slice(&data); 549 | assert_eq!(v1.norm(), 3.0); 550 | v1.normalize(); 551 | assert_eq!(v1.norm(), 1.0); 552 | } 553 | 554 | #[test] 555 | fn set() { 556 | let mut v = Vec3::::new(); 557 | assert_eq!(v.get_data(), [0.0, 0.0, 0.0]); 558 | let data = [1.0, 2.0, 3.0]; 559 | v.set_data(&data); 560 | assert_eq!(v.get_data(), [1.0, 2.0, 3.0]); 561 | } 562 | } 563 | -------------------------------------------------------------------------------- /tests/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::OpenOptions; 2 | use std::io::prelude::*; 3 | use rand::prelude::*; 4 | use std::time::Instant; 5 | 6 | use ray_tracer::vector::Vec3; 7 | use ray_tracer::scene::Scene; 8 | use ray_tracer::hitable::Hitable; 9 | use ray_tracer::hitable::primitive::Sphere; 10 | use ray_tracer::hitable::primitive::Rectangle; 11 | use ray_tracer::hitable::primitive::Cube; 12 | use ray_tracer::hitable::primitive::Group; 13 | use ray_tracer::hitable::transform::Translation; 14 | use ray_tracer::camera::Camera; 15 | use ray_tracer::camera::perspective::PerspectiveCamera; 16 | use ray_tracer::renderer::Renderer; 17 | use ray_tracer::renderer::Image; 18 | use ray_tracer::material::Material; 19 | use ray_tracer::material::plain::PlainMaterial; 20 | use ray_tracer::material::lambertian::LambertianMaterial; 21 | use ray_tracer::material::metal::MetalMaterial; 22 | use ray_tracer::material::dielectric::DielectricMaterial; 23 | use ray_tracer::actor::Actor; 24 | use ray_tracer::tree::TreeType; 25 | use ray_tracer::texture::uniform::UniformTexture; 26 | use ray_tracer::texture::checker::CheckerTexture; 27 | use ray_tracer::constants::Axis; 28 | 29 | const MULT : usize = 40; 30 | const SAMPLING : usize = 4; 31 | const REFLECTIONS : usize = 8; 32 | 33 | fn to_u8(f: f64) -> u8 { 34 | (f * 255.0) as u8 35 | } 36 | 37 | fn mix_images(image: &mut Image, delta: &Image, iteration: usize) { 38 | assert_eq!(delta.height, image.height); 39 | assert_eq!(delta.width, image.width); 40 | 41 | let frac_delta = 1.0 / (iteration + 1) as f64; 42 | let frac_image = 1.0 - frac_delta; 43 | for j in 0..image.height { 44 | for i in 0..image.width { 45 | let index = j * image.width + i; 46 | image.data[3 * index] = frac_image * image.data[3 * index] + frac_delta * delta.data[3 * index]; 47 | image.data[3 * index + 1] = frac_image * image.data[3 * index + 1] + frac_delta * delta.data[3 * index + 1]; 48 | image.data[3 * index + 2] = frac_image * image.data[3 * index + 2] + frac_delta * delta.data[3 * index + 2]; 49 | } 50 | } 51 | } 52 | 53 | fn image_diff(reference: &Image, image: &Image) -> f64 { 54 | assert_eq!(reference.height, image.height); 55 | assert_eq!(reference.width, image.width); 56 | 57 | let mut diff = 0.0; 58 | for j in 0..image.height { 59 | for i in 0..image.width { 60 | let index = j * image.width + i; 61 | let ref_color = Vec3::from_array([reference.data[3 * index], reference.data[3 * index + 1], reference.data[3 * index + 2]]); 62 | let image_color = Vec3::from_array([image.data[3 * index], image.data[3 * index + 1], image.data[3 * index + 2]]); 63 | diff += (ref_color - image_color).norm(); 64 | } 65 | } 66 | diff 67 | } 68 | 69 | fn print_ppm(image: &Image, gamma: f64, filename: &str) { 70 | let mut file = OpenOptions::new() 71 | .write(true) 72 | .append(false) 73 | .create(true) 74 | .open(filename) 75 | .unwrap(); 76 | 77 | if let Err(e) = writeln!(file, "P3\n# asd\n{} {}\n255", image.width, image.height) { 78 | eprintln!("Couldn't write to file: {}", e); 79 | } 80 | 81 | for j in 0..image.height { 82 | for i in 0..image.width { 83 | let index = j * image.width + i; 84 | if let Err(e) = writeln!( 85 | file, "{} {} {}", 86 | to_u8(image.data[3 * index].powf(1.0 / gamma)), 87 | to_u8(image.data[3 * index + 1].powf(1.0 / gamma)), 88 | to_u8(image.data[3 * index + 2].powf(1.0 / gamma)) 89 | ) { 90 | eprintln!("Couldn't write to file: {}", e); 91 | } 92 | } 93 | } 94 | } 95 | 96 | fn create_rectangle_room(length: f64, width: f64, height: f64, light: f64) -> Vec> { 97 | let mut actors = vec![]; 98 | 99 | let dimming = 1.0; 100 | 101 | // Rectangle used as light 102 | let width_axis = Axis::X; 103 | let height_axis = Axis::Y; 104 | // let hitable = Box::new(Rectangle::new(light, width_axis, light, height_axis)); 105 | let hitable = Box::new(Cube::new(light, light, 0.125 * light)); 106 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, width / 4.0, height / 2.0]))); 107 | let texture = Box::new(UniformTexture::new(Vec3::from_array([1.0, 1.0, 1.0]))); 108 | let material = Box::new(PlainMaterial::::new(texture)); 109 | let actor = Actor:: { hitable, material}; 110 | actors.push(actor); 111 | 112 | // Rectangle used as floor 113 | let width_axis = Axis::X; 114 | let height_axis = Axis::Y; 115 | let hitable = Box::new(Rectangle::new(length, width_axis, width, height_axis)); 116 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, 0.0, -height / 2.0]))); 117 | let texture0 = Box::new(UniformTexture::new(Vec3::from_array([1.0, 1.0, 1.0]))); 118 | let texture1 = Box::new(UniformTexture::new(Vec3::from_array([0.8, 0.8, 0.8]))); 119 | let mut texture = Box::new(CheckerTexture::new(texture0, texture1)); 120 | texture.set_period(Vec3::from_array([length / 8.0, length / 8.0, 1.0])); 121 | let material = Box::new(LambertianMaterial::::new(texture, dimming)); 122 | let actor = Actor:: { hitable, material}; 123 | actors.push(actor); 124 | 125 | // Rectangle used as front wall 126 | let width_axis = Axis::X; 127 | let height_axis = Axis::Z; 128 | let rectangle = Box::new(Rectangle::new(length, width_axis, height, height_axis)); 129 | let rectangle = Box::new(Translation::new(rectangle, Vec3::from_array([0.0, width / 2.0, 0.0]))); 130 | let texture = Box::new(UniformTexture::new(Vec3::from_array([1.0, 1.0, 1.0]))); 131 | let material = Box::new(LambertianMaterial::::new(texture, dimming)); 132 | let actor = Actor:: { hitable: rectangle, material}; 133 | actors.push(actor); 134 | 135 | // Rectangle used as back wall 136 | let width_axis = Axis::Z; 137 | let height_axis = Axis::X; 138 | let rectangle = Box::new(Rectangle::new(height, width_axis, length, height_axis)); 139 | let rectangle = Box::new(Translation::new(rectangle, Vec3::from_array([0.0, - width / 2.0, 0.0]))); 140 | let texture = Box::new(UniformTexture::new(Vec3::from_array([1.0, 1.0, 1.0]))); 141 | let material = Box::new(LambertianMaterial::::new(texture, dimming)); 142 | let actor = Actor:: { hitable: rectangle, material}; 143 | // actors.push(actor); 144 | 145 | // Rectangle used as left wall 146 | let width_axis = Axis::Y; 147 | let height_axis = Axis::Z; 148 | let rectangle = Box::new(Rectangle::new(width, width_axis, height, height_axis)); 149 | let rectangle = Box::new(Translation::new(rectangle, Vec3::from_array([-length / 2.0, 0.0, 0.0]))); 150 | let texture = Box::new(UniformTexture::new(Vec3::from_array([0.1, 1.0, 0.1]))); 151 | let material = Box::new(LambertianMaterial::::new(texture, dimming)); 152 | let actor = Actor:: { hitable: rectangle, material}; 153 | actors.push(actor); 154 | 155 | // Rectangle used as right wall 156 | let width_axis = Axis::Z; 157 | let height_axis = Axis::Y; 158 | let rectangle = Box::new(Rectangle::new(height, width_axis, width, height_axis)); 159 | let rectangle = Box::new(Translation::new(rectangle, Vec3::from_array([length / 2.0, 0.0, 0.0]))); 160 | let texture = Box::new(UniformTexture::new(Vec3::from_array([1.0, 0.1, 0.1]))); 161 | let material = Box::new(LambertianMaterial::::new(texture, dimming)); 162 | let actor = Actor:: { hitable: rectangle, material}; 163 | actors.push(actor); 164 | 165 | // Rectangle used as ceiling 166 | let width_axis = Axis::Y; 167 | let height_axis = Axis::X; 168 | let rectangle = Box::new(Rectangle::new(width, width_axis, length, height_axis)); 169 | let rectangle = Box::new(Translation::new(rectangle, Vec3::from_array([0.0, 0.0, height / 2.0]))); 170 | let texture = Box::new(UniformTexture::new(Vec3::from_array([1.0, 1.0, 1.0]))); 171 | let material = Box::new(LambertianMaterial::::new(texture, dimming)); 172 | let actor = Actor:: { hitable: rectangle, material}; 173 | actors.push(actor); 174 | 175 | actors 176 | } 177 | 178 | fn create_cube_box(length: f64, width: f64, height: f64, thickness: f64) -> Box> { 179 | let mut group : Box> = Box::new(Group::::new()); 180 | 181 | // cube used as floor 182 | let hitable = Box::new(Cube::new(length, width, thickness)); 183 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, 0.0, -height / 2.0]))); 184 | group.add_hitable(hitable); 185 | 186 | // cube used as ceiling 187 | let hitable = Box::new(Cube::new(length, width, thickness)); 188 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, 0.0, height / 2.0]))); 189 | group.add_hitable(hitable); 190 | 191 | // cube used as left wall 192 | let hitable = Box::new(Cube::new(thickness, width, height)); 193 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([- length / 2.0, 0.0, 0.0]))); 194 | group.add_hitable(hitable); 195 | 196 | // cube used as right wall 197 | let hitable = Box::new(Cube::new(thickness, width, height)); 198 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([length / 2.0, 0.0, 0.0]))); 199 | group.add_hitable(hitable); 200 | 201 | // cube used as back wall 202 | let hitable = Box::new(Cube::new(length, thickness, height)); 203 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, width / 2.0, 0.0]))); 204 | // group.add_hitable(hitable); 205 | 206 | group 207 | } 208 | 209 | #[test] 210 | fn rectangle_room() { 211 | let room_length = 16.0; 212 | let room_width = 16.0; 213 | let room_height = 9.0; 214 | let mut actors = create_rectangle_room(room_length, room_width, room_height, 6.5); 215 | 216 | let mut scene = Scene::::new(); 217 | // scene.set_background(Vec3::from_array([0.1, 0.1, 0.1])); 218 | 219 | loop { 220 | let actor = actors.pop(); 221 | match actor { 222 | Some(actor) => { 223 | scene.add_actor(actor); 224 | }, 225 | None => { 226 | break; 227 | } 228 | } 229 | }; 230 | 231 | let box_size = 4.0; 232 | let box_thickness = 0.05 * box_size; 233 | let hitable = create_cube_box(box_size, box_size, box_size, box_thickness); 234 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([- 0.3 * room_length, 0.3 * room_width, - 0.5 * room_height + 0.5 * box_size]))); 235 | let texture = Box::new(UniformTexture::new(Vec3::from_array([0.2, 0.2, 1.0]))); 236 | let material = Box::new(LambertianMaterial::::new(texture, 1.0)); 237 | let actor = Actor {hitable, material}; 238 | scene.add_actor(actor); 239 | 240 | // cube used as front glass wall 241 | let hitable = Box::new(Cube::new(box_size, box_thickness, box_size)); 242 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, - box_size / 2.0, 0.0]))); 243 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([- 0.3 * room_length, 0.3 * room_width, - 0.5 * room_height + 0.5 * box_size]))); 244 | let texture = Box::new(UniformTexture::new(Vec3::from_array([1.0, 1.0, 1.0]))); 245 | let material = Box::new(DielectricMaterial::::new(texture, 1.6)); 246 | let actor = Actor {hitable, material}; 247 | scene.add_actor(actor); 248 | 249 | // cube used as back glass wall 250 | let hitable = Box::new(Cube::new(box_size, box_thickness, box_size)); 251 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, box_size / 2.0, 0.0]))); 252 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([- 0.3 * room_length, 0.3 * room_width, - 0.5 * room_height + 0.5 * box_size]))); 253 | let texture = Box::new(UniformTexture::new(Vec3::from_array([1.0, 1.0, 1.0]))); 254 | let material = Box::new(DielectricMaterial::::new(texture, 1.6)); 255 | let actor = Actor {hitable, material}; 256 | scene.add_actor(actor); 257 | 258 | let sphere_size = 1.0; 259 | let hitable = Box::new(Sphere::new(sphere_size)); 260 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([- 0.3 * room_length, 0.3 * room_width, - 0.5 * room_height + 0.5 * box_size]))); 261 | let texture = Box::new(UniformTexture::new(Vec3::from_array([1.0, 0.2, 0.2]))); 262 | let material = Box::new(LambertianMaterial::::new(texture, 1.0)); 263 | let actor = Actor {hitable, material}; 264 | scene.add_actor(actor); 265 | 266 | // Large glass sphere in the front 267 | let sphere_size = 3.0; 268 | let hitable = Box::new(Sphere::new(sphere_size)); 269 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, 0.1 * room_width, - 0.5 * room_height + sphere_size]))); 270 | let texture = Box::new(UniformTexture::new(Vec3::from_array([1.0, 1.0, 1.0]))); 271 | let material = Box::new(DielectricMaterial::::new(texture, 2.4)); 272 | let actor = Actor {hitable, material}; 273 | scene.add_actor(actor); 274 | 275 | // Large metal sphere in the front; 276 | let sphere_size = 2.0; 277 | let hitable = Box::new(Sphere::new(sphere_size)); 278 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.3 * room_length, 0.3 * room_width, - 0.5 * room_height + sphere_size]))); 279 | let texture = Box::new(UniformTexture::new(Vec3::from_array([0.9, 0.9, 0.9]))); 280 | let material = Box::new(MetalMaterial::::new(texture, 0.0)); 281 | let actor = Actor {hitable, material}; 282 | scene.add_actor(actor); 283 | 284 | let mul = 120; 285 | let width = 12 * mul; 286 | let height = 8 * mul; 287 | let aspect = width as f64 / height as f64; 288 | let mut camera = PerspectiveCamera::::new(); 289 | camera.set_aspect(aspect); 290 | camera.set_fov(0.37 * std::f64::consts::PI); 291 | camera.set_position(&[0.0, - 0.49 * room_width, 0.0]); 292 | camera.set_direction(&[0.0, 1.0, 0.0]); 293 | // camera.set_lookat(&[0.0, 0.0, 0.0]); 294 | camera.set_up(&[0.0, 0.0, 1.0]); 295 | camera.set_fov(0.3 * std::f64::consts::PI); 296 | camera.set_focus(1.0); 297 | 298 | scene.set_tree_type(TreeType::Oct); 299 | 300 | let renderer = Renderer::new(width, height, 0, 0, false); 301 | let image = renderer.render(&mut scene, &camera); 302 | let gamma = 2.0; 303 | print_ppm(&image, gamma, "rectangle_room_preview.ppm"); 304 | 305 | let gamma = 2.6; 306 | let renderer = Renderer::new(width, height, 1, 32, false); 307 | let sampling = 1024; 308 | let mut image = Image::new(width, height); 309 | for i in 0..sampling { 310 | let delta = renderer.render(&scene, &camera); 311 | mix_images(&mut image, &delta, i); 312 | print_ppm(&image, gamma, "rectangle_room.ppm"); 313 | } 314 | } 315 | 316 | #[test] 317 | fn cube_scene() { 318 | let mut scene = Scene::::new(); 319 | scene.set_background(Vec3::from_array([0.2, 0.2, 0.7])); 320 | 321 | let room_size = 15.0; 322 | let light_size = 2.0 * room_size / 3.0; 323 | 324 | // Rectangle used as floor 325 | let width_axis = Axis::X; 326 | let height_axis = Axis::Y; 327 | let hitable = Box::new(Rectangle::new(room_size, width_axis, room_size, height_axis)); 328 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, 0.0, -room_size / 2.0]))); 329 | let texture0 = Box::new(UniformTexture::new(Vec3::from_array([0.9, 0.9, 0.9]))); 330 | let texture1 = Box::new(UniformTexture::new(Vec3::from_array([0.75, 0.75, 0.75]))); 331 | let texture = Box::new(CheckerTexture::new(texture0, texture1)); 332 | let material = Box::new(LambertianMaterial::::new(texture, 0.65)); 333 | let actor = Actor:: { hitable, material}; 334 | scene.add_actor(actor); 335 | 336 | // Box on the floor 337 | let length = 6.0; 338 | let width = 3.0; 339 | let heigth = 5.0; 340 | let hitable = Box::new(Cube::new(length, width, heigth)); 341 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([4.0, room_size / 3.0, -room_size / 2.0]))); 342 | let texture = Box::new(UniformTexture::new(Vec3::from_array([0.0, 1.0, 0.0]))); 343 | let material = Box::new(LambertianMaterial::::new(texture, 0.65)); 344 | let actor = Actor:: { hitable, material}; 345 | scene.add_actor(actor); 346 | 347 | // Rectangle used as light 348 | let width_axis = Axis::X; 349 | let height_axis = Axis::Y; 350 | let hitable = Box::new(Rectangle::new(light_size, width_axis, light_size, height_axis)); 351 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, 0.0, room_size / 2.0]))); 352 | let texture = Box::new(UniformTexture::new(Vec3::from_array([2.0, 2.0, 2.0]))); 353 | let material = Box::new(PlainMaterial::::new(texture)); 354 | let actor = Actor:: { hitable, material}; 355 | scene.add_actor(actor); 356 | 357 | let mul = 40; 358 | let width = 12 * mul; 359 | let height = 8 * mul; 360 | let aspect = width as f64 / height as f64; 361 | let mut camera = PerspectiveCamera::::new(); 362 | camera.set_aspect(aspect); 363 | camera.set_fov(0.35 * std::f64::consts::PI); 364 | camera.set_position(&[0.0, - 0.5 * room_size, 0.0]); 365 | camera.set_direction(&[0.0, 1.0, 0.0]); 366 | // camera.set_lookat(&[0.0, 0.0, 0.0]); 367 | camera.set_up(&[0.0, 0.0, 1.0]); 368 | camera.set_fov(0.4 * std::f64::consts::PI); 369 | camera.set_focus(1.0); 370 | 371 | scene.set_tree_type(TreeType::Oct); 372 | 373 | let renderer = Renderer::new(width, height, 0, 0, false); 374 | let image = renderer.render(&mut scene, &camera); 375 | let gamma = 2.0; 376 | print_ppm(&image, gamma, "cube_scene_preview.ppm"); 377 | 378 | let renderer = Renderer::new(width, height, 32, 8, false); 379 | let image = renderer.render(&mut scene, &camera); 380 | print_ppm(&image, gamma, "cube_scene.ppm"); 381 | } 382 | 383 | #[test] 384 | fn basic_scene() { 385 | let mut scene = Scene::::new(); 386 | scene.set_background(Vec3::from_array([0.2, 0.2, 0.7])); 387 | scene.set_background(Vec3::from_array([0.75, 0.75, 0.75])); 388 | 389 | let r = 1.0; 390 | let sphere = Box::new(Sphere::::new(r)); 391 | let sphere = Translation::new(sphere, Vec3::from_array([0.0, r, -4.0])); 392 | let texture = UniformTexture::new(Vec3::from_array([1.0, 0.2, 0.2])); 393 | let material = LambertianMaterial::::new(Box::new(texture), 0.5); 394 | let actor = Actor:: { hitable: Box::new(sphere), material: Box::new(material)}; 395 | scene.add_actor(actor); 396 | } 397 | 398 | #[test] 399 | fn sphere_in_box() { 400 | let mut scene = Scene::::new(); 401 | scene.set_background(Vec3::from_array([0.2, 0.2, 0.8])); 402 | 403 | let box_size = 5.0; 404 | let box_thickness = 0.05 * box_size; 405 | let hitable = create_cube_box(box_size, box_size, box_size, box_thickness); 406 | let texture = Box::new(UniformTexture::new(Vec3::from_array([0.9, 0.9, 0.9]))); 407 | let material = Box::new(LambertianMaterial::::new(texture, 0.75)); 408 | let actor = Actor {hitable, material}; 409 | scene.add_actor(actor); 410 | 411 | // cube used as front glass wall 412 | let hitable = Box::new(Cube::new(box_size, box_thickness, box_size)); 413 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, - box_size / 2.0, 0.0]))); 414 | let texture = Box::new(UniformTexture::new(Vec3::from_array([1.0, 1.0, 1.0]))); 415 | let material = Box::new(DielectricMaterial::::new(texture, 1.5)); 416 | let actor = Actor {hitable, material}; 417 | scene.add_actor(actor); 418 | 419 | let sphere_size = 1.0; 420 | let hitable = Box::new(Sphere::new(sphere_size)); 421 | let texture = Box::new(UniformTexture::new(Vec3::from_array([1.0, 0.2, 0.2]))); 422 | let material = Box::new(LambertianMaterial::::new(texture, 0.65)); 423 | let actor = Actor {hitable, material}; 424 | scene.add_actor(actor); 425 | 426 | // Light 427 | let sphere_size = 3.0; 428 | let hitable = Box::new(Sphere::new(sphere_size)); 429 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, - 2.5 * box_size + sphere_size + 0.1, 0.0]))); 430 | let texture = Box::new(UniformTexture::new(Vec3::from_array([2.0, 2.0, 2.0]))); 431 | let material = Box::new(PlainMaterial::::new(texture)); 432 | let actor = Actor {hitable, material}; 433 | scene.add_actor(actor); 434 | 435 | // Light 436 | let sphere_size = 3.0; 437 | let hitable = Box::new(Sphere::new(sphere_size)); 438 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([- 2.5 * box_size + sphere_size + 0.1, 0.0, 0.0]))); 439 | let texture = Box::new(UniformTexture::new(Vec3::from_array([2.0, 2.0, 2.0]))); 440 | let material = Box::new(PlainMaterial::::new(texture)); 441 | let actor = Actor {hitable, material}; 442 | scene.add_actor(actor); 443 | 444 | let mul = 40; 445 | let width = 12 * mul; 446 | let height = 8 * mul; 447 | let aspect = width as f64 / height as f64; 448 | let mut camera = PerspectiveCamera::::new(); 449 | camera.set_aspect(aspect); 450 | camera.set_fov(0.35 * std::f64::consts::PI); 451 | camera.set_position(&[-4.0, - 1.5 * box_size, 0.0]); 452 | // camera.set_direction(&[0.0, 1.0, 0.0]); 453 | camera.set_lookat(&[0.0, 0.0, 0.0]); 454 | camera.set_up(&[0.0, 0.0, 1.0]); 455 | camera.set_fov(0.4 * std::f64::consts::PI); 456 | camera.set_focus(1.0); 457 | 458 | scene.set_tree_type(TreeType::Oct); 459 | 460 | let renderer = Renderer::new(width, height, 0, 0, false); 461 | let image = renderer.render(&mut scene, &camera); 462 | let gamma = 2.0; 463 | print_ppm(&image, gamma, "sphere_in_box_preview.ppm"); 464 | 465 | let renderer = Renderer::new(width, height, 1, 8, false); 466 | let sampling = 128; 467 | let mut image = Image::new(width, height); 468 | for i in 0..sampling { 469 | let delta = renderer.render(&scene, &camera); 470 | mix_images(&mut image, &delta, i); 471 | print_ppm(&image, gamma, "sphere_in_box.ppm"); 472 | } 473 | } 474 | 475 | #[test] 476 | fn random_scene() { 477 | let mut scene = Scene::::new(); 478 | // scene.set_background(Vec3::from_array([0.2, 0.2, 0.7])); 479 | scene.set_background(Vec3::from_array([0.5, 0.7, 0.9])); 480 | 481 | const N_SPHERES_X : usize = 20; 482 | const N_SPHERES_Y : usize = N_SPHERES_X; 483 | 484 | const MIN_X : f64 = -20.0; 485 | const MAX_X : f64 = 20.0; 486 | 487 | const MIN_Y : f64 = MIN_X; 488 | const MAX_Y : f64 = MAX_X; 489 | 490 | const MIN_RADIUS : f64 = 0.2; 491 | const MAX_RADIUS : f64 = 0.4; 492 | 493 | const SPHERE_PROBABILITY : f64 = 0.66666666; 494 | 495 | const LAMBERTIAN_PROBABILITY : f64 = 0.3333; 496 | const METAL_PROBABILITY : f64 = 0.3333; 497 | // DIELECTRIC_PROBABILITY is 1 - LAMBERTIAN_PROBABILITY - METAL_PROBABILITY 498 | 499 | const MIN_FUZZINESS : f64 = 0.0; 500 | const MAX_FUZZINESS : f64 = 0.4; 501 | 502 | const MIN_REFRACTIVE : f64 = 1.2; 503 | const MAX_REFRACTIVE : f64 = 2.4; 504 | 505 | let mut rng = rand::thread_rng(); 506 | 507 | for i in 0..N_SPHERES_X { 508 | for j in 0..N_SPHERES_Y { 509 | let radius = MIN_RADIUS + (MAX_RADIUS - MIN_RADIUS) * rng.gen::(); 510 | let mut x = i as f64 + rng.gen::() * (1.0 - radius); 511 | x = MIN_X + (MAX_X - MIN_X) * x / N_SPHERES_X as f64; 512 | let mut y = j as f64 + rng.gen::() * (1.0 - radius); 513 | y = MIN_Y + (MAX_Y - MIN_Y) * y / N_SPHERES_Y as f64; 514 | 515 | let hitable_select = rng.gen::(); 516 | let hitable : Box> = if hitable_select < SPHERE_PROBABILITY { 517 | let hitable = Box::new(Sphere::::new(radius)); 518 | Box::new(Translation::new(hitable, Vec3::from_array([x, y, radius]))) 519 | } else { 520 | let l = radius * 2.0 * 0.8; 521 | let hitable = Box::new(Cube::::new(l, l, l)); 522 | Box::new(Translation::new(hitable, Vec3::from_array([x, y, radius * 0.8]))) 523 | }; 524 | 525 | let color = Vec3::from_array([rng.gen::(), rng.gen::(), rng.gen::()]); 526 | let texture = Box::new(UniformTexture::new(color)); 527 | let material_select = rng.gen::(); 528 | let material : Box> = if material_select < LAMBERTIAN_PROBABILITY { 529 | Box::new(LambertianMaterial::::new(texture, 0.5)) 530 | } else if material_select < LAMBERTIAN_PROBABILITY + METAL_PROBABILITY { 531 | let fuzziness = MIN_FUZZINESS + (MAX_FUZZINESS - MIN_FUZZINESS) * rng.gen::(); 532 | Box::new(MetalMaterial::::new(texture, fuzziness)) 533 | } else { 534 | let n = MIN_REFRACTIVE + (MAX_REFRACTIVE - MIN_REFRACTIVE) * rng.gen::(); 535 | Box::new(DielectricMaterial::::new(texture, n)) 536 | }; 537 | let actor = Actor:: { hitable, material}; 538 | scene.add_actor(actor); 539 | } 540 | } 541 | 542 | // Three larger spheres in the center 543 | let radius = 2.0; 544 | let sphere = Box::new(Sphere::::new(radius)); 545 | let sphere = Translation::new(sphere, Vec3::from_array([0.0, 0.0, radius])); 546 | let color = Vec3::from_array([0.78, 1.0, 0.78]); 547 | let texture = Box::new(UniformTexture::new(color)); 548 | let material = DielectricMaterial::::new(texture, 2.4); 549 | let actor = Actor:: { hitable: Box::new(sphere), material: Box::new(material)}; 550 | scene.add_actor(actor); 551 | 552 | let sphere = Box::new(Sphere::::new(radius)); 553 | let sphere = Translation::new(sphere, Vec3::from_array([0.0, - 2.0 * radius, radius])); 554 | let color = Vec3::from_array([0.9, 0.9, 0.9]); 555 | let texture = Box::new(UniformTexture::new(color)); 556 | let material = MetalMaterial::::new(texture, 0.0); 557 | let actor = Actor:: { hitable: Box::new(sphere), material: Box::new(material)}; 558 | scene.add_actor(actor); 559 | 560 | let sphere = Box::new(Sphere::::new(radius)); 561 | let sphere = Translation::new(sphere, Vec3::from_array([0.0, 2.0 * radius, radius])); 562 | let color = Vec3::from_array([1.0, 0.15, 0.15]); 563 | let texture = Box::new(UniformTexture::new(color)); 564 | let material = MetalMaterial::::new(texture, 0.1); 565 | let actor = Actor:: { hitable: Box::new(sphere), material: Box::new(material)}; 566 | scene.add_actor(actor); 567 | 568 | // Sphere used as light 569 | let radius = 4.0; 570 | let sphere = Box::new(Sphere::::new(radius)); 571 | let sphere = Translation::new(sphere, Vec3::from_array([0.0, 1.0, 12.5])); 572 | let color = Vec3::from_array([1.0, 1.0, 1.0]); 573 | let texture = Box::new(UniformTexture::new(color)); 574 | let material = PlainMaterial::::new(texture); 575 | let actor = Actor:: { hitable: Box::new(sphere), material: Box::new(material)}; 576 | scene.add_actor(actor); 577 | 578 | // Rectangle used as floor 579 | let length = 2000.0; 580 | let color0 = Vec3::from_array([1.0, 1.0, 1.0]); 581 | let color1 = Vec3::from_array([0.8, 0.8, 0.8]); 582 | let texture0 = UniformTexture::new(color0); 583 | let texture1 = UniformTexture::new(color1); 584 | let texture = Box::new(CheckerTexture::new(Box::new(texture0), Box::new(texture1))); 585 | 586 | let hitable = Box::new(Rectangle::::new(length, Axis::X, length, Axis::Y)); 587 | let hitable = Box::new(Translation::new(hitable, Vec3::from_array([0.0, 0.0, -radius]))); 588 | let material = Box::new(LambertianMaterial::::new(texture, 0.75)); 589 | let actor = Actor:: { hitable, material }; 590 | scene.add_actor(actor); 591 | 592 | let mul = 120; 593 | let width = 16 * mul; 594 | let height = 9 * mul; 595 | let aspect = width as f64 / height as f64; 596 | let mut camera = PerspectiveCamera::::new(); 597 | camera.set_aspect(aspect); 598 | camera.set_fov(0.25 * std::f64::consts::PI); 599 | camera.set_position(&[-6.0, -10.0, 3.0]); 600 | camera.set_lookat(&[0.0, 0.0, 2.0]); 601 | camera.set_up(&[0.0, 0.0, 1.0]); 602 | 603 | // camera.set_position(&[0.0, 0.0, 20.0]); 604 | // camera.set_lookat(&[0.0, 0.0, 0.0]); 605 | // camera.set_up(&[0.0, 1.0, 0.0]); 606 | 607 | camera.set_aperture(0.0); 608 | let focus = (camera.get_lookat() - camera.get_position()).norm(); 609 | camera.set_focus(focus); 610 | 611 | scene.set_tree_type(TreeType::Oct); 612 | 613 | let renderer = Renderer::new(width/4, height/4, 0, 2, false); 614 | let image = renderer.render(&mut scene, &camera); 615 | let gamma = 2.0; 616 | print_ppm(&image, gamma, "random_scene_preview.ppm"); 617 | 618 | let mut image = Image::new(width, height); 619 | let renderer = Renderer::new(width, height, 1, 16, false); 620 | let sampling = 1024; 621 | for i in 0..sampling { 622 | let delta = renderer.render(&scene, &camera); 623 | mix_images(&mut image, &delta, i); 624 | print_ppm(&image, gamma, "random_scene.ppm"); 625 | } 626 | } 627 | 628 | #[test] 629 | fn tree() { 630 | let mut scene = Scene::::new(); 631 | scene.set_background(Vec3::from_array([0.6, 0.8, 1.0])); 632 | 633 | const N_SPHERES_X : usize = 10; 634 | const N_SPHERES_Y : usize = N_SPHERES_X; 635 | const N_SPHERES_Z : usize = N_SPHERES_X; 636 | 637 | const MIN_X : f64 = -20.0; 638 | const MAX_X : f64 = 20.0; 639 | 640 | const MIN_Y : f64 = MIN_X; 641 | const MAX_Y : f64 = MAX_X; 642 | 643 | const MIN_Z : f64 = MIN_X; 644 | const MAX_Z : f64 = MAX_X; 645 | 646 | const MIN_RADIUS : f64 = 0.2; 647 | const MAX_RADIUS : f64 = 1.0; 648 | 649 | let mut rng = rand::thread_rng(); 650 | 651 | for i in 0..N_SPHERES_X { 652 | for j in 0..N_SPHERES_Y { 653 | for k in 0..N_SPHERES_Z { 654 | let radius = MIN_RADIUS + (MAX_RADIUS - MIN_RADIUS) * rng.gen::(); 655 | let mut x = i as f64 + rng.gen::() * (1.0 - radius); 656 | x = MIN_X + (MAX_X - MIN_X) * x / N_SPHERES_X as f64; 657 | let mut y = j as f64 + rng.gen::() * (1.0 - radius); 658 | y = MIN_Y + (MAX_Y - MIN_Y) * y / N_SPHERES_Y as f64; 659 | let mut z = k as f64 + rng.gen::() * (1.0 - radius); 660 | z = MIN_Z + (MAX_Z - MIN_Z) * z / N_SPHERES_Z as f64; 661 | 662 | let sphere = Box::new(Sphere::::new(radius)); 663 | let sphere = Translation::new(sphere, Vec3::from_array([x, y, z])); 664 | 665 | let color = Vec3::from_array([rng.gen::(), rng.gen::(), rng.gen::()]); 666 | let texture = Box::new(UniformTexture::new(color)); 667 | let material : Box> = Box::new(MetalMaterial::new(texture, 0.0)); 668 | 669 | let actor = Actor:: { hitable: Box::new(sphere), material}; 670 | scene.add_actor(actor); 671 | } 672 | } 673 | } 674 | 675 | let mul = 8; 676 | let width = 16 * mul; 677 | let height = 9 * mul; 678 | let aspect = width as f64 / height as f64; 679 | let mut camera = PerspectiveCamera::::new(); 680 | camera.set_aspect(aspect); 681 | camera.set_fov(0.3 * std::f64::consts::PI); 682 | camera.set_position(&[-6.0, -10.0, 3.0]); 683 | camera.set_lookat(&[0.0, 0.0, 2.0]); 684 | camera.set_up(&[0.0, 0.0, 1.0]); 685 | 686 | camera.set_aperture(0.0); 687 | let focus = (camera.get_lookat() - camera.get_position()).norm(); 688 | camera.set_focus(focus); 689 | 690 | let renderer = Renderer::new(width, height, 0, 0, false); 691 | 692 | scene.set_tree_type(TreeType::Linear); 693 | let now = Instant::now(); 694 | let image_linear = renderer.render(&scene, &camera); 695 | let t_linear = now.elapsed().as_millis(); 696 | // println!("Linear: {}", t_linear); 697 | 698 | scene.set_tree_type(TreeType::Binary); 699 | let now = Instant::now(); 700 | let image_binary = renderer.render(&mut scene, &camera); 701 | let t_binary = now.elapsed().as_millis(); 702 | let diff = image_diff(&image_linear, &image_binary); 703 | assert!(t_binary < t_linear); 704 | assert_eq!(diff, 0.0); 705 | // println!("Binary - t: {} diff: {}", t_binary, diff); 706 | 707 | scene.set_tree_type(TreeType::Oct); 708 | let now = Instant::now(); 709 | let image_oct = renderer.render(&scene, &camera); 710 | let t_oct = now.elapsed().as_millis(); 711 | let diff = image_diff(&image_linear, &image_oct); 712 | assert!(t_oct < t_linear); 713 | assert_eq!(diff, 0.0); 714 | // println!("Oct - t: {} diff: {}", t_oct, diff); 715 | } 716 | --------------------------------------------------------------------------------