├── .travis.yml ├── .idea ├── .gitignore ├── encodings.xml ├── vcs.xml ├── modules.xml ├── path-tracer.iml ├── xtextAutoBuilderState.xml ├── libraries │ ├── Rust__path_tracer_.xml │ └── Cargo__path_tracer_.xml └── misc.xml ├── .gitignore ├── src ├── material.rs ├── renderable.rs ├── scene.rs ├── geometry.rs ├── bin │ ├── merge.rs │ └── path_tracer.rs ├── math.rs └── lib.rs ├── README.md ├── Cargo.toml ├── path-tracer.iml ├── LICENSE └── Cargo.lock /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | workspace.xml 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | image.png 3 | image.part 4 | core 5 | *.swp 6 | image.ppm 7 | -------------------------------------------------------------------------------- /src/material.rs: -------------------------------------------------------------------------------- 1 | pub enum Material { 2 | Diffuse, 3 | Specular, 4 | Refractive 5 | } 6 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A [path tracer](http://en.wikipedia.org/wiki/Path_tracing), based on [smallpt](http://www.kevinbeason.com/smallpt/), written in [Rust](http://www.rust-lang.org). It's my first Rust program, so be gentle on me. 2 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/path-tracer.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "path_tracer" 3 | version = "0.1.0" 4 | description = "Path tracer" 5 | license = "MIT" 6 | authors = ["Matt Godbolt "] 7 | repository = "https://github.com/mattgodbolt/path-tracer.git" 8 | readme = "README.md" 9 | 10 | [dependencies] 11 | argparse = "*" 12 | image = "*" 13 | num_cpus = "*" 14 | rand = "*" 15 | threadpool = "*" 16 | 17 | [profile.release] 18 | lto = true 19 | -------------------------------------------------------------------------------- /src/renderable.rs: -------------------------------------------------------------------------------- 1 | use geometry::Ray; 2 | use material::Material; 3 | use math::{Vec3d, F64Rng}; 4 | 5 | pub struct Hit<'a> { 6 | pub pos: Vec3d, 7 | pub normal: Vec3d, 8 | pub material: &'a Material, 9 | pub emission: Vec3d, 10 | pub colour: Vec3d 11 | } 12 | 13 | pub trait Renderable: Send + Sync { 14 | fn intersect(&self, ray: &Ray) -> Option; 15 | fn get_hit(&self, ray: &Ray, dist: f64) -> Hit; 16 | fn is_emissive(&self) -> bool; 17 | fn random_emission(&self, from: Vec3d, normal: Vec3d, rng: &mut F64Rng) -> (Vec3d, Vec3d); 18 | fn identity(&self) -> u64; 19 | } 20 | -------------------------------------------------------------------------------- /.idea/xtextAutoBuilderState.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | xtend;xtext 5 | rO0ABXNyADtvcmcuZWNsaXBzZS54dGV4dC5yZXNvdXJjZS5pbXBsLkNodW5rZWRSZXNvdXJjZURlc2NyaXB0aW9uc4nX+GBmyGixDAAAeHB3FQAAAAEAC3BhdGgtdHJhY2VyAAAAAHg= 6 | rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABdAALcGF0aC10cmFjZXJzcgAvb3JnLmVjbGlwc2UueHRleHQuYnVpbGQuU291cmNlMkdlbmVyYXRlZE1hcHBpbmdgwc1G7ZGGygwAAHhwdwQAAAAAeHg= 7 | 8 | -------------------------------------------------------------------------------- /.idea/libraries/Rust__path_tracer_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /path-tracer.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Matt Godbolt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Android 12 | 13 | 14 | Android > Lint > Correctness 15 | 16 | 17 | Android > Lint > Performance 18 | 19 | 20 | CorrectnessLintAndroid 21 | 22 | 23 | GSPGrailsGroovy 24 | 25 | 26 | General 27 | 28 | 29 | GrailsGroovy 30 | 31 | 32 | Groovy 33 | 34 | 35 | Kotlin 36 | 37 | 38 | LintAndroid 39 | 40 | 41 | Maven 42 | 43 | 44 | Plugin DevKit 45 | 46 | 47 | XPath 48 | 49 | 50 | 51 | 52 | Action Cable 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 62 | -------------------------------------------------------------------------------- /src/scene.rs: -------------------------------------------------------------------------------- 1 | use geometry::*; 2 | use math::*; 3 | use renderable::{Hit, Renderable}; 4 | 5 | use std::f64; 6 | 7 | pub struct Scene { 8 | objects: Vec> 9 | } 10 | 11 | impl Scene { 12 | pub fn new() -> Scene { 13 | Scene { objects: Vec::new() } 14 | } 15 | pub fn add(&mut self, object: Box) { 16 | self.objects.push(object); 17 | } 18 | pub fn intersect<'a>(&'a self, ray: &Ray) -> Option> { 19 | let mut hit_dist = f64::INFINITY; 20 | let mut hit_obj: Option<&Box> = None; 21 | for obj in self.objects.iter() { 22 | if let Some(dist) = obj.intersect(&ray) { 23 | if dist < hit_dist { 24 | hit_dist = dist; 25 | hit_obj = Some(&obj); 26 | } 27 | } 28 | } 29 | 30 | match hit_obj { 31 | None => { None }, 32 | Some(obj) => { 33 | Some(obj.get_hit(&ray, hit_dist)) 34 | } 35 | } 36 | } 37 | 38 | fn shadow_cast(&self, ray: &Ray, light: &Renderable) -> bool { 39 | let mut hit_obj: Option<&Renderable> = None; 40 | let mut hit_dist = f64::INFINITY; 41 | for obj in self.objects.iter() { 42 | if let Some(dist) = obj.intersect(&ray) { 43 | if dist < hit_dist { 44 | hit_dist = dist; 45 | hit_obj = Some(&**obj); 46 | } 47 | } 48 | } 49 | match hit_obj { 50 | None => { false }, 51 | Some(obj) => { 52 | // Ideally, something like this: 53 | // if obj as *const Renderable == light as *const Renderable { true } else { false } 54 | // but we hit an ICE in rust 1.0.0 55 | obj.identity() == light.identity() 56 | } 57 | } 58 | } 59 | 60 | pub fn sample_lights(&self, from: Vec3d, normal: Vec3d, rng: &mut F64Rng) -> Vec3d { 61 | let mut emission = Vec3d::zero(); 62 | for obj in self.objects.iter() { 63 | if !obj.is_emissive() { continue; } 64 | let (random_obj_dir, obj_emission) = obj.random_emission(from, normal, rng); 65 | let ray = Ray::new(from, random_obj_dir); 66 | if self.shadow_cast(&ray, &**obj) { 67 | emission = emission + obj_emission; 68 | } 69 | } 70 | emission 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.idea/libraries/Cargo__path_tracer_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/geometry.rs: -------------------------------------------------------------------------------- 1 | use material::Material; 2 | use renderable::{Hit, Renderable}; 3 | use math::{Vec3d, F64Rng}; 4 | use std::f64::consts::PI; 5 | 6 | #[derive(Debug, Clone, Copy)] 7 | pub struct Ray { 8 | pub origin: Vec3d, 9 | pub direction: Vec3d 10 | } 11 | 12 | impl Ray { 13 | pub fn new(origin: Vec3d, direction: Vec3d) -> Ray { 14 | Ray { origin: origin, direction: direction } 15 | } 16 | } 17 | 18 | pub struct Sphere { 19 | material: Material, 20 | radius_squared: f64, 21 | position: Vec3d, 22 | emission: Vec3d, 23 | colour: Vec3d, 24 | emissive: bool, 25 | } 26 | 27 | unsafe impl Sync for Sphere {} 28 | 29 | unsafe impl Send for Sphere {} 30 | 31 | impl Sphere { 32 | pub fn new(material: Material, radius: f64, position: Vec3d, emission: Vec3d, colour: Vec3d) -> Sphere { 33 | Sphere { 34 | material: material, 35 | radius_squared: radius * radius, 36 | position: position, 37 | emission: emission, 38 | colour: colour, 39 | emissive: emission.max_component() > 0.0 40 | } 41 | } 42 | } 43 | 44 | impl Renderable for Sphere { 45 | fn get_hit(&self, ray: &Ray, dist: f64) -> Hit { 46 | let pos = ray.origin + ray.direction * dist; 47 | let normal = (pos - self.position).normalized(); 48 | Hit { 49 | pos: pos, 50 | normal: normal, 51 | material: &self.material, 52 | colour: self.colour, 53 | emission: self.emission 54 | } 55 | } 56 | fn intersect(&self, ray: &Ray) -> Option { 57 | let op = self.position - ray.origin; 58 | let b = op.dot(ray.direction); 59 | let determinant = b * b - op.dot(op) + self.radius_squared; 60 | if determinant < 0.0 { return None; } 61 | let determinant = determinant.sqrt(); 62 | let t1 = b - determinant; 63 | let t2 = b + determinant; 64 | const EPSILON: f64 = 0.0001; 65 | if t1 > EPSILON { 66 | Some(t1) 67 | } else if t2 > EPSILON { 68 | Some(t2) 69 | } else { 70 | None 71 | } 72 | } 73 | fn is_emissive(&self) -> bool { self.emissive } 74 | fn random_emission(&self, from: Vec3d, normal: Vec3d, rng: &mut F64Rng) -> (Vec3d, Vec3d) { 75 | let pos_to_center = self.position - from; 76 | let dist_squared = pos_to_center.length_squared(); 77 | let sw = pos_to_center.normalized(); 78 | // todo make an ONB func 79 | let su = if sw.x.abs() > 0.1 { 80 | Vec3d::new(0.0, 1.0, 0.0) 81 | } else { 82 | Vec3d::new(1.0, 0.0, 0.0) 83 | }.cross(sw).normalized(); 84 | let sv = sw.cross(su); 85 | // radius / dist = opp / adjacent = sin(angle), we need cos(angle) 86 | // sin^2(a)+cos^2(a) = 1, so cos(a) = sqrt(1-sin^2(a)) = sqrt(1-opp^2/adj^2). 87 | let cos_a_max = (1.0 - self.radius_squared / dist_squared).sqrt(); 88 | // Now the below is "just" a random cosine distribution, up to cos_a_max. 89 | let (eps1, eps2) = (rng.next(), rng.next()); 90 | let cos_a = 1.0 - eps1 + eps1 * cos_a_max; 91 | let sin_a = (1.0 - cos_a * cos_a).sqrt(); 92 | let phi = 2.0 * PI * eps2; 93 | let l = (su * phi.cos() * sin_a + sv * phi.sin() * sin_a + sw * cos_a).normalized(); 94 | let omega = 2.0 * PI * (1.0 - cos_a_max); 95 | let emission = self.emission * l.dot(normal) * omega * (1.0 / PI); 96 | (l, emission) 97 | } 98 | fn identity(&self) -> u64 { 99 | self as *const Self as u64 100 | } 101 | } 102 | 103 | #[test] 104 | fn intersection() { 105 | let sphere = Sphere::new( 106 | Material::Diffuse, 107 | 100.0, 108 | Vec3d::new(0.0, 0.0, 200.0), 109 | Vec3d::zero(), 110 | Vec3d::zero()); 111 | let ray = Ray::new(Vec3d::zero(), Vec3d::new(0.0, 0.0, 1.0)); 112 | match sphere.intersect(&ray) { 113 | Some(x) => assert_eq!(x, 100.0), 114 | None => panic!("unexpected") 115 | } 116 | let ray = Ray::new(Vec3d::zero(), Vec3d::new(0.0, 1.0, 0.0)); 117 | match sphere.intersect(&ray) { 118 | Some(_) => panic!("unexpected"), 119 | None => {} 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/bin/merge.rs: -------------------------------------------------------------------------------- 1 | extern crate argparse; 2 | extern crate image; 3 | extern crate path_tracer; 4 | 5 | use argparse::{ArgumentParser, Store, Collect}; 6 | use path_tracer::*; 7 | use std::fs::File; 8 | use std::io::{self, BufReader}; 9 | use std::io::prelude::*; 10 | 11 | #[derive(Debug)] 12 | enum ImageError { 13 | IoError(io::Error), 14 | BadFileError(String) 15 | } 16 | 17 | use ImageError::*; 18 | 19 | impl From for ImageError { 20 | fn from(e: io::Error) -> ImageError { 21 | ImageError::IoError(e) 22 | } 23 | } 24 | 25 | type Result = std::result::Result; 26 | 27 | struct PartialImage { 28 | image: Vec>, 29 | samples: i32, 30 | } 31 | 32 | impl PartialImage { 33 | fn empty() -> PartialImage { 34 | PartialImage { image: Vec::new(), samples: 0 } 35 | } 36 | 37 | fn add(self, other: PartialImage) -> PartialImage { 38 | let image = if self.samples == 0 { other.image } else { 39 | let combined = self.image.iter().zip(other.image.iter()).map(|(x, y)| { 40 | x.iter().zip(y.iter()).map(|(x, y)| { *x + *y }).collect() 41 | }).collect(); 42 | combined 43 | }; 44 | let samples = self.samples + other.samples; 45 | PartialImage { image: image, samples: samples } 46 | } 47 | 48 | fn height(&self) -> usize { 49 | self.image.len() 50 | } 51 | fn width(&self) -> usize { 52 | self.image[0].len() 53 | } 54 | } 55 | 56 | fn load_file(name: &String) -> Result { 57 | let mut result: Vec> = Vec::new(); 58 | let file = BufReader::new(try!(File::open(&name))); 59 | println!("Loading '{}'", name); 60 | let mut line_iter = file.lines(); 61 | let first_line = try!(line_iter.next().unwrap()); 62 | let first_line: Vec = first_line.split(' ').filter_map(|x| x.parse().ok()).collect(); 63 | // Rust experimental branch would let us match on the vector. 64 | if first_line.len() != 3 { return Err(BadFileError("Bad header".to_string())); } 65 | let width = first_line[0]; 66 | let height = first_line[1]; 67 | let samples = first_line[2] as i32; 68 | println!("Found {} samples in {}x{} image", samples, width, height); 69 | for line in line_iter.filter_map(|x| x.ok()) { 70 | let mut vecs: Vec = Vec::new(); 71 | let mut split = line.split(' ').filter_map(|x| x.parse::().ok()); 72 | loop { 73 | match (split.next(), split.next(), split.next()) { 74 | (None, _, _) => break, 75 | (Some(x), Some(y), Some(z)) => { 76 | vecs.push(Vec3d::new(x, y, z) * samples as f64); 77 | }, 78 | (_, _, _) => return Err(BadFileError("Bad line".to_string())), 79 | } 80 | } 81 | if vecs.len() != width { 82 | return Err(BadFileError("Bad width".to_string())); 83 | } 84 | result.push(vecs); 85 | } 86 | if result.len() != height { 87 | return Err(BadFileError("Bad height".to_string())); 88 | } 89 | println!("Loaded ok"); 90 | Ok(PartialImage { image: result, samples: samples }) 91 | } 92 | 93 | fn main() { 94 | let mut to_merge: Vec = Vec::new(); 95 | let mut output_filename = "image.png".to_string(); 96 | { 97 | let mut ap = ArgumentParser::new(); 98 | ap.set_description("Combine several sample images into one PNG"); 99 | ap.refer(&mut output_filename).add_option(&["-o", "--output"], Store, 100 | "Filename to output to"); 101 | ap.refer(&mut to_merge).add_argument("files", Collect, "Files to merge") 102 | .required(); 103 | ap.parse_args_or_exit(); 104 | } 105 | let accum: PartialImage = to_merge.iter() 106 | .map(load_file) 107 | .map(|x| x.unwrap()) 108 | .fold(PartialImage::empty(), |acc, item| { acc.add(item) }); 109 | 110 | println!("Merged {} samples", accum.samples); 111 | println!("Writing output to '{}'", output_filename); 112 | let height = accum.height(); 113 | let width = accum.width(); 114 | let samples = accum.samples; 115 | let mut image = image::ImageBuffer::new(width as u32, height as u32); 116 | for y in 0..height { 117 | for x in 0..width { 118 | let sum = accum.image[y][x] / samples as f64; 119 | image.put_pixel(x as u32, y as u32, image::Rgb([to_int(sum.x), to_int(sum.y), to_int(sum.z)])); 120 | } 121 | } 122 | let mut output_file = File::create(output_filename).unwrap(); 123 | image::ImageRgb8(image).save(&mut output_file, image::PNG).unwrap(); 124 | } 125 | -------------------------------------------------------------------------------- /src/math.rs: -------------------------------------------------------------------------------- 1 | use rand::{Rng, XorShiftRng}; 2 | use std::ops::{Add, Sub, Mul, Div}; 3 | 4 | #[derive(Debug, Clone, Copy)] 5 | pub struct Vec3d { 6 | pub x: f64, 7 | pub y: f64, 8 | pub z: f64 9 | } 10 | 11 | pub trait Clamp { 12 | fn clamp(self) -> Self; 13 | } 14 | 15 | impl Clamp for f64 { 16 | #[inline] 17 | fn clamp(self) -> f64 { 18 | if self < 0.0 { 0.0 } else if self > 1.0 { 1.0 } else { self } 19 | } 20 | } 21 | 22 | impl Vec3d { 23 | #[inline] 24 | pub fn normalized(self) -> Vec3d { 25 | self / self.dot(self).sqrt() 26 | } 27 | #[inline] 28 | pub fn dot(self, other: Vec3d) -> f64 { 29 | self.x * other.x + self.y * other.y + self.z * other.z 30 | } 31 | #[inline] 32 | pub fn length_squared(self) -> f64 { 33 | self.x * self.x + self.y * self.y + self.z * self.z 34 | } 35 | #[inline] 36 | pub fn length(self) -> f64 { 37 | (self.x * self.x + self.y * self.y + self.z * self.z).sqrt() 38 | } 39 | #[inline] 40 | pub fn new(x: f64, y: f64, z: f64) -> Vec3d { 41 | Vec3d { x: x, y: y, z: z } 42 | } 43 | #[inline] 44 | pub fn zero() -> Vec3d { 45 | Vec3d { x: 0.0, y: 0.0, z: 0.0 } 46 | } 47 | #[inline] 48 | pub fn one() -> Vec3d { 49 | Vec3d { x: 1.0, y: 1.0, z: 1.0 } 50 | } 51 | #[inline] 52 | pub fn cross(self, other: Vec3d) -> Vec3d { 53 | Vec3d { 54 | x: self.y * other.z - self.z * other.y, 55 | y: self.z * other.x - self.x * other.z, 56 | z: self.x * other.y - self.y * other.x 57 | } 58 | } 59 | #[inline] 60 | pub fn max_component(self) -> f64 { 61 | if self.x > self.y && self.x > self.z { self.x } else if self.y > self.x && self.y > self.z { self.y } else { self.z } 62 | } 63 | #[inline] 64 | pub fn max_ordinal(self) -> u8 { 65 | if self.x > self.y && self.x > self.z { 0 } else if self.y > self.x && self.y > self.z { 1 } else { 2 } 66 | } 67 | #[inline] 68 | pub fn min_component(self) -> f64 { 69 | if self.x < self.y && self.x < self.z { self.x } else if self.y < self.x && self.y < self.z { self.y } else { self.z } 70 | } 71 | #[inline] 72 | pub fn min_ordinal(self) -> u8 { 73 | if self.x < self.y && self.x < self.z { 0 } else if self.y < self.x && self.y < self.z { 1 } else { 2 } 74 | } 75 | #[inline] 76 | pub fn abs(self) -> Vec3d { 77 | Vec3d { x: self.x.abs(), y: self.y.abs(), z: self.z.abs() } 78 | } 79 | #[inline] 80 | pub fn neg(self) -> Vec3d { 81 | Vec3d { x: -self.x, y: -self.y, z: -self.z } 82 | } 83 | #[inline] 84 | pub fn clamp(self) -> Vec3d { 85 | Vec3d { x: self.x.clamp(), y: self.y.clamp(), z: self.z.clamp() } 86 | } 87 | #[inline] 88 | pub fn min(self, other: Vec3d) -> Vec3d { 89 | Vec3d { x: self.x.min(other.x), y: self.y.min(other.y), z: self.z.min(other.z) } 90 | } 91 | #[inline] 92 | pub fn max(self, other: Vec3d) -> Vec3d { 93 | Vec3d { x: self.x.max(other.x), y: self.y.max(other.y), z: self.z.max(other.z) } 94 | } 95 | } 96 | 97 | 98 | impl Add for Vec3d { 99 | type Output = Vec3d; 100 | 101 | #[inline] 102 | fn add(self, other: Vec3d) -> Vec3d { 103 | Vec3d { x: self.x + other.x, y: self.y + other.y, z: self.z + other.z } 104 | } 105 | } 106 | 107 | impl Sub for Vec3d { 108 | type Output = Vec3d; 109 | 110 | #[inline] 111 | fn sub(self, other: Vec3d) -> Vec3d { 112 | Vec3d { x: self.x - other.x, y: self.y - other.y, z: self.z - other.z } 113 | } 114 | } 115 | 116 | impl Mul for Vec3d { 117 | type Output = Vec3d; 118 | 119 | #[inline] 120 | fn mul(self, other: Vec3d) -> Vec3d { 121 | Vec3d { x: self.x * other.x, y: self.y * other.y, z: self.z * other.z } 122 | } 123 | } 124 | 125 | impl Mul for Vec3d { 126 | type Output = Vec3d; 127 | 128 | #[inline] 129 | fn mul(self, other: f64) -> Vec3d { 130 | Vec3d { x: self.x * other, y: self.y * other, z: self.z * other } 131 | } 132 | } 133 | 134 | impl Div for Vec3d { 135 | type Output = Vec3d; 136 | 137 | #[inline] 138 | fn div(self, other: Vec3d) -> Vec3d { 139 | Vec3d { x: self.x / other.x, y: self.y / other.y, z: self.z * other.z } 140 | } 141 | } 142 | 143 | impl Div for Vec3d { 144 | type Output = Vec3d; 145 | 146 | #[inline] 147 | fn div(self, other: f64) -> Vec3d { 148 | let recip = 1.0 / other; 149 | Vec3d { x: self.x * recip, y: self.y * recip, z: self.z * recip } 150 | } 151 | } 152 | 153 | pub trait F64Rng { 154 | fn next(&mut self) -> f64; 155 | } 156 | 157 | impl F64Rng for XorShiftRng { 158 | fn next(&mut self) -> f64 { 159 | return self.gen::(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | mod geometry; 4 | mod material; 5 | mod math; 6 | mod renderable; 7 | mod scene; 8 | 9 | pub use self::geometry::*; 10 | pub use self::material::Material; 11 | pub use self::math::*; 12 | pub use self::scene::*; 13 | 14 | 15 | use std::f64::consts::PI; 16 | 17 | pub fn radiance(scene: &Scene, ray: &Ray, depth: i32, rng: &mut F64Rng, emit: bool) -> Vec3d { 18 | scene.intersect(&ray).map_or(Vec3d::zero(), |hit| { 19 | let n1 = if hit.normal.dot(ray.direction) < 0.0 { hit.normal } else { hit.normal.neg() }; 20 | let mut emission = if emit { hit.emission } else { Vec3d::zero() }; 21 | let mut colour = hit.colour; 22 | let max_reflectance = colour.max_component(); 23 | let depth = depth + 1; 24 | if depth > 5 { 25 | let rand = rng.next(); 26 | if rand < max_reflectance && depth < 500 { 27 | // Rust's stack blows up ~600 on my machine 28 | colour = colour * (1.0 / max_reflectance); 29 | } else { 30 | return emission; 31 | } 32 | } 33 | match *hit.material { 34 | Material::Diffuse => { 35 | // Get a random polar coordinate. 36 | let r1 = rng.next() * 2.0 * PI; 37 | let r2 = rng.next(); 38 | let r2s = r2.sqrt(); 39 | // Create a coordinate system u,v,w local to the point, where the w is the normal 40 | // pointing out of the sphere and the u and v are orthonormal to w. 41 | let w = n1; 42 | // Pick an arbitrary non-zero preferred axis for u 43 | let u = if n1.x.abs() > 0.1 { Vec3d::new(0.0, 1.0, 0.0) } else { Vec3d::new(1.0, 0.0, 0.0) }.cross(w); 44 | let v = w.cross(u); 45 | // construct the new direction 46 | let new_dir = u * r1.cos() * r2s + v * r1.sin() * r2s + w * (1.0 - r2).sqrt(); 47 | let new_ray = Ray::new(hit.pos, new_dir.normalized()); 48 | emission = emission + colour * scene.sample_lights(hit.pos, n1, rng); 49 | colour = colour * radiance(scene, &new_ray, depth, rng, false); 50 | }, 51 | Material::Specular => { 52 | let reflection = ray.direction - hit.normal * 2.0 * hit.normal.dot(ray.direction); 53 | let reflected_ray = Ray::new(hit.pos, reflection); 54 | colour = colour * radiance(scene, &reflected_ray, depth, rng, true); 55 | }, 56 | Material::Refractive => { 57 | let reflection = ray.direction - hit.normal * 2.0 * hit.normal.dot(ray.direction); 58 | let reflected_ray = Ray::new(hit.pos, reflection); 59 | let into = hit.normal.dot(n1) > 0.0; 60 | let nc = 1.0; 61 | let nt = 1.5; 62 | let nnt = if into { nc / nt } else { nt / nc }; 63 | let ddn = ray.direction.dot(n1); 64 | let cos2t = 1.0 - nnt * nnt * (1.0 - ddn * ddn); 65 | if cos2t < 0.0 { 66 | // Total internal reflection 67 | colour = colour * radiance(scene, &reflected_ray, depth, rng, true); 68 | } else { 69 | let tbd = ddn * nnt + cos2t.sqrt(); 70 | let tbd = if into { tbd } else { -tbd }; 71 | let tdir = (ray.direction * nnt - hit.normal * tbd).normalized(); 72 | let transmitted_ray = Ray::new(hit.pos, tdir); 73 | let a = nt - nc; 74 | let b = nt + nc; 75 | let r0 = (a * a) / (b * b); 76 | let c = 1.0 - if into { -ddn } else { tdir.dot(hit.normal) }; 77 | let re = r0 + (1.0 - r0) * c * c * c * c * c; 78 | let tr = 1.0 - re; 79 | let p = 0.25 + 0.5 * re; 80 | let rp = re / p; 81 | let tp = tr / (1.0 - p); 82 | colour = colour * if depth > 2 { 83 | if rng.next() < p { 84 | radiance(scene, &reflected_ray, depth, rng, true) * rp 85 | } else { 86 | radiance(scene, &transmitted_ray, depth, rng, true) * tp 87 | } 88 | } else { 89 | radiance(scene, &reflected_ray, depth, rng, true) * re + 90 | radiance(scene, &transmitted_ray, depth, rng, true) * tr 91 | } 92 | } 93 | } 94 | } 95 | emission + colour 96 | }) 97 | } 98 | 99 | pub fn random_samp(rng: &mut T) -> f64 { 100 | let r = 2.0 * rng.next(); 101 | if r < 1.0 { r.sqrt() - 1.0 } else { 1.0 - (2.0 - r).sqrt() } 102 | } 103 | 104 | pub fn to_int(v: f64) -> u8 { 105 | let ch = (v.powf(1.0 / 2.2) * 255.0 + 0.5) as i64; 106 | if ch < 0 { 0u8 } else if ch > 255 { 255u8 } else { ch as u8 } 107 | } 108 | -------------------------------------------------------------------------------- /src/bin/path_tracer.rs: -------------------------------------------------------------------------------- 1 | // Based on smallpt, http://www.kevinbeason.com/smallpt/ which is also licensed under 2 | // the MIT license. 3 | extern crate argparse; 4 | extern crate image; 5 | extern crate num_cpus; 6 | extern crate rand; 7 | extern crate threadpool; 8 | 9 | use argparse::{ArgumentParser, Store, StoreTrue}; 10 | use rand::{XorShiftRng, SeedableRng}; 11 | use threadpool::ThreadPool; 12 | 13 | use std::fs::File; 14 | use std::io::{self, BufWriter}; 15 | use std::io::prelude::*; 16 | use std::sync::Arc; 17 | use std::sync::mpsc::channel; 18 | 19 | extern crate path_tracer; 20 | 21 | use path_tracer::*; 22 | 23 | 24 | fn main() { 25 | let mut samps = 1; 26 | let mut width = 1024; 27 | let mut height = 768; 28 | let mut output_filename = "".to_string(); 29 | let mut num_threads = num_cpus::get(); 30 | let mut seed = 0x193a6754; 31 | let mut partial = false; 32 | { 33 | let mut ap = ArgumentParser::new(); 34 | ap.set_description("Render a simple image"); 35 | ap.refer(&mut samps).add_option(&["-s", "--samples"], Store, "Number of samples"); 36 | ap.refer(&mut height).add_option(&["-h", "--height"], Store, "Height"); 37 | ap.refer(&mut width).add_option(&["-w", "--width"], Store, "Width"); 38 | ap.refer(&mut output_filename).add_option(&["-o", "--output"], Store, 39 | "Filename to output to"); 40 | ap.refer(&mut num_threads).add_option(&["--num-threads"], Store, 41 | "Number of threads to use"); 42 | ap.refer(&mut seed).add_option(&["--seed"], Store, "Random seed"); 43 | ap.refer(&mut partial).add_option(&["--partial"], StoreTrue, 44 | "Output a partial render"); 45 | ap.parse_args_or_exit(); 46 | } 47 | samps = samps / 4; 48 | if samps < 1 { samps = 1; } 49 | if output_filename == "" { 50 | output_filename = if partial { "image.part" } else { "image.png" }.to_string(); 51 | } 52 | const BLACK: Vec3d = Vec3d { x: 0.0, y: 0.0, z: 0.0 }; 53 | const RED: Vec3d = Vec3d { x: 0.75, y: 0.25, z: 0.25 }; 54 | const BLUE: Vec3d = Vec3d { x: 0.25, y: 0.25, z: 0.75 }; 55 | const GREY: Vec3d = Vec3d { x: 0.75, y: 0.75, z: 0.75 }; 56 | const WHITE: Vec3d = Vec3d { x: 0.999, y: 0.999, z: 0.999 }; 57 | let mut scene = Scene::new(); 58 | scene.add(Box::new(Sphere::new(Material::Diffuse, 1e5, 59 | Vec3d::new(1e5 + 1.0, 40.8, 81.6), 60 | BLACK, RED))); 61 | scene.add(Box::new(Sphere::new(Material::Diffuse, 1e5, 62 | Vec3d::new(-1e5 + 99.0, 40.8, 81.6), 63 | BLACK, BLUE))); 64 | scene.add(Box::new(Sphere::new(Material::Diffuse, 1e5, 65 | Vec3d::new(50.0, 40.8, 1e5), 66 | BLACK, GREY))); 67 | scene.add(Box::new(Sphere::new(Material::Diffuse, 1e5, 68 | Vec3d::new(50.0, 40.8, -1e5 + 170.0), 69 | BLACK, BLACK))); 70 | scene.add(Box::new(Sphere::new(Material::Diffuse, 1e5, 71 | Vec3d::new(50.0, 1e5, 81.6), 72 | BLACK, GREY))); 73 | scene.add(Box::new(Sphere::new(Material::Diffuse, 1e5, 74 | Vec3d::new(50.0, -1e5 + 81.6, 81.6), 75 | BLACK, GREY))); 76 | scene.add(Box::new(Sphere::new(Material::Specular, 16.5, 77 | Vec3d::new(27.0, 16.5, 47.0), 78 | BLACK, WHITE))); 79 | scene.add(Box::new(Sphere::new(Material::Refractive, 16.5, 80 | Vec3d::new(73.0, 16.5, 78.0), 81 | BLACK, WHITE))); 82 | scene.add(Box::new(Sphere::new(Material::Diffuse, 1.5, 83 | Vec3d::new(50.0, 81.6 - 16.5, 81.6), 84 | Vec3d::new(400.0, 400.0, 400.0), BLACK))); 85 | let scene = Arc::new(scene); 86 | 87 | let camera_pos = Vec3d::new(50.0, 52.0, 295.6); 88 | let camera_dir = Vec3d::new(0.0, -0.042612, -1.0).normalized(); 89 | let camera_x = Vec3d::new(width as f64 * 0.5135 / height as f64, 0.0, 0.0); 90 | let camera_y = camera_x.cross(camera_dir).normalized() * 0.5135; 91 | 92 | println!("Using {} threads", num_threads); 93 | let pool = ThreadPool::new(num_threads); 94 | let (tx, rx) = channel(); 95 | 96 | for y in 0..height { 97 | let tx = tx.clone(); 98 | let scene = scene.clone(); 99 | pool.execute(move || { 100 | let mut line = Vec::with_capacity(width); 101 | let mut rng = XorShiftRng::from_seed([1 + (y * y) as u32, seed, 0x15aac60d, 0xb017f00d]); 102 | for x in 0..width { 103 | let mut sum = Vec3d::zero(); 104 | for sx in 0..2 { 105 | for sy in 0..2 { 106 | let mut r = Vec3d::zero(); 107 | for _samp in 0..samps { 108 | let dx = random_samp(&mut rng); 109 | let dy = random_samp(&mut rng); 110 | let sub_x = (sx as f64 + 0.5 + dx) / 2.0; 111 | let dir_x = (sub_x + x as f64) / width as f64 - 0.5; 112 | let sub_y = (sy as f64 + 0.5 + dy) / 2.0; 113 | let dir_y = (sub_y + (height - y - 1) as f64) / height as f64 - 0.5; 114 | let dir = (camera_x * dir_x + camera_y * dir_y + camera_dir).normalized(); 115 | let jittered_ray = Ray::new(camera_pos + dir * 140.0, dir); 116 | let sample = radiance(&scene, &jittered_ray, 0, &mut rng, true); 117 | r = r + (sample / samps as f64); 118 | } 119 | sum = sum + r.clamp() * 0.25; 120 | } 121 | } 122 | line.push(sum); 123 | } 124 | tx.send((y, line)).unwrap(); 125 | }); 126 | } 127 | let mut left = height; 128 | let mut screen: Vec> = Vec::new(); 129 | for _y in 0..height { 130 | screen.push(Vec::new()); 131 | } 132 | while left > 0 { 133 | print!("Rendering ({} spp) {:.4}%...\r", samps * 4, 100.0 * (height - left) as f64 / height as f64); 134 | io::stdout().flush().ok().expect("Could not flush stdout"); 135 | let (y, line) = rx.recv().unwrap(); 136 | screen[y] = line; 137 | left -= 1; 138 | } 139 | if !partial { 140 | println!("\nWriting output to '{}'", output_filename); 141 | let mut image = image::ImageBuffer::new(width as u32, height as u32); 142 | for y in 0..height { 143 | for x in 0..width { 144 | let sum = screen[y][x]; 145 | image.put_pixel(x as u32, y as u32, image::Rgb([to_int(sum.x), to_int(sum.y), to_int(sum.z)])); 146 | } 147 | } 148 | let mut output_file = File::create(output_filename).unwrap(); 149 | image::ImageRgb8(image).save(&mut output_file, image::PNG).unwrap(); 150 | } else { 151 | println!("\nWriting partial output to '{}'", output_filename); 152 | let mut writer = BufWriter::new(File::create(output_filename).unwrap()); 153 | write!(&mut writer, "{} {} {}\n", width, height, samps).unwrap(); 154 | for y in 0..height { 155 | for x in 0..width { 156 | let sum = screen[y][x]; 157 | if x != 0 { write!(&mut writer, " ").unwrap(); } 158 | write!(&mut writer, "{} {} {}", sum.x, sum.y, sum.z).unwrap(); 159 | } 160 | write!(&mut writer, "\n").unwrap(); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "adler32" 3 | version = "1.0.2" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | 6 | [[package]] 7 | name = "argparse" 8 | version = "0.2.1" 9 | source = "registry+https://github.com/rust-lang/crates.io-index" 10 | 11 | [[package]] 12 | name = "arrayvec" 13 | version = "0.4.7" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | dependencies = [ 16 | "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 17 | ] 18 | 19 | [[package]] 20 | name = "bitflags" 21 | version = "1.0.1" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | 24 | [[package]] 25 | name = "byteorder" 26 | version = "1.2.2" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | 29 | [[package]] 30 | name = "cfg-if" 31 | version = "0.1.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | 34 | [[package]] 35 | name = "color_quant" 36 | version = "1.0.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | 39 | [[package]] 40 | name = "crossbeam-deque" 41 | version = "0.2.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | dependencies = [ 44 | "crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 46 | ] 47 | 48 | [[package]] 49 | name = "crossbeam-epoch" 50 | version = "0.3.1" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | dependencies = [ 53 | "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", 54 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 56 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 60 | ] 61 | 62 | [[package]] 63 | name = "crossbeam-utils" 64 | version = "0.2.2" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | dependencies = [ 67 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 68 | ] 69 | 70 | [[package]] 71 | name = "deflate" 72 | version = "0.7.18" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | dependencies = [ 75 | "adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 77 | ] 78 | 79 | [[package]] 80 | name = "either" 81 | version = "1.5.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | 84 | [[package]] 85 | name = "enum_primitive" 86 | version = "0.1.1" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | dependencies = [ 89 | "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", 90 | ] 91 | 92 | [[package]] 93 | name = "fuchsia-zircon" 94 | version = "0.3.3" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | dependencies = [ 97 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 98 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 99 | ] 100 | 101 | [[package]] 102 | name = "fuchsia-zircon-sys" 103 | version = "0.3.3" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | 106 | [[package]] 107 | name = "gif" 108 | version = "0.9.2" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | dependencies = [ 111 | "color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 112 | "lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 113 | ] 114 | 115 | [[package]] 116 | name = "image" 117 | version = "0.18.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | dependencies = [ 120 | "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 122 | "gif 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", 123 | "jpeg-decoder 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 124 | "num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 125 | "num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 126 | "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "png 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 128 | "scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 129 | ] 130 | 131 | [[package]] 132 | name = "inflate" 133 | version = "0.3.4" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | dependencies = [ 136 | "adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 137 | ] 138 | 139 | [[package]] 140 | name = "jpeg-decoder" 141 | version = "0.1.14" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | dependencies = [ 144 | "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 145 | "rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 146 | ] 147 | 148 | [[package]] 149 | name = "lazy_static" 150 | version = "1.0.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | 153 | [[package]] 154 | name = "libc" 155 | version = "0.2.40" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | 158 | [[package]] 159 | name = "lzw" 160 | version = "0.10.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | 163 | [[package]] 164 | name = "memoffset" 165 | version = "0.2.1" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | 168 | [[package]] 169 | name = "nodrop" 170 | version = "0.1.12" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | 173 | [[package]] 174 | name = "num-integer" 175 | version = "0.1.36" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | dependencies = [ 178 | "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 179 | ] 180 | 181 | [[package]] 182 | name = "num-iter" 183 | version = "0.1.35" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | dependencies = [ 186 | "num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", 187 | "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 188 | ] 189 | 190 | [[package]] 191 | name = "num-rational" 192 | version = "0.1.42" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | dependencies = [ 195 | "num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", 196 | "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 197 | ] 198 | 199 | [[package]] 200 | name = "num-traits" 201 | version = "0.1.43" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | dependencies = [ 204 | "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 205 | ] 206 | 207 | [[package]] 208 | name = "num-traits" 209 | version = "0.2.2" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | 212 | [[package]] 213 | name = "num_cpus" 214 | version = "1.8.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | dependencies = [ 217 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 218 | ] 219 | 220 | [[package]] 221 | name = "path_tracer" 222 | version = "0.1.0" 223 | dependencies = [ 224 | "argparse 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 225 | "image 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", 226 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 227 | "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 228 | "threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 229 | ] 230 | 231 | [[package]] 232 | name = "png" 233 | version = "0.11.0" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | dependencies = [ 236 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 237 | "deflate 0.7.18 (registry+https://github.com/rust-lang/crates.io-index)", 238 | "inflate 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 239 | "num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 240 | ] 241 | 242 | [[package]] 243 | name = "rand" 244 | version = "0.4.2" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | dependencies = [ 247 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 248 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 249 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 250 | ] 251 | 252 | [[package]] 253 | name = "rayon" 254 | version = "1.0.1" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | dependencies = [ 257 | "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 258 | "rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 259 | ] 260 | 261 | [[package]] 262 | name = "rayon-core" 263 | version = "1.4.0" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | dependencies = [ 266 | "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 267 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 268 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 269 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 270 | "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 271 | ] 272 | 273 | [[package]] 274 | name = "scoped_threadpool" 275 | version = "0.1.9" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | 278 | [[package]] 279 | name = "scopeguard" 280 | version = "0.3.3" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | 283 | [[package]] 284 | name = "threadpool" 285 | version = "1.7.1" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | dependencies = [ 288 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 289 | ] 290 | 291 | [[package]] 292 | name = "winapi" 293 | version = "0.3.4" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | dependencies = [ 296 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 297 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 298 | ] 299 | 300 | [[package]] 301 | name = "winapi-i686-pc-windows-gnu" 302 | version = "0.4.0" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | 305 | [[package]] 306 | name = "winapi-x86_64-pc-windows-gnu" 307 | version = "0.4.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | 310 | [metadata] 311 | "checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45" 312 | "checksum argparse 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37bb99f5e39ee8b23b6e227f5b8f024207e8616f44aa4b8c76ecd828011667ef" 313 | "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" 314 | "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" 315 | "checksum byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "73b5bdfe7ee3ad0b99c9801d58807a9dbc9e09196365b0203853b99889ab3c87" 316 | "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" 317 | "checksum color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a475fc4af42d83d28adf72968d9bcfaf035a1a9381642d8e85d8a04957767b0d" 318 | "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" 319 | "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" 320 | "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" 321 | "checksum deflate 0.7.18 (registry+https://github.com/rust-lang/crates.io-index)" = "32c8120d981901a9970a3a1c97cf8b630e0fa8c3ca31e75b6fd6fd5f9f427b31" 322 | "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" 323 | "checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" 324 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 325 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 326 | "checksum gif 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e41945ba23db3bf51b24756d73d81acb4f28d85c3dccc32c6fae904438c25f" 327 | "checksum image 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "545f000e8aa4e569e93f49c446987133452e0091c2494ac3efd3606aa3d309f2" 328 | "checksum inflate 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f5f9f47468e9a76a6452271efadc88fe865a82be91fe75e6c0c57b87ccea59d4" 329 | "checksum jpeg-decoder 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "0dfe27a6c0dabd772d0f9b9f8701c4ca12c4d1eebcadf2be1f6f70396f6a1434" 330 | "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" 331 | "checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b" 332 | "checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" 333 | "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" 334 | "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" 335 | "checksum num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f8d26da319fb45674985c78f1d1caf99aa4941f785d384a2ae36d0740bc3e2fe" 336 | "checksum num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "4b226df12c5a59b63569dd57fafb926d91b385dfce33d8074a412411b689d593" 337 | "checksum num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" 338 | "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" 339 | "checksum num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dee092fcdf725aee04dd7da1d21debff559237d49ef1cb3e69bcb8ece44c7364" 340 | "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" 341 | "checksum png 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f0b0cabbbd20c2d7f06dbf015e06aad59b6ca3d9ed14848783e98af9aaf19925" 342 | "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" 343 | "checksum rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80e811e76f1dbf68abf87a759083d34600017fc4e10b6bd5ad84a700f9dba4b1" 344 | "checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8" 345 | "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 346 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 347 | "checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865" 348 | "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" 349 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 350 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 351 | --------------------------------------------------------------------------------