use crate::{Point3, Ray, Vec3}; use crate::material::{Material}; use crate::aabb::Aabb; use std::f64::consts::PI; pub type HittableList = Vec<Box<dyn Hittable + Sync>>; //#[derive(Debug, Clone, Copy)] #[derive(Clone, Copy)] pub struct HitRecord<'material> { pub point: Point3, pub normal: Vec3, pub t: f64, pub u: f64, pub v: f64, pub front_face: bool, pub material: &'material Material } pub trait Hittable { fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord>; fn bounding_box(&self, time0: f64, time1: f64) -> Option<Aabb>; } //#[derive(Clone)] pub struct Sphere { pub center: Point3, pub radius: f64, pub material: Material } impl Sphere { pub fn new(center: Point3, radius: f64, material: Material) -> Sphere { Sphere { center, radius, material } } pub fn uv(point: &Point3) -> (f64, f64) { // p: a given point on the sphere of radius one, centered at the origin. // u: returned value [0,1] of angle around the Y axis from X=-1. // v: returned value [0,1] of angle from Y=-1 to Y=+1. // <1 0 0> yields <0.50 0.50> <-1 0 0> yields <0.00 0.50> // <0 1 0> yields <0.50 1.00> < 0 -1 0> yields <0.50 0.00> // <0 0 1> yields <0.25 0.50> < 0 0 -1> yields <0.75 0.50> let theta = -(point.y()).acos(); let phi = -(point.z()).atan2(point.x()) + PI; (phi / (2.0 * PI), theta / PI) } } impl Default for Sphere { fn default() -> Self { Sphere { center: Point3::default(), radius: 0.0, material: Material::default() } } } impl Hittable for Sphere { fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> { let oc = &ray.origin() - &self.center; let a = ray.direction().length_squared(); let half_b = oc.dot(&ray.direction()); let c = oc.length_squared() - &self.radius * &self.radius; let discriminant = half_b * half_b - a * c; if discriminant < 0.0 { return None; } let sqrtd = discriminant.sqrt(); let mut root = (-half_b - sqrtd) / a; if root < t_min || t_max < root { root = (-half_b + sqrtd) / a; if root < t_min || t_max < root { return None; } } let point = ray.at(root); let normal = (point - self.center) / self.radius; let dot = ray.direction().dot(&normal); let front_face = dot < 0.0; let (u, v) = Sphere::uv(&normal); let normal = if front_face { normal } else { -normal }; Some(HitRecord { point, normal, t: root, u, v, front_face, material: &self.material }) } fn bounding_box(&self, _time0: f64, _time1: f64) -> Option<Aabb> { let s = Vec3::new(self.radius, self.radius, self.radius); Some(Aabb { minimum: self.center - s, maximum: self.center + s, }) } } //#[derive(Clone)] pub struct MovableSphere { pub center0: Point3, pub center1: Point3, pub radius: f64, pub material: Material, pub time0: f64, pub time1: f64, } impl MovableSphere { pub fn new( center0: Point3, center1: Point3, radius: f64, material: Material, time0: f64, time1: f64) -> Self { MovableSphere { center0, center1, radius, material, time0, time1 } } pub fn center(&self, time: f64) -> Point3 { self.center0 + ((time - self.time0) / (self.time1 - self.time0))*(self.center1 - self.center0) } } impl Default for MovableSphere { fn default() -> Self { MovableSphere { center0: Point3::default(), center1: Point3::default(), radius: 0.0, material: Material::default(), time0: 0.0, time1: 0.0 } } } impl Hittable for MovableSphere { fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> { let oc = &ray.origin() - &self.center(ray.time()); let a = ray.direction().length_squared(); let half_b = oc.dot(&ray.direction()); let c = oc.length_squared() - &self.radius * &self.radius; let discriminant = half_b * half_b - a * c; if discriminant < 0.0 { return None; } let sqrtd = discriminant.sqrt(); let mut root = (-half_b - sqrtd) / a; if root < t_min || t_max < root { root = (-half_b + sqrtd) / a; if root < t_min || t_max < root { return None; } } let point = ray.at(root); let normal = (point - self.center(ray.time())) / self.radius; let dot = ray.direction().dot(&normal); let front_face = dot < 0.0; let (u, v) = Sphere::uv(&normal); let normal = if front_face { normal } else { -normal }; Some(HitRecord { point, normal, t: root, u, v, front_face, material: &self.material, }) } fn bounding_box(&self, time0: f64, time1: f64) -> Option<Aabb> { let s = Vec3::new(self.radius, self.radius, self.radius); let center0 = self.center(time0); let center1 = self.center(time1); Some(Aabb { minimum: center0 - s, maximum: center0 + s, }.surrounding(&Aabb { minimum: center1 - s, maximum: center1 + s })) } }