├── .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 |
61 |
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 |
--------------------------------------------------------------------------------