use anyhow::Result; use nalgebra::Vector3; use ordered_float::NotNan; use crate::{ray::Ray, utils::min_f64}; use super::{data::ObjectKind, illumination::IntersectionContext}; #[derive(Debug)] pub struct Sphere { pub center: Vector3, pub radius: f64, } impl ObjectKind for Sphere { /// Given a sphere, returns the first time at which this ray intersects the /// sphere. /// /// If there is no intersection point, returns None. fn intersects_ray_at( &self, ray: &Ray, ) -> Result> { let a = ray.direction.x.powi(2) + ray.direction.y.powi(2) + ray.direction.z.powi(2); let b = 2.0 * (ray.direction.x * (ray.origin.x - self.center.x) + ray.direction.y * (ray.origin.y - self.center.y) + ray.direction.z * (ray.origin.z - self.center.z)); let c = (ray.origin.x - self.center.x).powi(2) + (ray.origin.y - self.center.y).powi(2) + (ray.origin.z - self.center.z).powi(2) - self.radius.powi(2); let discriminant = b * b - 4.0 * a * c; let time = match discriminant { // Discriminant < 0, means the equation has no solutions. d if d < 0.0 => None, // Discriminant == 0 d if d == 0.0 => Some(-b / (2.0 * a)), d if d > 0.0 => { let solution_1 = (-b + discriminant.sqrt()) / (2.0 * a); let solution_2 = (-b - discriminant.sqrt()) / (2.0 * a); let solutions = [solution_1, solution_2] .into_iter() // Remove any t < 0, since that means it's behind the viewer and we // can't see it. .filter(|t| *t >= 0.0); // Return the minimum solution min_f64(solutions) } // Probably hit some NaN or Infinity value due to faulty inputs... _ => unreachable!("Invalid determinant value: {discriminant}"), }; let time = match time.and_then(|t| NotNan::new(t).ok()) { Some(v) => v, None => return Ok(None), }; let point = ray.eval(*time); let normal = (point - self.center).normalize(); Ok(Some(IntersectionContext { time, point, normal, })) } }