pathtracer/src/shape/triangle.rs
2020-03-23 12:44:17 +01:00

243 lines
6.4 KiB
Rust

use super::Shape;
use crate::{Point, Point2D, Vector};
use bvh::aabb::AABB;
use bvh::ray::Ray;
use serde::{Deserialize, Deserializer};
/// Represent a triangle inside the scene.
#[derive(Clone, Debug, PartialEq)]
pub struct Triangle {
c0: Point,
c0c1: Vector,
c0c2: Vector,
}
impl Triangle {
/// Creates a new `Triangle` from 3 [`Point`]s.
///
/// [`Point`]: ../../type.Point.html
///
/// # Examples
///
/// ```
/// # use pathtracer::shape::Triangle;
/// # use pathtracer::Point;
/// #
/// let t = Triangle::new(
/// Point::new(1.0, 0.0, 0.0),
/// Point::new(0.0, 1.0, 0.0),
/// Point::new(0.0, 0.0, 1.0),
/// );
/// ```
pub fn new(c0: Point, c1: Point, c2: Point) -> Self {
Triangle {
c0,
c0c1: c1 - c0,
c0c2: c2 - c0,
}
}
fn barycentric(&self, point: &Point) -> Point2D {
let c0_pos = point - self.c0;
// P - A = u * (B - A) + v * (C - A)
// (C - A) = v0 is c0c2
// (B - A) = v1 is c0c1
// (P - A) = v2 is c0_pos
let dot00 = self.c0c2.dot(&self.c0c2);
let dot01 = self.c0c2.dot(&self.c0c1);
let dot02 = self.c0c2.dot(&c0_pos);
let dot11 = self.c0c1.dot(&self.c0c1);
let dot12 = self.c0c1.dot(&c0_pos);
let inv_denom = 1. / (dot00 * dot11 - dot01 * dot01);
let u = (dot00 * dot12 - dot01 * dot02) * inv_denom;
let v = (dot11 * dot02 - dot01 * dot12) * inv_denom;
Point2D::new(u, v)
}
}
impl Shape for Triangle {
fn intersect(&self, ray: &Ray) -> Option<f32> {
let pvec = ray.direction.cross(&self.c0c2);
let det = self.c0c1.dot(&pvec);
if det.abs() < 1e-5 {
return None;
}
let to_ray = ray.origin - self.c0;
let inv_det = 1. / det;
let u = to_ray.dot(&pvec) * inv_det;
if u < 0. || u > 1. {
return None;
}
let qvec = to_ray.cross(&self.c0c1);
let v = ray.direction.dot(&qvec) * inv_det;
if v < 0. || u + v > 1. {
return None;
}
let t = self.c0c2.dot(&qvec) * inv_det;
if t < 0. {
None
} else {
Some(t)
}
}
fn normal(&self, _: &Point) -> Vector {
self.c0c1.cross(&self.c0c2).normalize()
}
fn project_texel(&self, point: &Point) -> Point2D {
self.barycentric(point)
}
fn aabb(&self) -> AABB {
AABB::empty()
.grow(&self.c0)
.grow(&(self.c0 + self.c0c1))
.grow(&(self.c0 + self.c0c2))
}
}
#[derive(Debug, Deserialize)]
struct SerializedTriangle {
corners: [Point; 3],
}
impl From<SerializedTriangle> for Triangle {
fn from(triangle: SerializedTriangle) -> Self {
Triangle::new(
triangle.corners[0],
triangle.corners[1],
triangle.corners[2],
)
}
}
impl<'de> Deserialize<'de> for Triangle {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let cam: SerializedTriangle = Deserialize::deserialize(deserializer)?;
Ok(cam.into())
}
}
#[cfg(test)]
mod test {
use super::*;
fn simple_triangle() -> Triangle {
Triangle::new(
Point::origin(),
Point::new(0., 1., 1.),
Point::new(0., 1., 0.),
)
}
#[test]
fn intersect_along_normal_works() {
let triangle = simple_triangle();
let ans = triangle.intersect(&Ray::new(
Point::new(-1., 0.5, 0.5),
Vector::new(1., 0., 0.),
));
assert_eq!(ans, Some(1.0))
}
#[test]
fn intersect_at_angle_works() {
let triangle = simple_triangle();
let ans = triangle.intersect(&Ray::new(
Point::new(-1., 0.5, 0.),
Vector::new(1., 0., 0.5),
));
assert!(ans.is_some());
assert!((ans.unwrap() - f32::sqrt(1.0 + 0.25)).abs() < 1e-5)
}
#[test]
fn intersect_out_of_bounds_is_none() {
let triangle = simple_triangle();
let ans = triangle.intersect(&Ray::new(Point::new(-1., 0.5, 0.), Vector::new(1., 1., 1.)));
assert_eq!(ans, None)
}
#[test]
fn normal_works() {
let triangle = simple_triangle();
let normal = triangle.normal(&Point::origin());
assert_eq!(normal, Vector::new(-1., 0., 0.));
}
#[test]
fn project_texel_works_1() {
let triangle = simple_triangle();
let ans = triangle.project_texel(&Point::origin());
assert!((ans - Point2D::origin()).magnitude() < 1e-5)
}
#[test]
fn project_texel_works_2() {
let triangle = simple_triangle();
let ans = triangle.project_texel(&Point::new(0., 1., 1.));
assert!((ans - Point2D::new(1., 0.)).norm() < 1e-5)
}
#[test]
fn project_texel_works_3() {
let triangle = simple_triangle();
let ans = triangle.project_texel(&Point::new(0., 1., 0.));
assert!((ans - Point2D::new(0., 1.)).norm() < 1e-5)
}
#[test]
fn project_texel_works_4() {
let triangle = Triangle::new(
Point::new(0., f32::sqrt(3.) / 2., 0.),
Point::new(-0.5, 0., 0.),
Point::new(0.5, 0., 0.),
);
// The centroid is at a third of the length of the height of the triangle
let ans = triangle.project_texel(&Point::new(0., f32::sqrt(3.) / 6., 0.));
assert!((ans - Point2D::new(1. / 3., 1. / 3.)).norm() < 1e-5);
}
#[test]
fn project_texel_works_5() {
let triangle = Triangle::new(
Point::new(0., f32::sqrt(3.) / 2., 0.),
Point::new(-0.5, 0., 0.),
Point::new(0.5, 0., 0.),
);
// The centroid is at a third of the length of the height of the triangle
let ans = triangle.project_texel(&Point::origin());
assert!((ans - Point2D::new(0.5, 0.5)).norm() < 1e-5);
}
#[test]
fn deserialization_works() {
let yaml = r#"
corners:
- [0.0, 0.0, 0.0]
- [0.0, 1.0, 1.0]
- [0.0, 1.0, 0.0]
"#;
let triangle: Triangle = serde_yaml::from_str(yaml).unwrap();
assert_eq!(
triangle,
Triangle::new(
Point::origin(),
Point::new(0., 1., 1.),
Point::new(0., 1., 0.)
)
)
}
}