1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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;

/// A geometrically perfect sphere.
#[derive(Copy, Clone, Default, Debug, PartialEq, Transformable)]
#[internal]
pub struct Sphere {
    #[transform]
    pub transform: Transform,
}

impl Sphere {
    /// Create a new [`Sphere`].
    pub fn new() -> Self {
        Sphere {
            transform: Transform::default(),
        }
    }

    /// Map a point on the sphere to UV coordinates.
    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);

        // If the ray origin is outside of the sphere,
        // tangent_squared is the square of the vector from the ray origin to a tangent point on the sphere.
        // If it is inside, tangent_squared will be < 0, but the formulas below still hold.
        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 {
            // Ray origin is outside the sphere.
            -projection - discriminant.sqrt()
        } else {
            // Ray origin is inside the sphere.
            -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))
    }
}