aboutsummaryrefslogtreecommitdiffstats
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
downloadleela-9f4cdae92c8df0a7990fdb77d9ff161b2eebf467.tar.gz
leela-9f4cdae92c8df0a7990fdb77d9ff161b2eebf467.zip
Add initial files
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock284
-rw-r--r--Cargo.toml11
-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
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
+ }
+}