Merge branch 'add-beevee' into 'master'

Add beevee

See merge request EPITA_bruno.belanyi/image/ing2/pathtracer!4
This commit is contained in:
Bruno BELANYI 2020-03-24 21:04:52 +00:00
commit 2e224b280d
42 changed files with 1570 additions and 140 deletions

View file

@ -1,38 +1,6 @@
[package]
name = "pathtracer"
version = "0.1.0"
authors = [
"Bruno BELANYI <brunobelanyi@gmail.com>",
"Antoine Martin <antoine97.martin@gmail.com>"
[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"]

11
beevee/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "beevee"
version = "0.1.0"
authors = ["Bruno BELANYI <brunobelanyi@gmail.com>"]
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"

View file

@ -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
}
}

View file

@ -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 = <AABB as Default>::default();
/// let empty = AABB::empty();
///
/// assert_eq!(default, empty);
/// ```
impl Default for AABB {
fn default() -> Self {
AABB::empty()
}
}

7
beevee/src/aabb/mod.rs Normal file
View file

@ -0,0 +1,7 @@
//! The module relating to Axis-Aligned Bounding Boxes.
mod bounded;
pub use bounded::*;
mod bounding_box;
pub use bounding_box::*;

170
beevee/src/axis.rs Normal file
View file

@ -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<T> Index<Axis> 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<T> IndexMut<Axis> 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<Axis> 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<Axis> 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<Axis> 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<Axis> 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,
}
}
}

View file

@ -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<f32>;
}

7
beevee/src/bvh/mod.rs Normal file
View file

@ -0,0 +1,7 @@
//! The Boudning Volume Hiearchy
mod intersected;
pub use intersected::*;
mod tree;
pub use tree::*;

495
beevee/src/bvh/tree.rs Normal file
View file

@ -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<Node>, right: Box<Node> },
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<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)
/// }
/// }
/// }
///
/// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 2.5 }];
/// let bvh = BVH::build(spheres);
/// ```
pub fn build<O: Intersected>(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<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)
/// }
/// }
/// }
///
/// 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<O: Intersected>(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<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)
/// # }
/// # }
/// # }
/// #
/// // 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<O: Intersected>(&self, objects: &[O]) -> bool {
fn check_node<O: Intersected>(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<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)
/// # }
/// # }
/// # }
/// #
/// // 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<f32> 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<O: Intersected>(objects: &[O]) -> AABB {
objects
.iter()
.map(|o| o.aabb())
.fold(AABB::empty(), |acc, other| acc.union(&other))
}
fn build_node<O: Intersected>(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<O: Intersected>(
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::<f32>::with_capacity(objects.len() - 1);
let mut right_surfaces = Vec::<f32>::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)
}

28
beevee/src/lib.rs Normal file
View file

@ -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<f32>;
/// The Vector to describe the [`Ray`]'s direction.
///
/// [`Ray`]: ray/struct.Ray.html
pub type Vector = nalgebra::Vector3<f32>;
/// 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;

152
beevee/src/ray.rs Normal file
View file

@ -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<Vector>,
/// 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<Vector>) -> 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<f32> {
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,
)
}
}

38
pathtracer/Cargo.toml Normal file
View file

@ -0,0 +1,38 @@
[package]
name = "pathtracer"
version = "0.1.0"
authors = [
"Bruno BELANYI <brunobelanyi@gmail.com>",
"Antoine Martin <antoine97.martin@gmail.com>"
]
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"]

View file

@ -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<f32>;
/// A 3D point coordinate
pub type Point = Point3<f32>;
/// A 3D vector
pub type Vector = Vector3<f32>;
pub type Point2D = nalgebra::Point2<f32>;
pub mod core;
pub mod light;

View file

@ -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<Vector>,
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<Vector>, 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<Vector>, 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))
)
}
}

View file

@ -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<Vector>, f32);
}
mod ambient_light;

View file

@ -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<Vector>, 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);
}

View file

@ -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<Vector>,
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<Vector>,
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<Vector>,
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<Vector>, 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<Vector>,
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)
)

View file

@ -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),
)],

View file

@ -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()
}
fn centroid(&self) -> Point {
self.shape.centroid()
}
impl BHShape for Object {
fn set_bh_node_index(&mut self, index: usize) {
self.index = index
}
fn bh_node_index(&self) -> usize {
self.index
impl Intersected for Object {
fn intersect(&self, ray: &Ray) -> Option<f32> {
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.);

View file

@ -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<Vector>,
reflection_limit: u32,
mut indices: RefractionInfo,
) -> LinearColor {
@ -203,14 +196,14 @@ impl Scene {
&self,
point: Point,
transparency: f32,
refracted: Vector,
refracted: Unit<Vector>,
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<Vector>,
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<Vector>,
reflected: Unit<Vector>,
) -> 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<Vector>,
reflected: Unit<Vector>,
) -> 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(),

View file

@ -1,19 +1,19 @@
use crate::Vector;
use nalgebra::Unit;
pub fn reflected(incident: Vector, normal: Vector) -> Vector {
pub fn reflected(incident: Unit<Vector>, normal: Unit<Vector>) -> Unit<Vector> {
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<Vector>,
normal: Unit<Vector>,
indices: &mut RefractionInfo,
new_index: f32,
) -> Option<(Vector, f32)> {
) -> Option<(Unit<Vector>, 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)]

View file

@ -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<Vector, D::Error>
pub fn vector_normalizer<'de, D>(deserializer: D) -> Result<Unit<Vector>, D::Error>
where
D: Deserializer<'de>,
{
let v: Vector = Deserialize::deserialize(deserializer)?;
Ok(v.normalize())
Ok(Unit::new_normalize(v))
}

View file

@ -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<f32>;
/// 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<Vector>;
/// 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<f32> {
self.intersect(ray)
}
}
mod sphere;

View file

@ -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<Vector> {
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.))
)
}

View file

@ -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<Vector> {
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]