diff options
author | Yann Herklotz <git@yannherklotz.com> | 2020-04-26 21:15:53 +0100 |
---|---|---|
committer | Yann Herklotz <git@yannherklotz.com> | 2020-04-26 21:15:53 +0100 |
commit | 9f4cdae92c8df0a7990fdb77d9ff161b2eebf467 (patch) | |
tree | eb1e8c4de3b6040859c88fb8c2ea69580a73ec10 | |
download | leela-9f4cdae92c8df0a7990fdb77d9ff161b2eebf467.tar.gz leela-9f4cdae92c8df0a7990fdb77d9ff161b2eebf467.zip |
Add initial files
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Cargo.lock | 284 | ||||
-rw-r--r-- | Cargo.toml | 11 | ||||
-rw-r--r-- | src/camera.rs | 25 | ||||
-rw-r--r-- | src/colour.rs | 15 | ||||
-rw-r--r-- | src/hittable.rs | 24 | ||||
-rw-r--r-- | src/main.rs | 35 | ||||
-rw-r--r-- | src/material.rs | 26 | ||||
-rw-r--r-- | src/random.rs | 28 | ||||
-rw-r--r-- | src/ray.rs | 27 | ||||
-rw-r--r-- | src/render.rs | 44 | ||||
-rw-r--r-- | src/scene.rs | 35 | ||||
-rw-r--r-- | src/sphere.rs | 40 |
13 files changed, 596 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6ff48c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +*.ppm diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..700c46b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,284 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "approx" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cgmath" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "283944cdecc44bf0b8dd010ec9af888d3b4f142844fdbe026c20ef68148d6fe7" +dependencies = [ + "approx", + "num-traits", + "rand 0.6.5", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "leela" +version = "0.1.0" +dependencies = [ + "cgmath", + "rand 0.7.3", +] + +[[package]] +name = "libc" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.7", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc 0.1.0", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..530abd3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "leela" +version = "0.1.0" +authors = ["Yann Herklotz <git@yannherklotz.com>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cgmath = "0.17.0" +rand = "0.7.3" diff --git a/src/camera.rs b/src/camera.rs new file mode 100644 index 0000000..8090bde --- /dev/null +++ b/src/camera.rs @@ -0,0 +1,25 @@ +use cgmath::{Vector3, vec3}; +use crate::ray::Ray; + +pub struct Camera { + origin: Vector3<f64>, + lower_left_corner: Vector3<f64>, + horizontal: Vector3<f64>, + vertical: Vector3<f64> +} + +impl Camera { + pub fn new() -> Camera { + Camera { + origin: vec3(0.0, 0.0, 0.0), + lower_left_corner: vec3(-2.0, -1.0, -1.0), + horizontal: vec3(4.0, 0.0, 0.0), + vertical: vec3(0.0, 2.0, 0.0) + } + } + + pub fn get_ray(&self, u: f64, v: f64) -> Ray { + Ray::new(self.origin, + self.lower_left_corner + u * self.horizontal + v * self.vertical) + } +} diff --git a/src/colour.rs b/src/colour.rs new file mode 100644 index 0000000..0d5e705 --- /dev/null +++ b/src/colour.rs @@ -0,0 +1,15 @@ +use cgmath::Vector3; + +pub fn clamp(x: f64, min: f64, max: f64) -> f64 { + if x < min { min } + else if x > max { max } + else { x } +} + +pub fn print_colour(colour: &Vector3<f64>, samples: i32) { + let scolour = colour.map(|x| (x / samples as f64).sqrt()); + println!("{} {} {}" + , (256.0 * clamp(scolour.x, 0.0, 0.999)) as i32 + , (256.0 * clamp(scolour.y, 0.0, 0.999)) as i32 + , (256.0 * clamp(scolour.z, 0.0, 0.999)) as i32) +} diff --git a/src/hittable.rs b/src/hittable.rs new file mode 100644 index 0000000..1c61872 --- /dev/null +++ b/src/hittable.rs @@ -0,0 +1,24 @@ +use cgmath::{Vector3, dot}; +use crate::material::Material; +use crate::ray::Ray; + +pub struct Hit<'a> { + pub t: f64, + pub p: Vector3<f64>, + pub normal: Vector3<f64>, + pub front_face: bool, + pub material: &'a (dyn Material) +} + +impl<'a> Hit<'a> { + pub fn new(ray: &Ray, t: f64, out_normal: Vector3<f64>, material: &'a(dyn Material)) -> Hit<'a> { + let front_face = dot(ray.dir, out_normal) < 0.0; + let normal = if front_face { out_normal } else { -out_normal }; + let p = ray.at(t); + Hit { t, p, normal, front_face, material } + } +} + +pub trait Hittable { + fn is_hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<Hit>; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4abc7c9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,35 @@ +use cgmath::vec3; +use rand::prelude::*; + +mod camera; +mod colour; +mod hittable; +mod material; +mod random; +mod ray; +mod render; +mod scene; +mod sphere; + +fn main() { + let image_width = 200; + let image_height = 100; + let samples = 50; + let max_depth = 25; + + println!("P3\n{} {}\n255", image_width, image_height); + + let mut world = scene::Scene::new(); + world.add(Box::new(sphere::Sphere::new( + vec3(0.0, 0.0, -1.0), 0.5, Box::new(material::Lambertian::new(vec3(1.0, 1.0, 1.0)))))); + world.add(Box::new(sphere::Sphere::new( + vec3(0.0, -100.5, -1.0), 100.0, Box::new(material::Lambertian::new(vec3(1.0, 1.0, 1.0)))))); + + let camera = camera::Camera::new(); + + let mut rng = thread_rng(); + + render::render(&world, camera, image_height, image_width, samples, max_depth, &mut rng); + + eprintln!("\nDone") +} diff --git a/src/material.rs b/src/material.rs new file mode 100644 index 0000000..3cbbb4a --- /dev/null +++ b/src/material.rs @@ -0,0 +1,26 @@ +use cgmath::Vector3; +use rand::prelude::*; +use crate::ray::{Ray, NextRay}; +use crate::hittable::Hit; +use crate::random::random_unit_vector; + +pub trait Material { + fn scatter(&self, rng: &mut ThreadRng, ray: &Ray, hit: &Hit) -> Option<NextRay>; +} + +pub struct Lambertian { + pub albedo: Vector3<f64> +} + +impl Lambertian { + pub fn new(albedo: Vector3<f64>) -> Lambertian { + Lambertian { albedo } + } +} + +impl Material for Lambertian { + fn scatter(&self, rng: &mut ThreadRng, _ray: &Ray, hit: &Hit) -> Option<NextRay> { + let scatter_direction = hit.normal + random_unit_vector(rng); + Some(NextRay::new(self.albedo, Ray::new(hit.p, scatter_direction))) + } +} diff --git a/src/random.rs b/src/random.rs new file mode 100644 index 0000000..66c637a --- /dev/null +++ b/src/random.rs @@ -0,0 +1,28 @@ +use cgmath::prelude::*; +use cgmath::{Vector3, vec3, dot}; +use rand::prelude::*; +use std::f64::consts::PI; + +pub fn random_vector3(rng: &mut ThreadRng, min: f64, max: f64) -> Vector3<f64> { + vec3(rng.gen_range(min, max), rng.gen_range(min, max), rng.gen_range(min, max)) +} + +pub fn random_in_unit_sphere(rng: &mut ThreadRng) -> Vector3<f64> { + loop { + let p = random_vector3(rng, -1.0, 1.0); + if p.magnitude2() <= 1.0 { return p } + } +} + +pub fn random_unit_vector(rng: &mut ThreadRng) -> Vector3<f64> { + let a = rng.gen_range(0.0, 2.0*PI); + let z = rng.gen_range(-1.0, 1.0); + let r = ((1.0 - z*z) as f64).sqrt(); + vec3(r*a.cos(), r*a.sin(), z) +} + +pub fn random_in_hemisphere(rng: &mut ThreadRng, normal: &Vector3<f64>) -> Vector3<f64> { + let in_unit_sphere = random_in_unit_sphere(rng); + if dot(in_unit_sphere, *normal) > 0.0 { in_unit_sphere } + else { -in_unit_sphere } +} diff --git a/src/ray.rs b/src/ray.rs new file mode 100644 index 0000000..08fee78 --- /dev/null +++ b/src/ray.rs @@ -0,0 +1,27 @@ +use cgmath::Vector3; + +pub struct Ray { + pub orig: Vector3<f64>, + pub dir: Vector3<f64> +} + +impl Ray { + pub fn new(orig: Vector3<f64>, dir: Vector3<f64>) -> Ray { + Ray {orig, dir} + } + + pub fn at(&self, t: f64) -> Vector3<f64> { + self.orig + t * self.dir + } +} + +pub struct NextRay { + pub attenuation: Vector3<f64>, + pub ray: Ray +} + +impl NextRay { + pub fn new(attenuation: Vector3<f64>, ray: Ray) -> NextRay { + NextRay { attenuation, ray } + } +} diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..6e2abf8 --- /dev/null +++ b/src/render.rs @@ -0,0 +1,44 @@ +use cgmath::prelude::*; +use cgmath::{Vector3, vec3}; +use rand::prelude::*; +use std::f64::INFINITY; +use crate::camera::Camera; +use crate::colour::print_colour; +use crate::scene::Scene; +use crate::random::random_in_unit_sphere; +use crate::ray::Ray; +use crate::hittable::Hittable; + +fn ray_colour(rng: &mut ThreadRng, ray: &Ray, scene: &Scene, depth: i32) -> Vector3<f64> { + if depth <= 0 { return vec3(0.0, 0.0, 0.0) } + + match scene.is_hit(ray, 0.001, INFINITY) { + None => { + let t = 0.5 * (ray.dir.normalize().y + 1.0); + (1.0 - t) * vec3(1.0, 1.0, 1.0) + t * vec3(0.5, 0.7, 1.0) + } + Some(t) => { + let target = t.p + t.normal + random_in_unit_sphere(rng); + 0.5 * ray_colour(rng, &Ray::new(t.p, target - t.p), scene, depth - 1) + } + } +} + +pub fn render(world: &Scene, camera: Camera, image_height: i32, image_width: i32, + samples: i32, max_depth: i32, rng: &mut ThreadRng) { + for j in 0 .. image_height { + eprint!("\rScanlines: {:3} / {:3}", j+1, image_height); + for i in 0 .. image_width { + let mut colour = vec3(0.0, 0.0, 0.0); + for _ in 0 .. samples { + let ru: f64 = rng.gen(); + let rv: f64 = rng.gen(); + let u = (i as f64 + ru) / image_width as f64; + let v = ((image_height - 1 - j) as f64 + rv) / image_height as f64; + let ray = camera.get_ray(u, v); + colour += ray_colour(rng, &ray, &world, max_depth); + } + print_colour(&colour, samples); + } + } +} diff --git a/src/scene.rs b/src/scene.rs new file mode 100644 index 0000000..368c784 --- /dev/null +++ b/src/scene.rs @@ -0,0 +1,35 @@ +use crate::hittable::{Hittable, Hit}; +use crate::ray::Ray; + +pub struct Scene { + objects: Vec<Box<dyn Hittable>> +} + +impl Scene { + pub fn new() -> Scene { + Scene { objects: Vec::new() } + } + + pub fn add(&mut self, obj: Box<dyn Hittable>) { + self.objects.push(obj) + } + + pub fn clear(&mut self) { + self.objects = Vec::new() + } +} + +impl Hittable for Scene { + fn is_hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<Hit> { + let mut closest_so_far = t_max; + let mut hit = None; + + for obj in self.objects.iter() { + if let Some(h) = obj.is_hit(ray, t_min, closest_so_far) { + closest_so_far = h.t; + hit = Some(h); + } + } + hit + } +} diff --git a/src/sphere.rs b/src/sphere.rs new file mode 100644 index 0000000..9ea9081 --- /dev/null +++ b/src/sphere.rs @@ -0,0 +1,40 @@ +use cgmath::prelude::*; +use cgmath::{Vector3, dot}; +use core::borrow::Borrow; +use crate::material::Material; +use crate::hittable::{Hittable, Hit}; +use crate::ray::Ray; + +pub struct Sphere { + center: Vector3<f64>, + radius: f64, + material: Box<dyn Material> +} + +impl Sphere { + pub fn new(center: Vector3<f64>, radius: f64, material: Box<dyn Material>) -> Sphere { + Sphere { center, radius, material } + } +} + +impl Hittable for Sphere { + fn is_hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<Hit> { + let oc = ray.orig - self.center; + let a = ray.dir.magnitude2(); + let b = dot(oc, ray.dir); + let c = oc.magnitude2() - self.radius * self.radius; + let discriminant = b*b - a*c; + + if discriminant > 0.0 { + let soln = (-b - discriminant.sqrt()) / a; + if soln < t_max && soln > t_min { + return Some(Hit::new(ray, soln, (ray.at(soln) - self.center) / self.radius, self.material.borrow())) + } + let soln = (-b + discriminant.sqrt()) / a; + if soln < t_max && soln > t_min { + return Some(Hit::new(ray, soln, (ray.at(soln) - self.center) / self.radius, self.material.borrow())) + } + } + None + } +} |