aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorYann Herklotz <git@yannherklotz.com>2020-04-26 21:15:53 +0100
committerYann Herklotz <git@yannherklotz.com>2020-04-26 21:15:53 +0100
commit9f4cdae92c8df0a7990fdb77d9ff161b2eebf467 (patch)
treeeb1e8c4de3b6040859c88fb8c2ea69580a73ec10 /src
downloadleela-9f4cdae92c8df0a7990fdb77d9ff161b2eebf467.tar.gz
leela-9f4cdae92c8df0a7990fdb77d9ff161b2eebf467.zip
Add initial files
Diffstat (limited to 'src')
-rw-r--r--src/camera.rs25
-rw-r--r--src/colour.rs15
-rw-r--r--src/hittable.rs24
-rw-r--r--src/main.rs35
-rw-r--r--src/material.rs26
-rw-r--r--src/random.rs28
-rw-r--r--src/ray.rs27
-rw-r--r--src/render.rs44
-rw-r--r--src/scene.rs35
-rw-r--r--src/sphere.rs40
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
+ }
+}