From 5c0fc9689eeb4a8df214dcd9ce831674cf7c09d1 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 23 Mar 2020 16:33:02 +0100 Subject: [PATCH] library: shape: add InterpolatedTriangle type This is a triangle with added normal interpolation at its edges. This is particularly useful when rendering mesh objects. --- pathtracer/src/shape/interpolated_triangle.rs | 153 ++++++++++++++++++ pathtracer/src/shape/mod.rs | 4 + pathtracer/src/shape/triangle.rs | 2 +- 3 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 pathtracer/src/shape/interpolated_triangle.rs diff --git a/pathtracer/src/shape/interpolated_triangle.rs b/pathtracer/src/shape/interpolated_triangle.rs new file mode 100644 index 0000000..77af206 --- /dev/null +++ b/pathtracer/src/shape/interpolated_triangle.rs @@ -0,0 +1,153 @@ +use super::triangle::Triangle; +use super::Shape; +use crate::{Point, Point2D, Vector}; +use beevee::aabb::AABB; +use beevee::ray::Ray; +use nalgebra::Unit; +use serde::Deserialize; + +/// Represent a triangle with interpolated normals inside the scene. +#[derive(Clone, Debug, PartialEq, Deserialize)] +pub struct InterpolatedTriangle { + #[serde(flatten)] + tri: Triangle, + // FIXME: serialize with unit + normals: [Unit; 3], +} + +impl InterpolatedTriangle { + /// Creates a new `InterpolatedTriangle` from 3 [`Point`]s and 3 [`Vector`]s. + /// + /// [`Point`]: ../../type.Point.html + /// [`Point`]: ../../type.Vector.html + /// + /// # Examples + /// + /// ``` + /// # use pathtracer::shape::InterpolatedTriangle; + /// # use pathtracer::{Point, Vector}; + /// # + /// let t = InterpolatedTriangle::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), + /// Vector::x_axis(), + /// Vector::y_axis(), + /// Vector::z_axis(), + /// ); + /// ``` + pub fn new( + c0: Point, + c1: Point, + c2: Point, + n0: Unit, + n1: Unit, + n2: Unit, + ) -> Self { + InterpolatedTriangle { + tri: Triangle::new(c0, c1, c2), + normals: [n0, n1, n2], + } + } +} + +impl Shape for InterpolatedTriangle { + fn intersect(&self, ray: &Ray) -> Option { + self.tri.intersect(ray) + } + + fn normal(&self, point: &Point) -> Unit { + let (u, v) = { + let c = self.tri.barycentric(point); + (c.x, c.y) + }; + let interpol = self.normals[0].as_ref() * (1. - u - v) + + self.normals[1].as_ref() * u + + self.normals[2].as_ref() * v; + Unit::new_normalize(interpol) + } + + fn project_texel(&self, point: &Point) -> Point2D { + self.tri.project_texel(point) + } + + fn aabb(&self) -> AABB { + self.tri.aabb() + } + + fn centroid(&self) -> Point { + self.tri.centroid() + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn simple_triangle() -> InterpolatedTriangle { + InterpolatedTriangle::new( + Point::origin(), + Point::new(0., 1., 1.), + Point::new(0., 1., 0.), + Vector::x_axis(), + Vector::y_axis(), + Vector::z_axis(), + ) + } + + #[test] + fn normal_interpolation_at_c0_works() { + let triangle = simple_triangle(); + let normal = triangle.normal(&Point::origin()); + assert_eq!(normal, Vector::x_axis()); + } + + #[test] + fn normal_interpolation_at_c1_works() { + let triangle = simple_triangle(); + let normal = triangle.normal(&Point::new(0., 1., 1.)); + assert_eq!(normal, Vector::y_axis()); + } + + #[test] + fn normal_interpolation_at_c2_works() { + let triangle = simple_triangle(); + let normal = triangle.normal(&Point::new(0., 1., 0.)); + assert_eq!(normal, Vector::z_axis()); + } + + #[test] + fn normal_interpolation_at_center_works() { + let triangle = simple_triangle(); + let center = Point::new(0., 2. / 3., 1. / 3.); + let normal = triangle.normal(¢er); + let expected = Unit::new_normalize(Vector::new(1., 1., 1.)); + assert!((normal.as_ref() - expected.as_ref()).magnitude() < 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] + normals: + - [1.0, 0.0, 0.0] + - [0.0, 1.0, 0.0] + - [0.0, 0.0, 1.0] + "#; + let triangle: InterpolatedTriangle = serde_yaml::from_str(yaml).unwrap(); + assert_eq!( + triangle, + InterpolatedTriangle::new( + Point::origin(), + Point::new(0., 1., 1.), + Point::new(0., 1., 0.), + Vector::x_axis(), + Vector::y_axis(), + Vector::z_axis(), + ) + ) + } +} diff --git a/pathtracer/src/shape/mod.rs b/pathtracer/src/shape/mod.rs index e68a59a..e510e93 100644 --- a/pathtracer/src/shape/mod.rs +++ b/pathtracer/src/shape/mod.rs @@ -18,6 +18,7 @@ use serde::Deserialize; pub enum ShapeEnum { Sphere, Triangle, + InterpolatedTriangle, } /// Represent an abstract shape inside the scene. @@ -51,6 +52,9 @@ impl Intersected for dyn Shape { } } +mod interpolated_triangle; +pub use interpolated_triangle::*; + mod sphere; pub use sphere::*; diff --git a/pathtracer/src/shape/triangle.rs b/pathtracer/src/shape/triangle.rs index 1fe5d25..b539d01 100644 --- a/pathtracer/src/shape/triangle.rs +++ b/pathtracer/src/shape/triangle.rs @@ -38,7 +38,7 @@ impl Triangle { } } - fn barycentric(&self, point: &Point) -> Point2D { + pub(crate) 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