77 lines
2.1 KiB
Rust
77 lines
2.1 KiB
Rust
|
use anyhow::Result;
|
||
|
use nalgebra::Vector3;
|
||
|
use ordered_float::NotNan;
|
||
|
|
||
|
use crate::{ray::Ray, utils::min_f64};
|
||
|
|
||
|
use super::data::{IntersectionContext, ObjectKind};
|
||
|
|
||
|
#[derive(Debug)]
|
||
|
pub struct Sphere {
|
||
|
pub center: Vector3<f64>,
|
||
|
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<Option<IntersectionContext>> {
|
||
|
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,
|
||
|
}))
|
||
|
}
|
||
|
}
|