diff --git a/Cargo.toml b/Cargo.toml index 770e871..9bde527 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,38 +1,6 @@ -[package] -name = "pathtracer" -version = "0.1.0" -authors = [ - "Bruno BELANYI ", - "Antoine Martin " +[workspace] + +members = [ + "beevee", + "pathtracer", ] -edition = "2018" -description = "A pathtracer written in Rust" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -name = "pathtracer" -path = "src/lib.rs" - -[[bin]] -name = "pathtracer" -path = "src/main.rs" - -[dependencies] -bvh = "0.3.2" -derive_more = "0.99.3" -enum_dispatch = "0.2.1" -image = "0.23.0" -indicatif = "0.14.0" -rand = "0.7" -rayon = "1.3.0" -serde_yaml = "0.8" -structopt = "0.3" - -[dependencies.nalgebra] -version = "0.20.0" -features = ["serde-serialize"] - -[dependencies.serde] -version = "1.0" -features = ["derive"] diff --git a/beevee/Cargo.toml b/beevee/Cargo.toml new file mode 100644 index 0000000..c3492e6 --- /dev/null +++ b/beevee/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "beevee" +version = "0.1.0" +authors = ["Bruno BELANYI "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nalgebra = "0.20" +pdqselect = "0.1.0" diff --git a/beevee/src/aabb/bounded.rs b/beevee/src/aabb/bounded.rs new file mode 100644 index 0000000..bd8a6ad --- /dev/null +++ b/beevee/src/aabb/bounded.rs @@ -0,0 +1,63 @@ +use super::AABB; +use crate::Point; + +/// A trait for objects that can be bounded using an [`AABB`] +/// +/// [`AABB`]: struct.AABB.html +pub trait Bounded { + /// Return the [`AABB`] surrounding self. + /// + /// [`AABB`]: struct.AABB.html + fn aabb(&self) -> AABB; + /// Return the centroid of self. + fn centroid(&self) -> Point; +} + +/// Implementation of [`Bounded`] for [`AABB`] +/// +/// [`Bounded`]: trait.Bounded.html +/// [`AABB`]: struct.Point.html +/// +/// # Examples +/// ``` +/// use beevee::Point; +/// use beevee::aabb::{AABB, Bounded}; +/// +/// let low = Point::new(0., 0., 0.); +/// let high = Point::new(1., 2., 3.); +/// let aabb = AABB::with_bounds(low, high); +/// assert_eq!(aabb, aabb.aabb()); +/// ``` +impl Bounded for AABB { + fn aabb(&self) -> AABB { + *self + } + + fn centroid(&self) -> Point { + self.centroid() + } +} + +/// Implementation of [`Bounded`] for [`Point`] +/// +/// [`Bounded`]: trait.Bounded.html +/// [`Point`]: ../type.Point.html +/// +/// # Examples +/// ``` +/// use beevee::Point; +/// use beevee::aabb::Bounded; +/// +/// let point = Point::new(1., 2., 3.); +/// let aabb = point.aabb(); +/// assert!(aabb.contains(&point)); +/// ``` +impl Bounded for Point { + fn aabb(&self) -> AABB { + AABB::with_bounds(*self, *self) + } + + fn centroid(&self) -> Point { + *self + } +} diff --git a/beevee/src/aabb/bounding_box.rs b/beevee/src/aabb/bounding_box.rs new file mode 100644 index 0000000..cb94577 --- /dev/null +++ b/beevee/src/aabb/bounding_box.rs @@ -0,0 +1,444 @@ +//! An Axis-Alighned Bounding Box. + +use crate::{Axis, Point, Vector}; +use std::fmt::{Display, Formatter, Result}; + +/// An Axis-Aligned Bounding Box. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct AABB { + /// The corner with the lowest (x, y, z) coordinates. + pub low: Point, + /// The corner with the highest (x, y, z) coordinates. + pub high: Point, +} + +impl AABB { + /// Create a new empty [`AABB`] + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// + /// ``` + /// use beevee::Point; + /// use beevee::aabb::AABB; + /// + /// let aabb = AABB::empty(); + /// + /// // Here for teh origin, but also true for any other point + /// let point = Point::origin(); + /// + /// assert!(!aabb.contains(&point)); + /// ``` + #[must_use] + pub fn empty() -> Self { + let lowest = std::f32::NEG_INFINITY; + let highest = std::f32::INFINITY; + AABB { + low: Point::new(highest, highest, highest), + high: Point::new(lowest, lowest, lowest), + } + } + + /// Create a new empty [`AABB`] + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert_eq!(aabb, AABB::with_bounds(low, high)); + /// ``` + #[must_use] + pub fn with_bounds(low: Point, high: Point) -> Self { + debug_assert!(low.x <= high.x); + debug_assert!(low.y <= high.y); + debug_assert!(low.z <= high.z); + AABB { low, high } + } + + /// Return a new bounding box containing both `self` and the new [`Point`] + /// + /// [`Point`]: ../type.Point.html + /// + /// # Examples + /// + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let aabb = AABB::empty(); + /// let new_aabb = aabb.grow(&Point::origin()); + /// + /// assert_eq!(new_aabb, AABB::with_bounds(Point::origin(), Point::origin())); + /// ``` + #[must_use] + pub fn grow(&self, point: &Point) -> Self { + let mut ans = *self; + ans.grow_mut(point); + ans + } + + /// Grow the bounding box to accomodate a new [`Point`]. + /// + /// [`Point`]: ../type.Point.html + /// + /// # Examples + /// + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let mut aabb = AABB::empty(); + /// aabb.grow_mut(&Point::origin()); + /// + /// assert_eq!(aabb, AABB::with_bounds(Point::origin(), Point::origin())); + /// ``` + pub fn grow_mut(&mut self, point: &Point) -> &mut Self { + // Update lowest bound + self.low.x = self.low.x.min(point.x); + self.low.y = self.low.y.min(point.y); + self.low.z = self.low.z.min(point.z); + // Update higher bound + self.high.x = self.high.x.max(point.x); + self.high.y = self.high.y.max(point.y); + self.high.z = self.high.z.max(point.z); + // Return self for method chaining + self + } + + /// Return true if the bounding box contains the [`Point`], false otherwise + /// + /// [`Point`]: ../type.Point.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// // It contains the whole box from low to high + /// assert!(aabb.contains(&low)); + /// assert!(aabb.contains(&high)); + /// assert!(aabb.contains(&Point::new(0.5, 0.5, 0.5))); + /// + /// // And doesn't contain anything else + /// assert!(!aabb.contains(&Point::new(-1., -1., -1.))); + /// assert!(!aabb.contains(&Point::new(1.1, 0., 0.))); + /// assert!(!aabb.contains(&Point::new(2., -2., 0.))); + /// ``` + pub fn contains(&self, point: &Point) -> bool { + (self.low.x..=self.high.x).contains(&point.x) + && (self.low.y..=self.high.y).contains(&point.y) + && (self.low.z..=self.high.z).contains(&point.z) + } + + /// Return a new `AABB` which encloses `self` and the other [`AABB`]. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::empty(); + /// let other = AABB::with_bounds(low, high); + /// + /// // Grow the AABB to enclose the other one. + /// let union = aabb.union(&other); + /// + /// // The result is now the union of an empty bounding box and the other one. + /// assert_eq!(union, other); + /// ``` + pub fn union(&self, other: &Self) -> Self { + // Clone the first bounding box. + let mut ans = *self; + // Update the new bounding box. + ans.union_mut(other); + // Return the new bounding box. + ans + } + + /// Grow `self` to enclose the other [`AABB`]. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let mut aabb = AABB::empty(); + /// let other = AABB::with_bounds(low, high); + /// + /// // Grow the AABB to enclose the other one. + /// aabb.union_mut(&other); + /// + /// // The bounding box has grown to become equal to other one. + /// assert_eq!(aabb, other); + /// ``` + pub fn union_mut(&mut self, other: &Self) -> &mut Self { + self.grow_mut(&other.low); + self.grow_mut(&other.high); + self + } + + /// Return a vector correspondin to the diagonal from `low` to `high` for the [`AABB`]. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert_eq!(aabb.diagonal(), high - low); + /// ``` + pub fn diagonal(&self) -> Vector { + self.high - self.low + } + + /// Return the center of the [`AABB`]. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert_eq!(aabb.centroid(), low + (high - low) / 2.); + /// ``` + pub fn centroid(&self) -> Point { + self.low + self.diagonal() / 2. + } + + /// Return true if the [`AABB`] is empty, false otherwise. + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let not_empty = AABB::with_bounds(low, high); + /// let empty = AABB::empty(); + /// + /// assert!(!not_empty.is_empty()); + /// assert!(empty.is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + self.low.x > self.high.x || self.low.y > self.high.y || self.low.z > self.high.z + } + + /// Return the total surface area of the [`AABB`]. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert!((aabb.surface() - 6.).abs() < std::f32::EPSILON); + /// ``` + pub fn surface(&self) -> f32 { + let diagonal = self.diagonal(); + 2. * (diagonal.x * diagonal.y + diagonal.x * diagonal.z + diagonal.y * diagonal.z) + } + + /// Return the total volume of the [`AABB`]. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert!((aabb.volume() - 1.).abs() < std::f32::EPSILON); + /// ``` + pub fn volume(&self) -> f32 { + let diagonal = self.diagonal(); + diagonal.x * diagonal.y * diagonal.z + } + + /// Return the axis along which the [`AABB`] is the largest. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// use beevee::Axis; + /// + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(3., 2., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert_eq!(aabb.largest_axis(), Axis::X); + /// ``` + /// + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// use beevee::Axis; + /// + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// // Prefers the X axis in case of a three-way tie, then the Y axis in a tie with Z + /// assert_eq!(aabb.largest_axis(), Axis::X); + /// ``` + pub fn largest_axis(&self) -> Axis { + let diagonal = self.diagonal(); + if diagonal.x >= diagonal.y && diagonal.x >= diagonal.z { + Axis::X + } else if diagonal.y >= diagonal.z { + Axis::Y + } else { + Axis::Z + } + } + + /// Return the shortest distance from an [`AABB`] to a [`Point`]. + /// + /// [`AABB`]: struct.AABB.html + /// [`AABB`]: ../type.Point.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert!((aabb.distance_to_point(Point::new(-1., 0., 0.)) - 1.).abs() < std::f32::EPSILON); + /// ``` + /// + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// // Returns 0. when the point is contained by the AABB + /// assert!(aabb.distance_to_point(Point::new(0.5, 0.5, 0.5)).abs() < std::f32::EPSILON); + /// ``` + pub fn distance_to_point(&self, point: Point) -> f32 { + f32::sqrt(self.sqdist_to_point(point)) + } + + /// Return the square of the shortest distance from an [`AABB`] to a [`Point`]. + /// + /// [`AABB`]: struct.AABB.html + /// [`AABB`]: ../type.Point.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert!((aabb.sqdist_to_point(Point::new(-1., 0., 0.)) - 1.).abs() < std::f32::EPSILON); + /// ``` + /// + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// // Returns 0. when the point is contained by the AABB + /// assert!(aabb.sqdist_to_point(Point::new(0.5, 0.5, 0.5)).abs() < std::f32::EPSILON); + /// ``` + pub fn sqdist_to_point(&self, point: Point) -> f32 { + let dx = (self.low.x - point.x).max(0.).max(point.x - self.high.x); + let dy = (self.low.y - point.y).max(0.).max(point.y - self.high.y); + let dz = (self.low.z - point.z).max(0.).max(point.z - self.high.z); + dx * dx + dy * dy + dz * dz + } +} + +/// Display implementation for [`AABB`]. +/// +/// [`AABB`]: struct.AABB.html +/// +/// # Examples +/// ``` +/// # use beevee::Point; +/// # use beevee::aabb::AABB; +/// let low = Point::new(0., 0., 0.); +/// let high = Point::new(1., 1., 1.); +/// let aabb = AABB::with_bounds(low, high); +/// +/// assert_eq!(format!("{}", aabb), "low: {0, 0, 0}, high: {1, 1, 1}"); +/// ``` +impl Display for AABB { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "low: {}, high: {}", self.low, self.high) + } +} + +/// Return an empty [`AABB`]. +/// +/// [`AABB`]: struct.AABB.html +/// +/// # Examples +/// +/// ``` +/// # use beevee::aabb::AABB; +/// let default = ::default(); +/// let empty = AABB::empty(); +/// +/// assert_eq!(default, empty); +/// ``` +impl Default for AABB { + fn default() -> Self { + AABB::empty() + } +} diff --git a/beevee/src/aabb/mod.rs b/beevee/src/aabb/mod.rs new file mode 100644 index 0000000..e3b0474 --- /dev/null +++ b/beevee/src/aabb/mod.rs @@ -0,0 +1,7 @@ +//! The module relating to Axis-Aligned Bounding Boxes. + +mod bounded; +pub use bounded::*; + +mod bounding_box; +pub use bounding_box::*; diff --git a/beevee/src/axis.rs b/beevee/src/axis.rs new file mode 100644 index 0000000..41d8cc4 --- /dev/null +++ b/beevee/src/axis.rs @@ -0,0 +1,170 @@ +use crate::{Point, Vector}; +use std::fmt::{Display, Formatter, Result}; +use std::ops::{Index, IndexMut}; + +/// An enum for indexing different spatial structures. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Axis { + /// The X axis. + X = 0, + /// The Y axis. + Y = 1, + /// The Z axis. + Z = 2, +} + +/// Display implementation for [`Axis`]. +/// +/// [`Axis`]: enum.Axis.html +/// +/// # Examples +/// ``` +/// # use beevee::Axis; +/// assert_eq!(format!("{}", Axis::X), "x"); +/// assert_eq!(format!("{}", Axis::Y), "y"); +/// assert_eq!(format!("{}", Axis::Z), "z"); +/// ``` +impl Display for Axis { + fn fmt(&self, f: &mut Formatter) -> Result { + write!( + f, + "{}", + match *self { + Axis::X => "x", + Axis::Y => "y", + Axis::Z => "z", + } + ) + } +} + +/// Slice indexing implementation by [`Axis`] +/// +/// [`Axis`]: enum.Axis.html +/// +/// # Examples +/// ``` +/// # use beevee::Axis; +/// # +/// let slice = &[0., 1., 2.]; +/// assert_eq!(slice[Axis::X], 0.); +/// ``` +impl Index for [T] { + type Output = T; + + fn index(&self, axis: Axis) -> &Self::Output { + &self[axis as usize] + } +} + +/// Mutable slice indexing implementation by [`Axis`] +/// +/// [`Axis`]: enum.Axis.html +/// +/// # Examples +/// ``` +/// # use beevee::Axis; +/// # +/// let slice = &mut [0., 1., 2.]; +/// slice[Axis::X] = 3.; +/// assert_eq!(slice, &[3., 1., 2.]); +/// ``` +impl IndexMut for [T] { + fn index_mut(&mut self, axis: Axis) -> &mut T { + &mut self[axis as usize] + } +} + +/// [`Point`] indexing implementation by [`Axis`] +/// +/// [`Axis`]: enum.Axis.html +/// [`Point`]: type.Point.html +/// +/// # Examples +/// ``` +/// # use beevee::{Axis, Point}; +/// # +/// let point = Point::new(0., 1., 2.); +/// assert_eq!(point[Axis::X], 0.); +/// ``` +impl Index for Point { + type Output = f32; + + fn index(&self, axis: Axis) -> &Self::Output { + match axis { + Axis::X => &self.x, + Axis::Y => &self.y, + Axis::Z => &self.z, + } + } +} + +/// Mutable [`Point`] indexing implementation by [`Axis`] +/// +/// [`Axis`]: enum.Axis.html +/// [`Point`]: type.Point.html +/// +/// # Examples +/// ``` +/// # use beevee::{Axis, Point}; +/// # +/// let mut point = Point::new(0., 1., 2.); +/// point[Axis::X] = 3.; +/// assert_eq!(point, Point::new(3., 1., 2.)); +/// ``` +impl IndexMut for Point { + fn index_mut(&mut self, axis: Axis) -> &mut f32 { + match axis { + Axis::X => &mut self.x, + Axis::Y => &mut self.y, + Axis::Z => &mut self.z, + } + } +} + +/// [`Vector`] indexing implementation by [`Axis`] +/// +/// [`Axis`]: enum.Axis.html +/// [`Vector`]: type.Vector.html +/// +/// # Examples +/// ``` +/// # use beevee::{Axis, Vector}; +/// # +/// let point = Vector::new(0., 1., 2.); +/// assert_eq!(point[Axis::X], 0.); +/// ``` +impl Index for Vector { + type Output = f32; + + fn index(&self, axis: Axis) -> &Self::Output { + match axis { + Axis::X => &self.x, + Axis::Y => &self.y, + Axis::Z => &self.z, + } + } +} + +/// Mutable [`Vector`] indexing implementation by [`Axis`] +/// +/// [`Axis`]: enum.Axis.html +/// [`Vector`]: type.Vector.html +/// +/// # Examples +/// ``` +/// # use beevee::{Axis, Vector}; +/// # +/// let mut point = Vector::new(0., 1., 2.); +/// point[Axis::X] = 3.; +/// assert_eq!(point, Vector::new(3., 1., 2.)); +/// ``` +impl IndexMut for Vector { + fn index_mut(&mut self, axis: Axis) -> &mut f32 { + match axis { + Axis::X => &mut self.x, + Axis::Y => &mut self.y, + Axis::Z => &mut self.z, + } + } +} diff --git a/beevee/src/bvh/intersected.rs b/beevee/src/bvh/intersected.rs new file mode 100644 index 0000000..e5a4316 --- /dev/null +++ b/beevee/src/bvh/intersected.rs @@ -0,0 +1,11 @@ +use crate::aabb::Bounded; +use crate::ray::Ray; + +/// The trait for any object to be used in the [`BVH`]. +/// +/// [`BVH`]: struct.BVH.html +pub trait Intersected: Bounded { + /// Return None if there is no intersection, or the distance along the ray to the closest + /// intersection + fn intersect(&self, ray: &Ray) -> Option; +} diff --git a/beevee/src/bvh/mod.rs b/beevee/src/bvh/mod.rs new file mode 100644 index 0000000..53a389c --- /dev/null +++ b/beevee/src/bvh/mod.rs @@ -0,0 +1,7 @@ +//! The Boudning Volume Hiearchy + +mod intersected; +pub use intersected::*; + +mod tree; +pub use tree::*; diff --git a/beevee/src/bvh/tree.rs b/beevee/src/bvh/tree.rs new file mode 100644 index 0000000..ae1493f --- /dev/null +++ b/beevee/src/bvh/tree.rs @@ -0,0 +1,495 @@ +use super::Intersected; +use crate::aabb::AABB; +use crate::ray::Ray; +use crate::Axis; + +/// An enum representing either an internal or a leaf node of the [`BVH`] +/// +/// [`BVH`]: struct.BVH.html +#[derive(Clone, Debug, PartialEq)] +enum NodeEnum { + Internal { left: Box, right: Box }, + Leaf, +} + +/// A node representing either an internal or a leaf node of the [`BVH`] +/// +#[derive(Clone, Debug, PartialEq)] +struct Node { + bounds: AABB, + begin: usize, + end: usize, + kind: NodeEnum, +} + +/// The BVH containing all the objects of type O. +/// This type must implement [`Intersected`]. +/// +/// [`Intersected`]: trait.Intersected.html +#[derive(Clone, Debug, PartialEq)] +pub struct BVH { + tree: Node, +} + +impl BVH { + /// Build a [`BVH`] for the given slice of objects. + /// Each leaf node will be built in a way to try and contain less than 32 objects. + /// + /// # Examples + /// ``` + /// use beevee::{Point, Vector}; + /// use beevee::aabb::{AABB, Bounded}; + /// use beevee::bvh::{BVH, Intersected}; + /// use beevee::ray::Ray; + /// + /// #[derive(Clone, Debug, PartialEq)] + /// struct Sphere { + /// center: Point, + /// radius: f32, + /// } + /// + /// impl Bounded for Sphere { + /// fn aabb(&self) -> AABB { + /// let delt = Vector::new(self.radius, self.radius, self.radius); + /// AABB::with_bounds(self.center - delt, self.center + delt) + /// } + /// fn centroid(&self) -> Point { + /// self.center + /// } + /// } + /// + /// impl Intersected for Sphere { + /// fn intersect(&self, ray: &Ray) -> Option { + /// 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) + /// } + /// } + /// } + /// + /// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 2.5 }]; + /// let bvh = BVH::build(spheres); + /// ``` + pub fn build(objects: &mut [O]) -> Self { + Self::with_max_capacity(objects, 32) + } + + /// Build a [`BVH`] for the given slice of objects, with an indicated max capacity per + /// leaf-node. The max capacity is not respected when the SAH heuristic indicate that it would + /// be better to iterate over all objects instead of splitting. + /// + /// # Examples + /// ``` + /// use beevee::{Point, Vector}; + /// use beevee::aabb::{AABB, Bounded}; + /// use beevee::bvh::{BVH, Intersected}; + /// use beevee::ray::Ray; + /// + /// #[derive(Clone, Debug, PartialEq)] + /// struct Sphere { + /// center: Point, + /// radius: f32, + /// } + /// + /// impl Bounded for Sphere { + /// fn aabb(&self) -> AABB { + /// let delt = Vector::new(self.radius, self.radius, self.radius); + /// AABB::with_bounds(self.center - delt, self.center + delt) + /// } + /// fn centroid(&self) -> Point { + /// self.center + /// } + /// } + /// + /// impl Intersected for Sphere { + /// fn intersect(&self, ray: &Ray) -> Option { + /// 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) + /// } + /// } + /// } + /// + /// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 2.5 }]; + /// let bvh = BVH::with_max_capacity(spheres, 32); + /// ``` + pub fn with_max_capacity(objects: &mut [O], max_cap: usize) -> Self { + let tree = build_node(objects, 0, objects.len(), max_cap); + Self { tree } + } + + /// Return the true if the [`BVH`] has been built soundly: + /// * Each child node is contained inside the parent's bounding box. + /// * Each object in a leaf node is inside the node's bounding box. + /// * There is no missing object indices. + /// + /// # Examples + /// ``` + /// # use beevee::{Point, Vector}; + /// # use beevee::aabb::{AABB, Bounded}; + /// # use beevee::bvh::{BVH, Intersected}; + /// # use beevee::ray::Ray; + /// # + /// # #[derive(Clone, Debug, PartialEq)] + /// # struct Sphere { + /// # center: Point, + /// # radius: f32, + /// # } + /// # + /// # impl Bounded for Sphere { + /// # fn aabb(&self) -> AABB { + /// # let delt = Vector::new(self.radius, self.radius, self.radius); + /// # AABB::with_bounds(self.center - delt, self.center + delt) + /// # } + /// # fn centroid(&self) -> Point { + /// # self.center + /// # } + /// # } + /// # + /// # impl Intersected for Sphere { + /// # fn intersect(&self, ray: &Ray) -> Option { + /// # 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) + /// # } + /// # } + /// # } + /// # + /// // Using the same sphere definition than build + /// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 2.5 }]; + /// let bvh = BVH::with_max_capacity(spheres, 32); + /// assert!(bvh.is_sound(spheres)); + /// ``` + pub fn is_sound(&self, objects: &[O]) -> bool { + fn check_node(objects: &[O], node: &Node) -> bool { + if node.begin > node.end { + return false; + } + match node.kind { + NodeEnum::Leaf => objects[node.begin..node.end] + .iter() + .all(|o| node.bounds.union(&o.aabb()) == node.bounds), + NodeEnum::Internal { + ref left, + ref right, + } => { + check_node(objects, left.as_ref()) + && check_node(objects, right.as_ref()) + && node.bounds.union(&left.bounds) == node.bounds + && node.bounds.union(&right.bounds) == node.bounds + } + } + }; + check_node(objects, &self.tree) + } + + /// Iterate recursively over the [`BVH`] to find an intersection point with the given [`Ray`]. + /// This algorithm tries to only iterate over Nodes that are abolutely necessary, and skip + /// visiting nodes that are too far away. + /// + /// [`BVH`]: struct.BVH.html + /// [`Ray`]: ../ray/struct.Ray.html + /// # Examples + /// ``` + /// # use beevee::{Point, Vector}; + /// # use beevee::aabb::{AABB, Bounded}; + /// # use beevee::bvh::{BVH, Intersected}; + /// # use beevee::ray::Ray; + /// # + /// # #[derive(Clone, Debug, PartialEq)] + /// # struct Sphere { + /// # center: Point, + /// # radius: f32, + /// # } + /// # + /// # impl Bounded for Sphere { + /// # fn aabb(&self) -> AABB { + /// # let delt = Vector::new(self.radius, self.radius, self.radius); + /// # AABB::with_bounds(self.center - delt, self.center + delt) + /// # } + /// # fn centroid(&self) -> Point { + /// # self.center + /// # } + /// # } + /// # + /// # impl Intersected for Sphere { + /// # fn intersect(&self, ray: &Ray) -> Option { + /// # 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) + /// # } + /// # } + /// # } + /// # + /// // Using the same sphere definition than build + /// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 0.5 }]; + /// let bvh = BVH::with_max_capacity(spheres, 32); + /// + /// // This ray is directly looking at the spheres + /// let ray = Ray::new(Point::new(-1., 0., 0.), Vector::x_axis()); + /// let res = bvh.walk(&ray, spheres); + /// + /// assert!(res.is_some()); + /// let (dist, obj) = res.unwrap(); + /// assert_eq!(dist, 0.5); + /// assert_eq!(obj, &spheres[0]); + /// ``` + pub fn walk<'o, O: Intersected>(&self, ray: &Ray, objects: &'o [O]) -> Option<(f32, &'o O)> { + walk_rec_helper(ray, objects, &self.tree, std::f32::INFINITY) + } +} + +fn walk_rec_helper<'o, O: Intersected>( + ray: &Ray, + objects: &'o [O], + node: &Node, + min: f32, +) -> Option<(f32, &'o O)> { + use std::cmp::Ordering; + + match &node.kind { + // Return the smallest intersection distance on leaf nodes + NodeEnum::Leaf => objects[node.begin..node.end] + .iter() + // This turns the Option of an intersection into an Option<(f32, &O)> + .filter_map(|o| o.intersect(ray).map(|d| (d, o))) + // Discard values that are too far away + .filter(|(dist, _)| dist < &min) + // Only keep the minimum value, if there is one + .min_by(|(lhs, _), (rhs, _)| lhs.partial_cmp(rhs).unwrap_or(Ordering::Equal)), + + // Recursively find the best node otherwise + NodeEnum::Internal { left, right } => { + let left_dist = left.bounds.distance_to_point(ray.origin); + let right_dist = right.bounds.distance_to_point(ray.origin); + // Pick the short and far nodes + let (near, far, short_dist, far_dist) = if left_dist < right_dist { + (left, right, left_dist, right_dist) + } else { + (right, left, right_dist, left_dist) + }; + // Don't recurse if we know we cannot possibly find a short-enough intersection + if short_dist > min { + return None; + } + // Recurse to the nearest Node first + let nearest_res = walk_rec_helper(ray, objects, near.as_ref(), min); + // Return immediately if there is no point going to the right at all + if far_dist > min { + return nearest_res; + } + match nearest_res { + // Short-circuit if we know it is shorter than any point in the far node + Some((t, obj)) if t <= far_dist => Some((t, obj)), + // We have short_dist <= far_dist <= min in this scenario + // With the eventual val.0 in the [short_dist, min) window + val => { + // Compute the new minimal distance encountered + let min = val.map_or(min, |(t, _)| min.min(t)); + // Recursing with this new minimum can only return None or a better intersecion + walk_rec_helper(ray, objects, far.as_ref(), min).or(val) + } + } + } + } +} + +fn bounds_from_slice(objects: &[O]) -> AABB { + objects + .iter() + .map(|o| o.aabb()) + .fold(AABB::empty(), |acc, other| acc.union(&other)) +} + +fn build_node(objects: &mut [O], begin: usize, end: usize, max_cap: usize) -> Node { + let aabb = bounds_from_slice(objects); + // Don't split nodes under capacity + if objects.len() <= max_cap { + return Node { + bounds: aabb, + begin, + end, + kind: NodeEnum::Leaf, + }; + } + // Calculate the SAH heuristic for this slice + let (split, axis, cost) = compute_sah(&mut objects[begin..end], aabb.surface(), max_cap); + // Only split if the heuristic shows that it is worth it + if cost >= objects.len() as f32 { + return Node { + bounds: aabb, + begin, + end, + kind: NodeEnum::Leaf, + }; + } + // Avoid degenerate cases, and recenter the split inside [begin, end) + let split = if split == 0 || split >= (end - begin - 1) { + begin + (end - begin) / 2 + } else { + begin + split + }; + // Project along chosen axis + pdqselect::select_by(objects, split, |lhs, rhs| { + lhs.centroid()[axis] + .partial_cmp(&rhs.centroid()[axis]) + .expect("Can't use Nans in the SAH computation") + }); + // Construct children recurivsely on [begin, split) and [split, end) + let left = Box::new(build_node(objects, begin, split, max_cap)); + let right = Box::new(build_node(objects, split, end, max_cap)); + // Build the node recursivelly + Node { + bounds: aabb, + begin, + end, + kind: NodeEnum::Internal { left, right }, + } +} + +/// Returns the index at which to split for SAH, the Axis along which to split, and the calculated +/// cost. +fn compute_sah( + objects: &mut [O], + surface: f32, + max_cap: usize, +) -> (usize, Axis, f32) { + // FIXME(Bruno): too imperative to my taste... + let mut mid = objects.len() / 2; + let mut dim = Axis::X; // Arbitrary split + let mut min = std::f32::INFINITY; + + // Pre-allocate the vectors + let mut left_surfaces = Vec::::with_capacity(objects.len() - 1); + let mut right_surfaces = Vec::::with_capacity(objects.len() - 1); + + // For each axis compute the cost + for &axis in [Axis::X, Axis::Y, Axis::Z].iter() { + left_surfaces.clear(); + right_surfaces.clear(); + // Sort in order along the axis + objects.sort_by(|lhs, rhs| { + lhs.centroid()[axis] + .partial_cmp(&rhs.centroid()[axis]) + .expect("Can't use NaNs in the SAH computation") + }); + + // Compute the surface for each possible split + { + let mut left_box = AABB::empty(); + let mut right_box = AABB::empty(); + for i in 0..(objects.len() - 1) { + left_box.union_mut(&objects[i].aabb()); + left_surfaces.push(left_box.surface()); + + right_box.union_mut(&objects[objects.len() - 1 - i].aabb()); + right_surfaces.push(right_box.surface()); + } + } + + // Calculate the cost + for left_count in 1..objects.len() { + let right_count = objects.len() - left_count; + + let cost = 1. / max_cap as f32 + + (left_count as f32 * left_surfaces[left_count - 1] + + right_count as f32 * right_surfaces[right_count]) + / surface; + + if cost < min { + min = cost; + dim = axis; + mid = left_count + } + } + } + (mid, dim, min) +} diff --git a/beevee/src/lib.rs b/beevee/src/lib.rs new file mode 100644 index 0000000..650a99c --- /dev/null +++ b/beevee/src/lib.rs @@ -0,0 +1,28 @@ +#![warn(missing_docs)] + +//! A Bounding Volume Hierarchy crate for use with ray-tracing. + +/// The point to describe the [`AABB`]'s corners. +/// +/// [`AABB`]: aabb/struct.AABB.html +pub type Point = nalgebra::Point3; + +/// The Vector to describe the [`Ray`]'s direction. +/// +/// [`Ray`]: ray/struct.Ray.html +pub type Vector = nalgebra::Vector3; + +/// The module relating to Axis-Aligned Bouding Boxes. +pub mod aabb; + +mod axis; +pub use axis::*; + +/// The module relating to Bouding Volume Hiearchy +pub mod bvh; + +/// Module defining a [`Ray`] structure to intersect with the [`BVH`] +/// +/// [`BVH`]: ../bvh/struct.BVH.html +/// [`Ray`]: ray/struct.Ray.html +pub mod ray; diff --git a/beevee/src/ray.rs b/beevee/src/ray.rs new file mode 100644 index 0000000..e8ef15c --- /dev/null +++ b/beevee/src/ray.rs @@ -0,0 +1,152 @@ +use crate::aabb::AABB; +use crate::{Point, Vector}; +use nalgebra::Unit; +use std::fmt::{Display, Formatter, Result}; + +/// The [`Ray`] to intersect with the [`BVH`]. +/// +/// [`BVH`]: ../bvh/struct.BVH.html +/// [`Ray`]: struct.Ray.html +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Ray { + /// The point of origin of the ray. + pub origin: Point, + /// A unit vector representing the direction of the ray. + pub direction: Unit, + /// The inverse of each coefficient of the ray's direction. + pub inv_direction: Vector, +} + +impl Ray { + /// Create a new [`Ray`] with the given origin and direction + /// + /// [`Ray`]: struct.Ray.html + /// + /// # Examples + /// ``` + /// use beevee::{Point, Vector}; + /// use beevee::ray::Ray; + /// + /// let ray = Ray::new(Point::origin(), Vector::x_axis()); + /// ``` + #[must_use] + pub fn new(origin: Point, direction: Unit) -> Self { + let inv_direction = Vector::new(1. / direction.x, 1. / direction.y, 1. / direction.z); + Ray { + origin, + direction, + inv_direction, + } + } + + /// Return the distance to intersect with an [`AABB`], or [`None`] if there's no intersection. + /// + /// [`AABB`]: ../aabb/struct.AABB.html + /// [`Ray`]: struct.Ray.html + /// + /// # Examples + /// ``` + /// use beevee::{Point, Vector}; + /// use beevee::aabb::AABB; + /// use beevee::ray::Ray; + /// + /// let aabb = AABB::with_bounds(Point::new(1., -1., -1.), Point::new(3., 1., 1.)); + /// let ray = Ray::new(Point::origin(), Vector::x_axis()); + /// + /// assert_eq!(ray.aabb_intersection(&aabb), Some(1.)); + /// ``` + /// + /// ``` + /// use beevee::{Point, Vector}; + /// use beevee::aabb::AABB; + /// use beevee::ray::Ray; + /// + /// let aabb = AABB::with_bounds(Point::new(-1., -1., -1.), Point::new(1., 1., 1.)); + /// let ray = Ray::new(Point::origin(), Vector::x_axis()); + /// + /// // Also works from inside the AABB. + /// assert_eq!(ray.aabb_intersection(&aabb), Some(1.)); + /// ``` + /// + /// ``` + /// use beevee::{Point, Vector}; + /// use beevee::aabb::AABB; + /// use beevee::ray::Ray; + /// + /// let aabb = AABB::with_bounds(Point::new(1., -1., -1.), Point::new(3., 1., 1.)); + /// let ray = Ray::new(Point::origin(), Vector::y_axis()); + /// + /// assert_eq!(ray.aabb_intersection(&aabb), None); + /// ``` + pub fn aabb_intersection(&self, aabb: &AABB) -> Option { + use crate::Axis; + let min_max = |axis: Axis| { + let a = (aabb.high[axis] - self.origin[axis]) * self.inv_direction[axis]; + let b = (aabb.low[axis] - self.origin[axis]) * self.inv_direction[axis]; + if self.direction[axis] < 0. { + (a, b) + } else { + (b, a) + } + }; + let (mut t_min, mut t_max) = min_max(Axis::X); + + let (y_min, y_max) = min_max(Axis::Y); + + if y_min > t_max || y_max < t_min { + return None; + } + + if y_min > t_min { + t_min = y_min; + } + if y_max < t_max { + t_max = y_max; + } + + let (z_min, z_max) = min_max(Axis::Z); + + if z_min > t_max || z_max < t_min { + return None; + } + + if z_min > t_min { + t_min = z_min; + } + if z_max < t_max { + t_max = z_max; + } + + if t_max < 0. { + return None; + } + + if t_min < 0. { + Some(t_max) + } else { + Some(t_min) + } + } +} + +/// Display implementation for [`Ray`]. +/// +/// [`Ray`]: struct.Ray.html +/// +/// # Examples +/// ``` +/// # use beevee::{Point, Vector}; +/// # use beevee::ray::Ray; +/// let ray = Ray::new(Point::origin(), Vector::x_axis()); +/// +/// assert_eq!(format!("{}", ray), "origin: {0, 0, 0}, direction: {1, 0, 0}") +/// ``` +impl Display for Ray { + fn fmt(&self, f: &mut Formatter) -> Result { + write!( + f, + "origin: {}, direction: {{{}, {}, {}}}", + self.origin, self.direction.x, self.direction.y, self.direction.z, + ) + } +} diff --git a/pathtracer/Cargo.toml b/pathtracer/Cargo.toml new file mode 100644 index 0000000..dd98d70 --- /dev/null +++ b/pathtracer/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "pathtracer" +version = "0.1.0" +authors = [ + "Bruno BELANYI ", + "Antoine Martin " +] +edition = "2018" +description = "A pathtracer written in Rust" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "pathtracer" +path = "src/lib.rs" + +[[bin]] +name = "pathtracer" +path = "src/main.rs" + +[dependencies] +beevee = { path = "../beevee" } +derive_more = "0.99.3" +enum_dispatch = "0.2.1" +image = "0.23.0" +indicatif = "0.14.0" +rand = "0.7" +rayon = "1.3.0" +serde_yaml = "0.8" +structopt = "0.3" + +[dependencies.nalgebra] +version = "0.20.0" +features = ["serde-serialize"] + +[dependencies.serde] +version = "1.0" +features = ["derive"] diff --git a/examples/colorful.yaml b/pathtracer/examples/colorful.yaml similarity index 100% rename from examples/colorful.yaml rename to pathtracer/examples/colorful.yaml diff --git a/examples/scene.yaml b/pathtracer/examples/scene.yaml similarity index 100% rename from examples/scene.yaml rename to pathtracer/examples/scene.yaml diff --git a/examples/triangles.yaml b/pathtracer/examples/triangles.yaml similarity index 100% rename from examples/triangles.yaml rename to pathtracer/examples/triangles.yaml diff --git a/src/core/camera.rs b/pathtracer/src/core/camera.rs similarity index 100% rename from src/core/camera.rs rename to pathtracer/src/core/camera.rs diff --git a/src/core/color.rs b/pathtracer/src/core/color.rs similarity index 100% rename from src/core/color.rs rename to pathtracer/src/core/color.rs diff --git a/src/core/film.rs b/pathtracer/src/core/film.rs similarity index 100% rename from src/core/film.rs rename to pathtracer/src/core/film.rs diff --git a/src/core/light_properties.rs b/pathtracer/src/core/light_properties.rs similarity index 100% rename from src/core/light_properties.rs rename to pathtracer/src/core/light_properties.rs diff --git a/src/core/mod.rs b/pathtracer/src/core/mod.rs similarity index 100% rename from src/core/mod.rs rename to pathtracer/src/core/mod.rs diff --git a/src/lib.rs b/pathtracer/src/lib.rs similarity index 51% rename from src/lib.rs rename to pathtracer/src/lib.rs index d3e939a..e601b3d 100644 --- a/src/lib.rs +++ b/pathtracer/src/lib.rs @@ -2,14 +2,11 @@ //! A pathtracing crate -use bvh::nalgebra::{Point2, Point3, Vector3}; +/// 3D points and vectors +pub use beevee::{Point, Vector}; /// A 2D point coordinate -pub type Point2D = Point2; -/// A 3D point coordinate -pub type Point = Point3; -/// A 3D vector -pub type Vector = Vector3; +pub type Point2D = nalgebra::Point2; pub mod core; pub mod light; diff --git a/src/light/ambient_light.rs b/pathtracer/src/light/ambient_light.rs similarity index 100% rename from src/light/ambient_light.rs rename to pathtracer/src/light/ambient_light.rs diff --git a/src/light/directional_light.rs b/pathtracer/src/light/directional_light.rs similarity index 77% rename from src/light/directional_light.rs rename to pathtracer/src/light/directional_light.rs index a3a44a0..b40d153 100644 --- a/src/light/directional_light.rs +++ b/pathtracer/src/light/directional_light.rs @@ -1,13 +1,14 @@ use super::{Light, SpatialLight}; use crate::core::LinearColor; use crate::{Point, Vector}; +use nalgebra::Unit; use serde::Deserialize; /// Represent a light emanating from a far away source, with parallel rays on all points. #[derive(Debug, PartialEq, Deserialize)] pub struct DirectionalLight { #[serde(deserialize_with = "crate::serialize::vector_normalizer")] - direction: Vector, + direction: Unit, color: LinearColor, } @@ -22,15 +23,12 @@ impl DirectionalLight { /// # use pathtracer::Vector; /// # /// let dir_light = DirectionalLight::new( - /// Vector::new(1.0, 0.0, 0.0), + /// Vector::x_axis(), /// LinearColor::new(1.0, 0.0, 1.0), /// ); /// ``` - pub fn new(direction: Vector, color: LinearColor) -> Self { - DirectionalLight { - direction: direction.normalize(), - color, - } + pub fn new(direction: Unit, color: LinearColor) -> Self { + DirectionalLight { direction, color } } } @@ -41,8 +39,8 @@ impl Light for DirectionalLight { } impl SpatialLight for DirectionalLight { - fn to_source(&self, _: &Point) -> (Vector, f32) { - (self.direction * -1., std::f32::INFINITY) + fn to_source(&self, _: &Point) -> (Unit, f32) { + (-self.direction, std::f32::INFINITY) } } @@ -52,7 +50,7 @@ mod test { #[test] fn new_works() { - let direction = Vector::new(1., 0., 0.); + let direction = Vector::x_axis(); let color = LinearColor::new(1., 1., 1.); let light = DirectionalLight::new(direction, color.clone()); let res = DirectionalLight { direction, color }; @@ -60,7 +58,7 @@ mod test { } fn simple_light() -> impl SpatialLight { - let direction = Vector::new(1., 0., 0.); + let direction = Vector::x_axis(); let color = LinearColor::new(1., 1., 1.); DirectionalLight::new(direction, color) } @@ -76,7 +74,10 @@ mod test { fn to_source_is_correct() { let light = simple_light(); let ans = light.to_source(&Point::new(1., 0., 0.)); - let expected = (Vector::new(-1., 0., 0.), std::f32::INFINITY); + let expected = ( + Unit::new_normalize(Vector::new(-1., 0., 0.)), + std::f32::INFINITY, + ); assert_eq!(ans, expected) } @@ -86,7 +87,7 @@ mod test { let light: DirectionalLight = serde_yaml::from_str(yaml).unwrap(); assert_eq!( light, - DirectionalLight::new(Vector::new(1., 0., 0.), LinearColor::new(1., 0.5, 0.2)) + DirectionalLight::new(Vector::x_axis(), LinearColor::new(1., 0.5, 0.2)) ) } } diff --git a/src/light/mod.rs b/pathtracer/src/light/mod.rs similarity index 89% rename from src/light/mod.rs rename to pathtracer/src/light/mod.rs index 10f6e55..ca3bf77 100644 --- a/src/light/mod.rs +++ b/pathtracer/src/light/mod.rs @@ -2,6 +2,7 @@ use super::core::LinearColor; use super::{Point, Vector}; +use nalgebra::Unit; /// Represent a light in the scene being rendered. pub trait Light: std::fmt::Debug { @@ -12,7 +13,7 @@ pub trait Light: std::fmt::Debug { /// Represent a light which has an abstract position in the scene being rendered. pub trait SpatialLight: Light { /// Get a unit vector from the origin to the position of the light, and its distance - fn to_source(&self, origin: &Point) -> (Vector, f32); + fn to_source(&self, origin: &Point) -> (Unit, f32); } mod ambient_light; diff --git a/src/light/point_light.rs b/pathtracer/src/light/point_light.rs similarity index 92% rename from src/light/point_light.rs rename to pathtracer/src/light/point_light.rs index 3fb00d5..2e69ec8 100644 --- a/src/light/point_light.rs +++ b/pathtracer/src/light/point_light.rs @@ -1,6 +1,7 @@ use super::{Light, SpatialLight}; use crate::core::LinearColor; use crate::{Point, Vector}; +use nalgebra::Unit; use serde::Deserialize; /// Represent a light emanating from a point in space, following the square distance law. @@ -38,10 +39,10 @@ impl Light for PointLight { } impl SpatialLight for PointLight { - fn to_source(&self, point: &Point) -> (Vector, f32) { + fn to_source(&self, point: &Point) -> (Unit, f32) { let delt = self.position - point; let dist = delt.norm(); - (delt.normalize(), dist) + (Unit::new_normalize(delt), dist) } } @@ -75,7 +76,7 @@ mod test { fn to_source_is_correct() { let light = simple_light(); let ans = light.to_source(&Point::new(1., 0., 0.)); - let expected = (Vector::new(-1., 0., 0.), 1.); + let expected = (Unit::new_normalize(Vector::new(-1., 0., 0.)), 1.); assert_eq!(ans, expected); } diff --git a/src/light/spot_light.rs b/pathtracer/src/light/spot_light.rs similarity index 90% rename from src/light/spot_light.rs rename to pathtracer/src/light/spot_light.rs index 9fa80ff..5fed907 100644 --- a/src/light/spot_light.rs +++ b/pathtracer/src/light/spot_light.rs @@ -1,6 +1,7 @@ use super::{Light, SpatialLight}; use crate::core::LinearColor; use crate::{Point, Vector}; +use nalgebra::Unit; use serde::{Deserialize, Deserializer}; /// Represent a light emanating from a directed light-source, outputting rays in a cone. @@ -9,7 +10,7 @@ use serde::{Deserialize, Deserializer}; #[derive(Debug, PartialEq)] pub struct SpotLight { position: Point, - direction: Vector, + direction: Unit, cosine_value: f32, color: LinearColor, } @@ -18,13 +19,13 @@ impl SpotLight { /// Construct a SpotLight with the given FOV in radian. pub fn radians_new( position: Point, - direction: Vector, + direction: Unit, fov_rad: f32, color: LinearColor, ) -> Self { SpotLight { position, - direction: direction.normalize(), + direction, cosine_value: (fov_rad / 2.).cos(), color, } @@ -33,7 +34,7 @@ impl SpotLight { /// Construct a SpotLight with the given FOV in degrees. pub fn degrees_new( position: Point, - direction: Vector, + direction: Unit, fov_deg: f32, color: LinearColor, ) -> Self { @@ -59,10 +60,10 @@ impl Light for SpotLight { } impl SpatialLight for SpotLight { - fn to_source(&self, point: &Point) -> (Vector, f32) { + fn to_source(&self, point: &Point) -> (Unit, f32) { let delt = self.position - point; let dist = delt.norm(); - (delt.normalize(), dist) + (Unit::new_normalize(delt), dist) } } @@ -70,7 +71,7 @@ impl SpatialLight for SpotLight { struct SerializedSpotLight { position: Point, #[serde(deserialize_with = "crate::serialize::vector_normalizer")] - direction: Vector, + direction: Unit, fov: f32, color: LinearColor, } @@ -99,7 +100,7 @@ mod test { fn radian_new_works() { let light = SpotLight::radians_new( Point::origin(), - Vector::new(1., 0., 0.), + Vector::x_axis(), std::f32::consts::PI / 2., LinearColor::new(1., 1., 1.), ); @@ -109,7 +110,7 @@ mod test { light, SpotLight { position: Point::origin(), - direction: Vector::new(1., 0., 0.), + direction: Vector::x_axis(), cosine_value: calculated_cosine_value, color: LinearColor::new(1., 1., 1.), } @@ -122,7 +123,7 @@ mod test { fn degrees_new_works() { let light = SpotLight::degrees_new( Point::origin(), - Vector::new(1., 0., 0.), + Vector::x_axis(), 60., LinearColor::new(1., 1., 1.), ); @@ -131,7 +132,7 @@ mod test { light, SpotLight { position: Point::origin(), - direction: Vector::new(1., 0., 0.), + direction: Vector::x_axis(), cosine_value: calculated_cosine_value, color: LinearColor::new(1., 1., 1.), } @@ -143,7 +144,7 @@ mod test { fn simple_light() -> impl SpatialLight { SpotLight::degrees_new( Point::origin(), - Vector::new(1., 0., 0.), + Vector::x_axis(), 90., LinearColor::new(1., 1., 1.), ) @@ -181,7 +182,7 @@ mod test { fn to_source_is_correct() { let light = simple_light(); let ans = light.to_source(&Point::new(1., 0., 0.)); - let expected = (Vector::new(-1., 0., 0.), 1.); + let expected = (Unit::new_normalize(Vector::new(-1., 0., 0.)), 1.); assert_eq!(ans, expected); } @@ -198,7 +199,7 @@ mod test { light, SpotLight::degrees_new( Point::origin(), - Vector::new(1., 0., 0.), + Vector::x_axis(), 90., LinearColor::new(1., 0.5, 0.2) ) diff --git a/src/main.rs b/pathtracer/src/main.rs similarity index 100% rename from src/main.rs rename to pathtracer/src/main.rs diff --git a/src/material/mod.rs b/pathtracer/src/material/mod.rs similarity index 100% rename from src/material/mod.rs rename to pathtracer/src/material/mod.rs diff --git a/src/material/uniform.rs b/pathtracer/src/material/uniform.rs similarity index 100% rename from src/material/uniform.rs rename to pathtracer/src/material/uniform.rs diff --git a/src/render/light_aggregate.rs b/pathtracer/src/render/light_aggregate.rs similarity index 98% rename from src/render/light_aggregate.rs rename to pathtracer/src/render/light_aggregate.rs index 26af7cc..48070c2 100644 --- a/src/render/light_aggregate.rs +++ b/pathtracer/src/render/light_aggregate.rs @@ -141,7 +141,7 @@ mod test { let expected = LightAggregate::new( vec![AmbientLight::new(LinearColor::new(1., 0.5, 0.2))], vec![DirectionalLight::new( - Vector::new(1., 0., 0.), + Vector::x_axis(), LinearColor::new(1., 0.5, 0.2), )], vec![PointLight::new( @@ -150,7 +150,7 @@ mod test { )], vec![SpotLight::degrees_new( Point::origin(), - Vector::new(1., 0., 0.), + Vector::x_axis(), 90., LinearColor::new(1., 0.5, 0.2), )], diff --git a/src/render/mod.rs b/pathtracer/src/render/mod.rs similarity index 100% rename from src/render/mod.rs rename to pathtracer/src/render/mod.rs diff --git a/src/render/object.rs b/pathtracer/src/render/object.rs similarity index 92% rename from src/render/object.rs rename to pathtracer/src/render/object.rs index 7f09fdc..d7b4399 100644 --- a/src/render/object.rs +++ b/pathtracer/src/render/object.rs @@ -3,8 +3,12 @@ use crate::material::MaterialEnum; use crate::shape::{Shape, ShapeEnum}; use crate::texture::TextureEnum; -use bvh::aabb::{Bounded, AABB}; -use bvh::bounding_hierarchy::BHShape; +use crate::Point; +use beevee::{ + aabb::{Bounded, AABB}, + bvh::Intersected, + ray::Ray, +}; use serde::Deserialize; /// An object being rendered in the scene. @@ -60,14 +64,15 @@ impl Bounded for Object { fn aabb(&self) -> AABB { self.shape.aabb() } -} -impl BHShape for Object { - fn set_bh_node_index(&mut self, index: usize) { - self.index = index - } - fn bh_node_index(&self) -> usize { - self.index + fn centroid(&self) -> Point { + self.shape.centroid() + } +} + +impl Intersected for Object { + fn intersect(&self, ray: &Ray) -> Option { + self.shape.intersect(ray) } } @@ -79,7 +84,6 @@ mod test { use crate::material::UniformMaterial; use crate::shape::Sphere; use crate::texture::UniformTexture; - use crate::Point; fn simple_object() -> Object { let shape = Sphere::new(Point::new(5., 0., 0.), 1.); diff --git a/src/render/scene.rs b/pathtracer/src/render/scene.rs similarity index 93% rename from src/render/scene.rs rename to pathtracer/src/render/scene.rs index f50a6eb..2952ffa 100644 --- a/src/render/scene.rs +++ b/pathtracer/src/render/scene.rs @@ -1,7 +1,5 @@ //! Scene rendering logic -use std::cmp::Ordering; - use super::{light_aggregate::LightAggregate, object::Object, utils::*}; use crate::{ core::{Camera, LightProperties, LinearColor, ReflTransEnum}, @@ -10,8 +8,9 @@ use crate::{ texture::Texture, {Point, Vector}, }; -use bvh::{bvh::BVH, ray::Ray}; +use beevee::{bvh::BVH, ray::Ray}; use image::RgbImage; +use nalgebra::Unit; use rand::prelude::thread_rng; use rand::Rng; use serde::{Deserialize, Deserializer}; @@ -120,12 +119,12 @@ impl Scene { fn pixel(&self, x: f32, y: f32) -> LinearColor { let (x, y) = self.camera.film().pixel_ratio(x, y); let pixel = self.camera.film().pixel_at_ratio(x, y); - let direction = (pixel - self.camera.origin()).normalize(); + let direction = Unit::new_normalize(pixel - self.camera.origin()); let indices = RefractionInfo::with_index(self.diffraction_index); self.cast_ray(Ray::new(pixel, direction)) .map_or_else(LinearColor::black, |(t, obj)| { self.color_at( - pixel + direction * t, + pixel + direction.as_ref() * t, obj, direction, self.reflection_limit, @@ -150,20 +149,14 @@ impl Scene { } fn cast_ray(&self, ray: Ray) -> Option<(f32, &Object)> { - self.bvh - .traverse(&ray, &self.objects) - .iter() - .filter_map(|obj| obj.shape.intersect(&ray).map(|distance| (distance, *obj))) - .min_by(|(dist_a, _), (dist_b, _)| { - dist_a.partial_cmp(dist_b).unwrap_or(Ordering::Equal) - }) + self.bvh.walk(&ray, &self.objects) } fn color_at( &self, point: Point, object: &Object, - incident_ray: Vector, + incident_ray: Unit, reflection_limit: u32, mut indices: RefractionInfo, ) -> LinearColor { @@ -203,14 +196,14 @@ impl Scene { &self, point: Point, transparency: f32, - refracted: Vector, + refracted: Unit, reflection_limit: u32, indices: RefractionInfo, ) -> LinearColor { if transparency > 1e-5 && reflection_limit > 0 { - let refraction_start = point + refracted * 0.001; + let refraction_start = point + refracted.as_ref() * 0.001; if let Some((t, obj)) = self.cast_ray(Ray::new(refraction_start, refracted)) { - let resulting_position = refraction_start + refracted * t; + let resulting_position = refraction_start + refracted.as_ref() * t; let refracted = self.color_at( resulting_position, obj, @@ -227,14 +220,14 @@ impl Scene { fn reflection( &self, point: Point, - reflected: Vector, + reflected: Unit, reflection_limit: u32, indices: RefractionInfo, ) -> LinearColor { if reflection_limit > 0 { - let reflection_start = point + reflected * 0.001; + let reflection_start = point + reflected.as_ref() * 0.001; if let Some((t, obj)) = self.cast_ray(Ray::new(reflection_start, reflected)) { - let resulting_position = reflection_start + reflected * t; + let resulting_position = reflection_start + reflected.as_ref() * t; let color = self.color_at( resulting_position, obj, @@ -253,8 +246,8 @@ impl Scene { point: Point, object_color: LinearColor, properties: &LightProperties, - normal: Vector, - reflected: Vector, + normal: Unit, + reflected: Unit, ) -> LinearColor { let ambient = self.illuminate_ambient(object_color.clone()); let spatial = self.illuminate_spatial(point, properties, normal, reflected); @@ -273,14 +266,14 @@ impl Scene { &self, point: Point, properties: &LightProperties, - normal: Vector, - reflected: Vector, + normal: Unit, + reflected: Unit, ) -> LinearColor { self.lights .spatial_lights_iter() .map(|light| { let (direction, t) = light.to_source(&point); - let light_ray = Ray::new(point + 0.001 * direction, direction); + let light_ray = Ray::new(point + 0.001 * direction.as_ref(), direction); match self.cast_ray(light_ray) { // Take shadows into account Some((obstacle_t, _)) if obstacle_t < t => return LinearColor::black(), diff --git a/src/render/utils.rs b/pathtracer/src/render/utils.rs similarity index 78% rename from src/render/utils.rs rename to pathtracer/src/render/utils.rs index 185765a..3f12bfa 100644 --- a/src/render/utils.rs +++ b/pathtracer/src/render/utils.rs @@ -1,19 +1,19 @@ use crate::Vector; +use nalgebra::Unit; -pub fn reflected(incident: Vector, normal: Vector) -> Vector { +pub fn reflected(incident: Unit, normal: Unit) -> Unit { let proj = incident.dot(&normal); - let delt = normal * (proj * 2.); - (incident - delt).normalize() + let delt = normal.into_inner() * (proj * 2.); + Unit::new_normalize(incident.as_ref() - delt) } /// Returns None if the ray was totally reflected, Some(refracted_ray, reflected_amount) if not -/// Adds an element to the top of indices that should be removed pub fn refracted( - incident: Vector, - normal: Vector, + incident: Unit, + normal: Unit, indices: &mut RefractionInfo, new_index: f32, -) -> Option<(Vector, f32)> { +) -> Option<(Unit, f32)> { let cos1 = incident.dot(&normal); let normal = if cos1 < 0. { // Entering object, change the medium @@ -32,12 +32,12 @@ pub fn refracted( } let cos1 = cos1.abs(); let cos2 = k.sqrt(); - let refracted = eta * incident + (eta * cos1 - cos2) * normal; + let refracted = eta * incident.as_ref() + (eta * cos1 - cos2) * normal.as_ref(); let f_r = (n_2 * cos1 - n_1 * cos2) / (n_2 * cos1 + n_1 * cos2); let f_t = (n_1 * cos2 - n_2 * cos1) / (n_1 * cos2 + n_2 * cos1); let refl_t = (f_r * f_r + f_t * f_t) / 2.; //Some((refracted, 0.)) - Some((refracted.normalize(), refl_t)) + Some((Unit::new_normalize(refracted), refl_t)) } #[derive(Debug, PartialEq, Clone)] diff --git a/src/serialize/coefficient.rs b/pathtracer/src/serialize/coefficient.rs similarity index 100% rename from src/serialize/coefficient.rs rename to pathtracer/src/serialize/coefficient.rs diff --git a/src/serialize/mod.rs b/pathtracer/src/serialize/mod.rs similarity index 100% rename from src/serialize/mod.rs rename to pathtracer/src/serialize/mod.rs diff --git a/src/serialize/vector.rs b/pathtracer/src/serialize/vector.rs similarity index 71% rename from src/serialize/vector.rs rename to pathtracer/src/serialize/vector.rs index 00d40ab..7ffc605 100644 --- a/src/serialize/vector.rs +++ b/pathtracer/src/serialize/vector.rs @@ -1,15 +1,16 @@ //! Helper functions to deserialize `Vector` values. use crate::Vector; +use nalgebra::Unit; use serde::de::{Deserialize, Deserializer}; /// Deserialize a vector. /// /// Needs a custom implementation to make sur the vector is normalized when deserialized. -pub fn vector_normalizer<'de, D>(deserializer: D) -> Result +pub fn vector_normalizer<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let v: Vector = Deserialize::deserialize(deserializer)?; - Ok(v.normalize()) + Ok(Unit::new_normalize(v)) } diff --git a/src/shape/mod.rs b/pathtracer/src/shape/mod.rs similarity index 75% rename from src/shape/mod.rs rename to pathtracer/src/shape/mod.rs index c820dbd..e68a59a 100644 --- a/src/shape/mod.rs +++ b/pathtracer/src/shape/mod.rs @@ -1,10 +1,12 @@ //! Various shape implementations use super::{Point, Point2D, Vector}; -use bvh::{ +use beevee::{ aabb::{Bounded, AABB}, + bvh::Intersected, ray::Ray, }; +use nalgebra::Unit; use serde::Deserialize; /// All the existing `Shape` implementation. @@ -24,17 +26,29 @@ pub trait Shape: std::fmt::Debug { /// Return the distance at which the object intersects with the ray, or None if it does not. fn intersect(&self, ray: &Ray) -> Option; /// Return the unit vector corresponding to the normal at this point of the shape. - fn normal(&self, point: &Point) -> Vector; + fn normal(&self, point: &Point) -> Unit; /// Project the point from the shape's surface to its texel coordinates. fn project_texel(&self, point: &Point) -> Point2D; /// Enclose the `Shape` in an axi-aligned bounding-box. fn aabb(&self) -> AABB; + /// Return the centroid of the shape. + fn centroid(&self) -> Point; } impl Bounded for dyn Shape { fn aabb(&self) -> AABB { self.aabb() } + + fn centroid(&self) -> Point { + self.centroid() + } +} + +impl Intersected for dyn Shape { + fn intersect(&self, ray: &Ray) -> Option { + self.intersect(ray) + } } mod sphere; diff --git a/src/shape/sphere.rs b/pathtracer/src/shape/sphere.rs similarity index 85% rename from src/shape/sphere.rs rename to pathtracer/src/shape/sphere.rs index 56ce835..8022cae 100644 --- a/src/shape/sphere.rs +++ b/pathtracer/src/shape/sphere.rs @@ -1,7 +1,8 @@ use super::Shape; use crate::{Point, Point2D, Vector}; -use bvh::aabb::AABB; -use bvh::ray::Ray; +use beevee::aabb::AABB; +use beevee::ray::Ray; +use nalgebra::Unit; use serde::Deserialize; /// Represent a sphere shape inside the scene. @@ -67,13 +68,13 @@ impl Shape for Sphere { } } - fn normal(&self, point: &Point) -> Vector { + fn normal(&self, point: &Point) -> Unit { let delt = if self.inverted { self.center - point } else { point - self.center }; - delt.normalize() + Unit::new_normalize(delt) } fn project_texel(&self, point: &Point) -> Point2D { @@ -90,6 +91,10 @@ impl Shape for Sphere { let max = self.center + delt; AABB::with_bounds(min, max) } + + fn centroid(&self) -> Point { + self.center + } } #[cfg(test)] @@ -103,21 +108,30 @@ mod test { #[test] fn intersect_along_axis_works() { let sphere = simple_sphere(); - let ray = Ray::new(Point::new(-2., 0., 0.), Vector::new(1., 0., 0.)); + let ray = Ray::new( + Point::new(-2., 0., 0.), + Unit::new_normalize(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.)); + let ray = Ray::new( + Point::new(-2., 0., 0.), + Unit::new_normalize(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.)); + let ray = Ray::new( + Point::new(1., 1., 1.), + Unit::new_normalize(Vector::new(-1., -1., -1.)), + ); assert_eq!(sphere.intersect(&ray), Some(f32::sqrt(3.) - 1.)) } @@ -126,7 +140,7 @@ mod test { let sphere = simple_sphere(); assert_eq!( sphere.normal(&Point::new(-1., 0., 0.)), - Vector::new(-1., 0., 0.) + Unit::new_normalize(Vector::new(-1., 0., 0.)) ) } @@ -135,7 +149,7 @@ mod test { let sphere = Sphere::inverted_new(Point::origin(), 1.); assert_eq!( sphere.normal(&Point::new(-1., 0., 0.)), - Vector::new(1., 0., 0.) + Unit::new_normalize(Vector::new(1., 0., 0.)) ) } diff --git a/src/shape/triangle.rs b/pathtracer/src/shape/triangle.rs similarity index 90% rename from src/shape/triangle.rs rename to pathtracer/src/shape/triangle.rs index a78af0e..1fe5d25 100644 --- a/src/shape/triangle.rs +++ b/pathtracer/src/shape/triangle.rs @@ -1,7 +1,8 @@ use super::Shape; use crate::{Point, Point2D, Vector}; -use bvh::aabb::AABB; -use bvh::ray::Ray; +use beevee::aabb::AABB; +use beevee::ray::Ray; +use nalgebra::Unit; use serde::{Deserialize, Deserializer}; /// Represent a triangle inside the scene. @@ -88,8 +89,8 @@ impl Shape for Triangle { } } - fn normal(&self, _: &Point) -> Vector { - self.c0c1.cross(&self.c0c2).normalize() + fn normal(&self, _: &Point) -> Unit { + Unit::new_normalize(self.c0c1.cross(&self.c0c2)) } fn project_texel(&self, point: &Point) -> Point2D { @@ -102,6 +103,10 @@ impl Shape for Triangle { .grow(&(self.c0 + self.c0c1)) .grow(&(self.c0 + self.c0c2)) } + + fn centroid(&self) -> Point { + self.c0 + (self.c0c1 + self.c0c2) / 2. + } } #[derive(Debug, Deserialize)] @@ -132,6 +137,7 @@ impl<'de> Deserialize<'de> for Triangle { #[cfg(test)] mod test { use super::*; + use nalgebra::Unit; fn simple_triangle() -> Triangle { Triangle::new( @@ -146,7 +152,7 @@ mod test { let triangle = simple_triangle(); let ans = triangle.intersect(&Ray::new( Point::new(-1., 0.5, 0.5), - Vector::new(1., 0., 0.), + Unit::new_normalize(Vector::new(1., 0., 0.)), )); assert_eq!(ans, Some(1.0)) } @@ -156,7 +162,7 @@ mod test { let triangle = simple_triangle(); let ans = triangle.intersect(&Ray::new( Point::new(-1., 0.5, 0.), - Vector::new(1., 0., 0.5), + Unit::new_normalize(Vector::new(1., 0., 0.5)), )); assert!(ans.is_some()); assert!((ans.unwrap() - f32::sqrt(1.0 + 0.25)).abs() < 1e-5) @@ -165,7 +171,10 @@ mod test { #[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.))); + let ans = triangle.intersect(&Ray::new( + Point::new(-1., 0.5, 0.), + Unit::new_normalize(Vector::new(1., 1., 1.)), + )); assert_eq!(ans, None) } @@ -173,7 +182,7 @@ mod test { fn normal_works() { let triangle = simple_triangle(); let normal = triangle.normal(&Point::origin()); - assert_eq!(normal, Vector::new(-1., 0., 0.)); + assert_eq!(normal, Unit::new_normalize(Vector::new(-1., 0., 0.))); } #[test] diff --git a/src/texture/mod.rs b/pathtracer/src/texture/mod.rs similarity index 100% rename from src/texture/mod.rs rename to pathtracer/src/texture/mod.rs diff --git a/src/texture/uniform.rs b/pathtracer/src/texture/uniform.rs similarity index 100% rename from src/texture/uniform.rs rename to pathtracer/src/texture/uniform.rs