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 /src | |
download | leela-9f4cdae92c8df0a7990fdb77d9ff161b2eebf467.tar.gz leela-9f4cdae92c8df0a7990fdb77d9ff161b2eebf467.zip |
Add initial files
Diffstat (limited to 'src')
-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 |
10 files changed, 299 insertions, 0 deletions
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 + } +} |