299 lines
11 KiB
Rust
299 lines
11 KiB
Rust
use crate::materials::Material;
|
|
use crate::math::Vec3;
|
|
|
|
use super::{BoundingBox, HasBoundingBox, Intersect, IntersectionInfo, Primitive, Ray};
|
|
|
|
use std::sync::Arc;
|
|
|
|
#[derive(Clone)]
|
|
pub struct Plane {
|
|
normal: Vec3,
|
|
tangent: Vec3,
|
|
cotangent: Vec3,
|
|
distance_from_origin: f64,
|
|
material: Arc<dyn Material>,
|
|
}
|
|
|
|
impl Plane {
|
|
pub fn new(normal: Vec3, distance_from_origin: f64, material: Arc<dyn Material>) -> Plane {
|
|
let normal = normal.normalize();
|
|
let mut axis_closest_to_tangent = Vec3::zeros();
|
|
axis_closest_to_tangent[normal.smallest_coord()] = 1.0;
|
|
let cotangent = normal.cross(&axis_closest_to_tangent).normalize();
|
|
let tangent = normal.cross(&cotangent);
|
|
Plane {
|
|
normal,
|
|
tangent,
|
|
cotangent,
|
|
distance_from_origin,
|
|
material,
|
|
}
|
|
}
|
|
}
|
|
|
|
/*impl Transform for Plane {
|
|
fn transform(&self, transformation: &Affine3<f64>) -> Self {
|
|
Plane {
|
|
normal: transformation.transform_vector(&self.normal).normalize(),
|
|
cotangent: transformation.transform_vector(&self.cotangent).normalize(),
|
|
tangent: self.normal.cross(&self.cotangent),
|
|
distance_from_origin: transformation
|
|
.transform_vector(&(self.normal * self.distance_from_origin))
|
|
.norm(),
|
|
material: Arc::clone(&self.material),
|
|
}
|
|
}
|
|
}*/
|
|
|
|
impl Intersect for Plane {
|
|
fn intersect(&self, ray: &Ray) -> Option<IntersectionInfo> {
|
|
let ray_direction_dot_plane_normal = ray.direction.dot(&self.normal);
|
|
let point_on_plane = self.normal * self.distance_from_origin;
|
|
let point_on_plane_minus_ray_origin_dot_normal =
|
|
(point_on_plane - ray.origin).dot(&self.normal);
|
|
if ray_direction_dot_plane_normal == 0.0 {
|
|
//Ray is parallel to plane
|
|
if point_on_plane_minus_ray_origin_dot_normal != 0.0 {
|
|
//Ray is not in plane
|
|
return None;
|
|
}
|
|
}
|
|
let t = point_on_plane_minus_ray_origin_dot_normal / ray_direction_dot_plane_normal;
|
|
if t < 0.0 {
|
|
return None;
|
|
}
|
|
Some(IntersectionInfo {
|
|
distance: t,
|
|
location: ray.point_at(t),
|
|
normal: self.normal,
|
|
tangent: self.tangent,
|
|
cotangent: self.cotangent,
|
|
retro: -ray.direction,
|
|
material: Arc::clone(&self.material),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl HasBoundingBox for Plane {
|
|
fn bounding_box(&self) -> BoundingBox {
|
|
let p0 = self.normal * self.distance_from_origin;
|
|
let f = |v: Vec3| {
|
|
Vec3::new(
|
|
if v.x() == 0.0 {
|
|
0.0
|
|
} else {
|
|
std::f64::INFINITY
|
|
},
|
|
if v.y() == 0.0 {
|
|
0.0
|
|
} else {
|
|
std::f64::INFINITY
|
|
},
|
|
if v.z() == 0.0 {
|
|
0.0
|
|
} else {
|
|
std::f64::INFINITY
|
|
},
|
|
)
|
|
};
|
|
let tangent = f(self.tangent);
|
|
let cotangent = f(self.cotangent);
|
|
let p1 = p0 + tangent;
|
|
let p2 = p0 - tangent;
|
|
let p3 = p0 + cotangent;
|
|
let p4 = p0 - cotangent;
|
|
BoundingBox::from_points(&[p1, p2, p3, p4])
|
|
}
|
|
}
|
|
|
|
impl Primitive for Plane {}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
|
|
use super::*;
|
|
use crate::materials::LambertianMaterial;
|
|
use crate::math::Vec3;
|
|
|
|
#[test]
|
|
fn ray_intersects_plane() {
|
|
let r = Ray::new(Vec3::new(1.0, 2.0, 3.0), Vec3::new(-1.0, 0.0, 1.0));
|
|
let p = Plane::new(
|
|
Vec3::new(1.0, 0.0, 0.0),
|
|
-5.0,
|
|
Arc::new(LambertianMaterial::new_dummy()),
|
|
);
|
|
if let None = p.intersect(&r) {
|
|
panic!("Intersection failed.");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ray_does_not_intersect_plane() {
|
|
let r = Ray::new(Vec3::new(1.0, 2.0, 3.0), Vec3::new(1.0, 0.0, 1.0));
|
|
let p = Plane::new(
|
|
Vec3::new(1.0, 0.0, 0.0),
|
|
-5.0,
|
|
Arc::new(LambertianMaterial::new_dummy()),
|
|
);
|
|
if let Some(_) = p.intersect(&r) {
|
|
panic!("Intersection failed.");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn intersection_point_is_on_plane() {
|
|
let r = Ray::new(Vec3::new(1.0, 2.0, 3.0), Vec3::new(-1.0, 0.0, 1.0));
|
|
let p = Plane::new(
|
|
Vec3::new(1.0, 0.0, 0.0),
|
|
-5.0,
|
|
Arc::new(LambertianMaterial::new_dummy()),
|
|
);
|
|
match p.intersect(&r) {
|
|
Some(IntersectionInfo {
|
|
distance: _,
|
|
location,
|
|
normal: _,
|
|
tangent: _,
|
|
cotangent: _,
|
|
retro: _,
|
|
material: _,
|
|
}) => assert!((location.x() - (-5.0f64)).abs() < 0.0000000001),
|
|
None => panic!(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn bounding_box_is_correct_for_yz_plane() {
|
|
let target = Plane::new(
|
|
Vec3::new(1.0, 0.0, 0.0),
|
|
2.0,
|
|
Arc::new(LambertianMaterial::new_dummy()),
|
|
);
|
|
let bb = target.bounding_box();
|
|
assert!(!bb.contains_point(Vec3::new(1.0, 2.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(2.0, 2.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(2.0, 2000.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(2.0, 0.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(2.0, -2000.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(2.0, 2.0, 3000.0)));
|
|
assert!(bb.contains_point(Vec3::new(2.0, 2.0, 0.0)));
|
|
assert!(bb.contains_point(Vec3::new(2.0, 2.0, -3000.0)));
|
|
assert!(!bb.contains_point(Vec3::new(3.0, 2.0, 3.0)));
|
|
}
|
|
|
|
#[test]
|
|
fn bounding_box_is_correct_for_yz_plane_with_negative_normal() {
|
|
let target = Plane::new(
|
|
Vec3::new(-1.0, 0.0, 0.0),
|
|
2.0,
|
|
Arc::new(LambertianMaterial::new_dummy()),
|
|
);
|
|
let bb = target.bounding_box();
|
|
assert!(!bb.contains_point(Vec3::new(1.0, 2.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(-2.0, 2.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(-2.0, 2000.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(-2.0, 0.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(-2.0, -2000.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(-2.0, 2.0, 3000.0)));
|
|
assert!(bb.contains_point(Vec3::new(-2.0, 2.0, 0.0)));
|
|
assert!(bb.contains_point(Vec3::new(-2.0, 2.0, -3000.0)));
|
|
assert!(!bb.contains_point(Vec3::new(-3.0, 2.0, 3.0)));
|
|
}
|
|
|
|
#[test]
|
|
fn bounding_box_is_correct_for_xz_plane() {
|
|
let target = Plane::new(
|
|
Vec3::new(0.0, 1.0, 0.0),
|
|
2.0,
|
|
Arc::new(LambertianMaterial::new_dummy()),
|
|
);
|
|
let bb = target.bounding_box();
|
|
assert!(!bb.contains_point(Vec3::new(1.0, 1.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(1000.0, 2.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(0.0, 2.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(-1000.0, 2.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 3000.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 0.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, 2.0, -3000.0)));
|
|
assert!(!bb.contains_point(Vec3::new(1.0, 3.0, 3.0)));
|
|
}
|
|
|
|
#[test]
|
|
fn bounding_box_is_correct_for_xz_plane_with_negative_normal() {
|
|
let target = Plane::new(
|
|
Vec3::new(0.0, -1.0, 0.0),
|
|
2.0,
|
|
Arc::new(LambertianMaterial::new_dummy()),
|
|
);
|
|
let bb = target.bounding_box();
|
|
assert!(!bb.contains_point(Vec3::new(1.0, -1.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, -2.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(1000.0, -2.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(0.0, -2.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(-1000.0, -2.0, 3.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, -2.0, 3000.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, -2.0, 0.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, -2.0, -3000.0)));
|
|
assert!(!bb.contains_point(Vec3::new(1.0, 3.0, 3.0)));
|
|
}
|
|
|
|
#[test]
|
|
fn bounding_box_is_correct_for_xy_plane() {
|
|
let target = Plane::new(
|
|
Vec3::new(0.0, 0.0, 1.0),
|
|
2.0,
|
|
Arc::new(LambertianMaterial::new_dummy()),
|
|
);
|
|
let bb = target.bounding_box();
|
|
assert!(!bb.contains_point(Vec3::new(1.0, 2.0, 1.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, 2000.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, 0.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, -2000.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(2000.0, 2.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(0.0, 2.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(-2000.0, 2.0, 2.0)));
|
|
assert!(!bb.contains_point(Vec3::new(3.0, 2.0, 3.0)));
|
|
}
|
|
|
|
#[test]
|
|
fn bounding_box_is_correct_for_xy_plane_with_negative_normal() {
|
|
let target = Plane::new(
|
|
Vec3::new(0.0, 0.0, -1.0),
|
|
-2.0,
|
|
Arc::new(LambertianMaterial::new_dummy()),
|
|
);
|
|
let bb = target.bounding_box();
|
|
assert!(!bb.contains_point(Vec3::new(1.0, 2.0, 1.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, 2000.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, 0.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, -2000.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(2000.0, 2.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(0.0, 2.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(-2000.0, 2.0, 2.0)));
|
|
assert!(!bb.contains_point(Vec3::new(3.0, 2.0, 3.0)));
|
|
}
|
|
|
|
#[test]
|
|
fn bounding_box_is_infinite_when_normal_is_not_aligned_with_axis() {
|
|
let target = Plane::new(
|
|
Vec3::new(0.1, 0.0, -1.0),
|
|
-2.0,
|
|
Arc::new(LambertianMaterial::new_dummy()),
|
|
);
|
|
let bb = target.bounding_box();
|
|
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 1.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, 2000.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, 0.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(1.0, -2000.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(2000.0, 2.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(0.0, 2.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(-2000.0, 2.0, 2.0)));
|
|
assert!(bb.contains_point(Vec3::new(3.0, 2.0, 3.0)));
|
|
}
|
|
}
|