library: shape: add Sphere implementation
This commit is contained in:
parent
7d28e21a70
commit
7c8cc0d970
165
src/shape/sphere.rs
Normal file
165
src/shape/sphere.rs
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue