use std::f64::consts::FRAC_1_PI;
use crystal_ball_derive::Transformable;
use crate::math::{Bounds3, Hit, Point2, Point3, Ray, Transform, Vec3};
use crate::shapes::Shape;
#[derive(Copy, Clone, Default, Debug, PartialEq, Transformable)]
#[internal]
pub struct Sphere {
#[transform]
pub transform: Transform,
}
impl Sphere {
pub fn new() -> Self {
Sphere {
transform: Transform::default(),
}
}
pub fn uv_map(&self, point: Point3) -> Point2 {
let u = 0.5 + point.z.atan2(point.x) * 0.5 * FRAC_1_PI;
let v = 0.5 - point.y.asin() * FRAC_1_PI;
Point2::new(u, v)
}
}
impl Shape for Sphere {
fn intersects(&self, ray: Ray) -> Option<Hit> {
let ray = self.transform.inverse_transform_ray(ray);
let tangent_squared = ray.origin.to_vec3().magnitude_squared() - 1.0;
let projection = Vec3::dot(ray.direction, ray.origin.into());
let discriminant = projection.powi(2) - tangent_squared;
if discriminant <= 0.0 {
return None;
}
let mut intersection_distance = if tangent_squared > 0.0 {
-projection - discriminant.sqrt()
} else {
-projection + discriminant.sqrt()
};
if intersection_distance < 0.0 {
return None;
}
let mut intersection_point = ray.get(intersection_distance);
let mut normal = intersection_point.to_vec3();
let uv = self.uv_map((-normal).into());
let ray = self.transform.transform_ray(ray);
intersection_point = self.transform.mat4 * intersection_point;
intersection_distance = (intersection_point - ray.origin).magnitude();
normal = (self.transform.mat4_inverse.transpose() * normal).normalize();
let mat4 = self.transform.mat4;
let origin = Point3::new(mat4[0][3], mat4[1][3], mat4[2][3]);
let mut tangent = Vec3::cross(Vec3::Y, intersection_point - origin)
.normalize()
.extend(1.0);
if Vec3::dot(ray.direction, normal) > 0.0 {
normal = -normal;
tangent = -tangent;
}
Some(Hit::new(
intersection_point,
normal,
Some(tangent),
intersection_distance,
Point2::new(1.0 - uv.x, 1.0 - uv.y),
))
}
fn bounds(&self) -> Bounds3 {
let min = Point3::splat(-1.0);
let max = Point3::splat(1.0);
self.transform.transform_bounds(Bounds3::new(min, max))
}
}