use core::f32::math::sqrt; use std::f32::consts::PI; use std::fmt::{self, Debug}; use std::sync::Arc; use crate::Vec3; use crate::objects::hit::Hit; use crate::objects::materials::traits::Material; use crate::objects::traits::Hittable; use crate::ray::Ray; pub struct Sphere { center: Vec3, radius: f32, material: Arc, } impl Debug for Sphere { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Sphere") .field("center", &self.center) .field("radius", &self.radius) .field("material", &self.material) .finish() } } impl Sphere { pub fn new(center: Vec3, r: f32, mat: Arc) -> Self { Self { center, radius: r, material: mat, } } pub fn xyz(x: f32, y: f32, z: f32, r: f32, mat: Arc) -> Self { Self { center: Vec3::new(x, y, z), radius: r, material: mat, } } } impl Hittable for Sphere { fn hit(&self, r: &Ray) -> Option { let oc = self.center - r.origin(); let a = r.dir().length_squared(); let h = r.dir().dot(&oc); let c = oc.length_squared() - self.radius * self.radius; let d = h * h - a * c; if d < 0. { None } else { let tl = (h - sqrt(d)) / a; let tr = (h + sqrt(d)) / a; let t = if tl > 0.001 { tl } else { tr }; let p = r.at(t); let out_n = (p - self.center) / self.radius; let uv = self.to_uv(&p); Some(Hit::new( t, p, (p - self.center).get_unit(), self.material.clone(), out_n.dot(r.dir()) < 0., *uv.x(), *uv.y(), )) } } fn to_uv(&self, point: &Vec3) -> Vec3 { let p = *point - self.center; // TODO: add rotated texture support Vec3::new( 0.5 + p.y().atan2(*p.x()) / (2. * PI), 1. - (p.z() / self.radius).acos() / PI, 0.0, ) } }