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
 99
100
use crate::{Vec3, Ray, RayIntersection, Intersect};

/// A sphere primitive.
pub struct Sphere {
    origin: Vec3,
    radius: f32,
}

impl Sphere {
    /// Create a new Sphere.
    ///
    /// # Examples
    /// ```
    /// use obscura::{Sphere, Vec3};
    /// 
    /// let origin = Vec3::zero();
    /// let x = Sphere::new(&origin, 1.0);
    /// ```
    pub fn new(origin: &Vec3, radius: f32) -> Sphere {
        Sphere {
            origin: Vec3::new(origin.x, origin.y, origin.z),
            radius: radius
        }
    }
}

impl Intersect for Sphere {
    /// Intersect ray with sphere.
    /// Return t or t1,t2 where intersection point p = ray.origin + t * ray.direction
    /// 
    /// # Examples
    /// ```
    /// use obscura::{Sphere, Vec3, Ray, RayIntersection, Intersect};
    /// 
    /// let origin = Vec3::zero();
    /// 
    /// let target: Sphere = Sphere::new(
    ///     &Vec3::new(2.0, 0.0, 0.0),
    ///     1.0
    /// );
    /// 
    /// // A ray that intersects the sphere twice.
    /// let ray1: Ray = Ray::new(
    ///     &origin,
    ///     &Vec3::new(1.0, 0.0, 0.0)
    /// );
    /// 
    /// if let RayIntersection::Hit(t1, t2) = Sphere::intersect(&target, &ray1) {
    ///     assert_eq!(t1, 1.0);
    ///     assert_eq!(t2, 3.0);
    /// } else {
    ///     panic!("Oh no!");
    /// }
    /// 
    /// // A ray which never intersects the sphere.
    /// let ray2: Ray = Ray::new(
    ///     &origin,
    ///     &Vec3::new(0.0, 0.0, 1.0)
    /// );
    /// 
    /// let result = Sphere::intersect(&target, &ray2);
    /// assert_eq!(result, RayIntersection::Miss);
    /// ```
    fn intersect(&self, ray: &Ray) -> RayIntersection {
        // http://viclw17.github.io/2018/07/16/raytracing-ray-sphere-intersection/

        let target = self;

        let a = Vec3::dot(&ray.direction, &ray.direction);
        let b = 2.0 * Vec3::dot(
            &ray.direction,
            &Vec3::sub(&ray.origin, &target.origin)
        );
        let c = Vec3::dot(
            &Vec3::sub(&ray.origin, &target.origin), 
            &Vec3::sub(&ray.origin, &target.origin) 
        ) - (target.radius * target.radius);

        let discriminant = b*b - 4.0*a*c;

        if discriminant < 0.0 {

            RayIntersection::Miss

        } else if discriminant == 1.0 {

            let t = (-b + f32::sqrt(discriminant)) / (2.0 * a);

            RayIntersection::Tangent(t)

        } else {

            let t1 = ((-b) - f32::sqrt(discriminant)) / (2.0 * a);
            let t2 = ((-b) + f32::sqrt(discriminant)) / (2.0 * a);

            RayIntersection::Hit(t1, t2)

        }
    }
}