library: shape: add Sphere implementation

This commit is contained in:
Bruno BELANYI 2020-03-16 17:27:31 +01:00
parent 7d28e21a70
commit 7c8cc0d970

165
src/shape/sphere.rs Normal file
View file

@ -0,0 +1,165 @@
use super::super::{Point, Point2D, Vector};
use super::Shape;
use bvh::aabb::{Bounded, AABB};
use bvh::ray::Ray;
/// Represent a sphere shape inside the scene.
#[derive(Debug, PartialEq)]
pub struct Sphere {
/// The sphere is inverted if it is expected to be seen from the inside.
inverted: bool,
/// The center of the sphere in space.
center: Point,
/// The radius of the sphere being rendered.
radius: f32,
}
impl Sphere {
/// Return a sphere which should be rendered as seen from the outside.
pub fn new(center: Point, radius: f32) -> Self {
Sphere {
center,
radius,
inverted: false,
}
}
/// Return a sphere which should be rendered as seen from the inside.
pub fn inverted_new(center: Point, radius: f32) -> Self {
Sphere {
center,
radius,
inverted: true,
}
}
}
impl Bounded for Sphere {
fn aabb(&self) -> AABB {
let delt = Vector::new(self.radius, self.radius, self.radius);
let min = self.center - delt;
let max = self.center + delt;
AABB::with_bounds(min, max)
}
}
impl Shape for Sphere {
fn intersect(&self, ray: &Ray) -> Option<f32> {
use std::mem;
let delt = self.center - ray.origin;
let tca = ray.direction.dot(&delt);
let d2 = delt.norm_squared() - tca * tca;
let r_2 = self.radius * self.radius;
if d2 > r_2 {
return None;
}
let thc = (r_2 - d2).sqrt();
let mut t_0 = tca - thc;
let mut t_1 = tca + thc;
if t_0 > t_1 {
mem::swap(&mut t_0, &mut t_1)
}
if t_0 < 0. {
t_0 = t_1
}
if t_0 < 0. {
None
} else {
Some(t_0)
}
}
fn normal(&self, point: &Point) -> Vector {
let delt = if self.inverted {
self.center - point
} else {
point - self.center
};
delt.normalize()
}
fn project_texel(&self, point: &Point) -> Point2D {
// Project the sphere on the XY-plane
Point2D::new(
0.5 + (point.x - self.center.x) / (2. * self.radius),
0.5 + (point.y - self.center.y) / (2. * self.radius),
)
}
}
#[cfg(test)]
mod test {
use super::*;
fn simple_sphere() -> Sphere {
Sphere::new(Point::origin(), 1.)
}
#[test]
fn intersect_along_axis_works() {
let sphere = simple_sphere();
let ray = Ray::new(Point::new(-2., 0., 0.), Vector::new(1., 0., 0.));
assert_eq!(sphere.intersect(&ray), Some(1.))
}
#[test]
fn non_intersect_along_axis_works() {
let sphere = simple_sphere();
let ray = Ray::new(Point::new(-2., 0., 0.), Vector::new(-1., 0., 0.));
assert_eq!(sphere.intersect(&ray), None)
}
#[test]
fn intersect_not_on_axis() {
let sphere = simple_sphere();
let ray = Ray::new(Point::new(1., 1., 1.), Vector::new(-1., -1., -1.));
assert_eq!(sphere.intersect(&ray), Some(f32::sqrt(3.) - 1.))
}
#[test]
fn normal_works() {
let sphere = simple_sphere();
assert_eq!(
sphere.normal(&Point::new(-1., 0., 0.)),
Vector::new(-1., 0., 0.)
)
}
#[test]
fn inverted_normal_works() {
let sphere = Sphere::inverted_new(Point::origin(), 1.);
assert_eq!(
sphere.normal(&Point::new(-1., 0., 0.)),
Vector::new(1., 0., 0.)
)
}
#[test]
fn projection_works_1() {
let sphere = simple_sphere();
let projection = sphere.project_texel(&Point::new(-1., -1., 1.));
assert!(projection.x.abs() < 1e-5);
assert!(projection.y.abs() < 1e-5)
}
#[test]
fn projection_works_2() {
let sphere = simple_sphere();
let projection = sphere.project_texel(&Point::new(1., -1., 1.));
assert!((projection.x - 1.).abs() < 1e-5);
assert!(projection.y.abs() < 1e-5)
}
#[test]
fn projection_works_3() {
let sphere = simple_sphere();
let projection = sphere.project_texel(&Point::new(1., 0., 1.));
assert!((projection.x - 1.).abs() < 1e-5);
assert!((projection.y - 0.5).abs() < 1e-5)
}
}