Compare commits
2 commits
Author | SHA1 | Date | |
---|---|---|---|
9e29a5cb7d | |||
ce64403449 |
42
Cargo.toml
42
Cargo.toml
|
@ -1,6 +1,38 @@
|
|||
[workspace]
|
||||
|
||||
members = [
|
||||
"beevee",
|
||||
"pathtracer",
|
||||
[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]
|
||||
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"]
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
[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"
|
|
@ -1,63 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
|
@ -1,444 +0,0 @@
|
|||
//! 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()
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
//! The module relating to Axis-Aligned Bounding Boxes.
|
||||
|
||||
mod bounded;
|
||||
pub use bounded::*;
|
||||
|
||||
mod bounding_box;
|
||||
pub use bounding_box::*;
|
|
@ -1,170 +0,0 @@
|
|||
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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
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>;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
//! The Boudning Volume Hiearchy
|
||||
|
||||
mod intersected;
|
||||
pub use intersected::*;
|
||||
|
||||
mod tree;
|
||||
pub use tree::*;
|
|
@ -1,495 +0,0 @@
|
|||
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)
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
#![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;
|
|
@ -1,152 +0,0 @@
|
|||
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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
aliasing_limit: 10
|
||||
reflection_limit: 5
|
||||
background: {r: 0.5, g: 0.5, b: 0.5}
|
||||
|
||||
camera:
|
||||
origin: [-1.0, 0.0, 0.0]
|
|
@ -35,7 +35,7 @@ objects:
|
|||
g: 1.0
|
||||
b: 1.0
|
||||
transparency: 1.0
|
||||
index: 1.5
|
||||
index: 1.9
|
||||
texture:
|
||||
type: uniform
|
||||
color:
|
|
@ -1,38 +0,0 @@
|
|||
[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"]
|
|
@ -1,17 +0,0 @@
|
|||
#![warn(missing_docs)]
|
||||
|
||||
//! A pathtracing crate
|
||||
|
||||
/// 3D points and vectors
|
||||
pub use beevee::{Point, Vector};
|
||||
|
||||
/// A 2D point coordinate
|
||||
pub type Point2D = nalgebra::Point2<f32>;
|
||||
|
||||
pub mod core;
|
||||
pub mod light;
|
||||
pub mod material;
|
||||
pub mod render;
|
||||
pub mod serialize;
|
||||
pub mod shape;
|
||||
pub mod texture;
|
|
@ -1,67 +0,0 @@
|
|||
use crate::Vector;
|
||||
use nalgebra::Unit;
|
||||
|
||||
pub fn reflected(incident: Unit<Vector>, normal: Unit<Vector>) -> Unit<Vector> {
|
||||
let proj = incident.dot(&normal);
|
||||
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
|
||||
pub fn refracted(
|
||||
incident: Unit<Vector>,
|
||||
normal: Unit<Vector>,
|
||||
indices: &mut RefractionInfo,
|
||||
new_index: f32,
|
||||
) -> Option<(Unit<Vector>, f32)> {
|
||||
let cos1 = incident.dot(&normal);
|
||||
let normal = if cos1 < 0. {
|
||||
// Entering object, change the medium
|
||||
indices.enter_medium(new_index); // The old index is now in old_index
|
||||
normal
|
||||
} else {
|
||||
// Exiting object, exit the medium
|
||||
indices.exit_medium(); // We swapped the indices
|
||||
-normal
|
||||
};
|
||||
let (n_1, n_2) = (indices.old_index, indices.new_index);
|
||||
let eta = n_1 / n_2;
|
||||
let k = 1. - eta * eta * (1. - cos1 * cos1);
|
||||
if k < 0. {
|
||||
return None;
|
||||
}
|
||||
let cos1 = cos1.abs();
|
||||
let cos2 = k.sqrt();
|
||||
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((Unit::new_normalize(refracted), refl_t))
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct RefractionInfo {
|
||||
pub old_index: f32,
|
||||
pub new_index: f32,
|
||||
}
|
||||
|
||||
impl RefractionInfo {
|
||||
pub fn with_index(index: f32) -> Self {
|
||||
RefractionInfo {
|
||||
old_index: index,
|
||||
new_index: index,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enter_medium(&mut self, index: f32) {
|
||||
*self = RefractionInfo {
|
||||
old_index: self.new_index,
|
||||
new_index: index,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exit_medium(&mut self) {
|
||||
std::mem::swap(&mut self.old_index, &mut self.new_index)
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
//! Helper functions deserialize coefficients.
|
||||
|
||||
/// Returns the identity for a f32, i.e. 1.0.
|
||||
pub fn default_identity() -> f32 {
|
||||
1.
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
//! 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<Unit<Vector>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let v: Vector = Deserialize::deserialize(deserializer)?;
|
||||
Ok(Unit::new_normalize(v))
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
//! Camera related logic
|
||||
|
||||
use super::film::Film;
|
||||
use crate::{Point, Vector};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
@ -14,24 +12,6 @@ pub struct Camera {
|
|||
}
|
||||
|
||||
impl Camera {
|
||||
/// Creates a new `Camera`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::Camera;
|
||||
/// use pathtracer::{Point, Vector};
|
||||
///
|
||||
/// let cam = Camera::new(
|
||||
/// Point::new(-1., 0., 0.),
|
||||
/// Vector::new(1., 0., 0.),
|
||||
/// Vector::new(0., 1., 0.),
|
||||
/// 2. * f32::atan(1.), /* 90° in radian */
|
||||
/// 1.,
|
||||
/// 1080,
|
||||
/// 1080,
|
||||
/// );
|
||||
/// ```
|
||||
pub fn new(
|
||||
origin: Point,
|
||||
forward: Vector,
|
||||
|
@ -48,73 +28,15 @@ impl Camera {
|
|||
Camera { origin, film }
|
||||
}
|
||||
|
||||
/// Get the `Camera`'s [`Film`].
|
||||
///
|
||||
/// [`Film`]: ../film/struct.Film.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::{Camera, Film};
|
||||
/// #
|
||||
/// let cam = Camera::default();
|
||||
/// let film: &Film = cam.film();
|
||||
/// ```
|
||||
pub fn film(&self) -> &Film {
|
||||
&self.film
|
||||
}
|
||||
|
||||
/// Get the `Camera`'s `Point` of origin.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::Camera;
|
||||
/// # use pathtracer::Point;
|
||||
/// #
|
||||
/// let cam = Camera::default();
|
||||
/// let origin: &Point = cam.origin();
|
||||
/// ```
|
||||
pub fn origin(&self) -> &Point {
|
||||
&self.origin
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Camera {
|
||||
/// Returns a `Camera` with a 1080x1080 `Film`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::Camera;
|
||||
/// use pathtracer::{Point, Vector};
|
||||
///
|
||||
/// let default = Camera::default();
|
||||
/// let new = Camera::new(
|
||||
/// Point::new(0., 0., 0.),
|
||||
/// Vector::new(1., 0., 0.),
|
||||
/// Vector::new(0., 1., 0.),
|
||||
/// 2. * f32::atan(1.), /* 90° in radian */
|
||||
/// 1.,
|
||||
/// 1080,
|
||||
/// 1080,
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(default, new);
|
||||
/// ```
|
||||
fn default() -> Self {
|
||||
Self::new(
|
||||
Point::origin(),
|
||||
Vector::new(1., 0., 0.),
|
||||
Vector::new(0., 1., 0.),
|
||||
2. * f32::atan(1.), /* 90° in radian */
|
||||
1.,
|
||||
1080,
|
||||
1080,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SerializedCamera {
|
||||
origin: Point,
|
|
@ -1,5 +1,3 @@
|
|||
//! Color definition and operations
|
||||
|
||||
use derive_more::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign, Sum};
|
||||
use serde::Deserialize;
|
||||
use std::ops::{Div, DivAssign, Mul, MulAssign};
|
||||
|
@ -21,34 +19,12 @@ use std::ops::{Div, DivAssign, Mul, MulAssign};
|
|||
)]
|
||||
/// A structure to represent operations in the linear RGB colorspace.
|
||||
pub struct LinearColor {
|
||||
/// The color's red component
|
||||
pub r: f32,
|
||||
/// The color's green component
|
||||
pub g: f32,
|
||||
/// The color's blue component
|
||||
pub b: f32,
|
||||
}
|
||||
|
||||
impl LinearColor {
|
||||
/// Creates the color black.
|
||||
///
|
||||
/// All 3 components are set to 0.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::LinearColor;
|
||||
/// #
|
||||
/// let black = LinearColor::black();
|
||||
/// assert_eq!(
|
||||
/// black,
|
||||
/// LinearColor {
|
||||
/// r: 0.,
|
||||
/// g: 0.,
|
||||
/// b: 0.
|
||||
/// }
|
||||
/// );
|
||||
/// ```
|
||||
pub fn black() -> Self {
|
||||
LinearColor {
|
||||
r: 0.,
|
||||
|
@ -57,30 +33,11 @@ impl LinearColor {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new `Color`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::LinearColor;
|
||||
/// #
|
||||
/// let color = LinearColor::new(1.0, 0.0, 0.0); // bright red!
|
||||
/// ```
|
||||
pub fn new(r: f32, g: f32, b: f32) -> Self {
|
||||
LinearColor { r, g, b }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Clamps the color's RGB components between 0.0 and 1.0.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::LinearColor;
|
||||
/// #
|
||||
/// let color = LinearColor::new(1.5, -1.0, 0.5);
|
||||
/// assert_eq!(color.clamp(), LinearColor::new(1.0, 0.0, 0.5))
|
||||
/// ```
|
||||
pub fn clamp(self) -> Self {
|
||||
fn clamp(v: f32) -> f32 {
|
||||
if v > 1. {
|
||||
|
@ -152,6 +109,19 @@ impl From<LinearColor> for image::Rgb<u8> {
|
|||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn black_is_black() {
|
||||
let black = LinearColor::black();
|
||||
assert_eq!(
|
||||
black,
|
||||
LinearColor {
|
||||
r: 0.,
|
||||
g: 0.,
|
||||
b: 0.
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_is_black() {
|
||||
assert_eq!(<LinearColor as Default>::default(), LinearColor::black())
|
||||
|
@ -334,6 +304,12 @@ mod test {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clamp_works() {
|
||||
let color = LinearColor::new(1.5, -1., 0.5);
|
||||
assert_eq!(color.clamp(), LinearColor::new(1., 0., 0.5))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialization_works() {
|
||||
let yaml = "{r: 1.0, g: 0.5, b: 0.2}";
|
|
@ -1,5 +1,3 @@
|
|||
//! Camera film logic
|
||||
|
||||
use crate::{Point, Vector};
|
||||
|
||||
/// Represent an abstract camera film, to know where each pixel is in space.
|
||||
|
@ -13,23 +11,6 @@ pub struct Film {
|
|||
}
|
||||
|
||||
impl Film {
|
||||
/// Creates a new `Film`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::Film;
|
||||
/// # use pathtracer::{Point, Vector};
|
||||
/// #
|
||||
/// let film = Film::new(
|
||||
/// 1080,
|
||||
/// 1080,
|
||||
/// 10.0,
|
||||
/// Point::origin(),
|
||||
/// Vector::new(0.0, 1.0, 0.0),
|
||||
/// Vector::new(1.0, 0.0, 0.0)
|
||||
/// );
|
||||
/// ```
|
||||
pub fn new(x: u32, y: u32, screen_size: f32, center: Point, up: Vector, right: Vector) -> Self {
|
||||
let (x_size, y_size) = if x > y {
|
||||
(screen_size, screen_size * y as f32 / x as f32)
|
||||
|
@ -45,103 +26,30 @@ impl Film {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the `Film`'s width.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::Film;
|
||||
/// #
|
||||
/// let film = Film::default();
|
||||
/// let width: u32 = film.width();
|
||||
/// ```
|
||||
pub fn width(&self) -> u32 {
|
||||
self.x
|
||||
}
|
||||
|
||||
/// Get the `Film`'s height.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::Film;
|
||||
/// #
|
||||
/// let film = Film::default();
|
||||
/// let height: u32 = film.height();
|
||||
/// ```
|
||||
pub fn height(&self) -> u32 {
|
||||
self.y
|
||||
}
|
||||
|
||||
/// Get a ratio of the pixel's position on the screen.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::Film;
|
||||
/// #
|
||||
/// let film = Film::default(); // 1080x1080 film, width of 1.0
|
||||
/// let (x, y) = film.pixel_ratio(108.0, 972.0);
|
||||
/// assert_eq!(x, 0.1);
|
||||
/// assert_eq!(y, 0.9);
|
||||
/// ```
|
||||
pub fn pixel_ratio(&self, x: f32, y: f32) -> (f32, f32) {
|
||||
(x / self.x as f32, y / self.y as f32)
|
||||
}
|
||||
|
||||
/// Get a pixel's absolute position from a relative screen ratio.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::Film;
|
||||
/// use pathtracer::Point;
|
||||
///
|
||||
/// let film = Film::default(); // 1080x1080 film, width of 1.0
|
||||
/// let (x, y) = film.pixel_ratio(108.0, 1080.0);
|
||||
/// let pos: Point = film.pixel_at_ratio(x, y);
|
||||
/// assert_eq!(pos, Point::new(-0.4, -0.5, 0.0));
|
||||
/// ```
|
||||
pub fn pixel_at_ratio(&self, x: f32, y: f32) -> Point {
|
||||
let delt_x = x - 0.5;
|
||||
let delt_y = 0.5 - y;
|
||||
self.center + self.ratio_right * delt_x + self.ratio_up * delt_y
|
||||
}
|
||||
|
||||
/// Get a pixel's absolute position from screen coordinates.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::Film;
|
||||
/// use pathtracer::Point;
|
||||
///
|
||||
/// let film = Film::default(); // 1080x1080 film, width of 1.0
|
||||
/// let pos: Point = film.pixel_at_coord(108, 1080);
|
||||
/// assert_eq!(pos, Point::new(-0.4, -0.5, 0.0));
|
||||
/// ```
|
||||
pub fn pixel_at_coord(&self, x: u32, y: u32) -> Point {
|
||||
let (x, y) = self.pixel_ratio(x as f32, y as f32);
|
||||
self.pixel_at_ratio(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Film {
|
||||
/// Creates a simple 1080x1080 `Film`.
|
||||
///
|
||||
/// The screen size is 1.0, and the screen is centered at the origin.
|
||||
fn default() -> Self {
|
||||
Film::new(
|
||||
1080,
|
||||
1080,
|
||||
1.0,
|
||||
Point::origin(),
|
||||
Vector::new(0.0, 1.0, 0.0),
|
||||
Vector::new(1.0, 0.0, 0.0),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
|
@ -1,13 +1,9 @@
|
|||
//! Light property coefficients (diffuse, specular, transparency, reflectivity...)
|
||||
|
||||
use super::color::LinearColor;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
/// This enum stores the reflectivity or transparency information.
|
||||
pub enum ReflTransEnum {
|
||||
/// Transparence properties.
|
||||
Transparency {
|
||||
/// The transparency coefficient.
|
||||
#[serde(rename = "transparency")]
|
||||
|
@ -15,7 +11,6 @@ pub enum ReflTransEnum {
|
|||
/// The diffraction index.
|
||||
index: f32,
|
||||
},
|
||||
/// Reflectivity properties.
|
||||
Reflectivity {
|
||||
/// The reflectivity coefficient.
|
||||
#[serde(rename = "reflectivity")]
|
||||
|
@ -36,20 +31,6 @@ pub struct LightProperties {
|
|||
}
|
||||
|
||||
impl LightProperties {
|
||||
/// Creates a new `LightProperties` struct.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::light_properties::{LightProperties, ReflTransEnum};
|
||||
/// # use pathtracer::core::color::LinearColor;
|
||||
/// #
|
||||
/// let lp = LightProperties::new(
|
||||
/// LinearColor::new(0.25, 0.5, 1.),
|
||||
/// LinearColor::new(0.75, 0.375, 0.125),
|
||||
/// Some(ReflTransEnum::Reflectivity { coef: 0.5 }),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn new(
|
||||
diffuse: LinearColor,
|
||||
specular: LinearColor,
|
|
@ -1,5 +1,3 @@
|
|||
//! Core pathtracing pipeline elements
|
||||
|
||||
pub mod camera;
|
||||
pub use camera::*;
|
||||
|
13
src/lib.rs
Normal file
13
src/lib.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use bvh::nalgebra::{Point2, Point3, Vector3};
|
||||
|
||||
pub type Point2D = Point2<f32>;
|
||||
pub type Point = Point3<f32>;
|
||||
pub type Vector = Vector3<f32>;
|
||||
|
||||
pub mod core;
|
||||
pub mod light;
|
||||
pub mod material;
|
||||
pub mod render;
|
||||
pub mod serialize;
|
||||
pub mod shape;
|
||||
pub mod texture;
|
|
@ -10,16 +10,6 @@ pub struct AmbientLight {
|
|||
}
|
||||
|
||||
impl AmbientLight {
|
||||
/// Creates a new `AmbientLight`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::light::AmbientLight;
|
||||
/// # use pathtracer::core::color::LinearColor;
|
||||
/// #
|
||||
/// let amb_light = AmbientLight::new(LinearColor::new(1.0, 0.0, 1.0));
|
||||
/// ```
|
||||
pub fn new(color: LinearColor) -> Self {
|
||||
AmbientLight { color }
|
||||
}
|
|
@ -1,34 +1,22 @@
|
|||
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: Unit<Vector>,
|
||||
direction: Vector,
|
||||
color: LinearColor,
|
||||
}
|
||||
|
||||
impl DirectionalLight {
|
||||
/// Creates a new `DirectionalLight`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::light::DirectionalLight;
|
||||
/// # use pathtracer::core::color::LinearColor;
|
||||
/// # use pathtracer::Vector;
|
||||
/// #
|
||||
/// let dir_light = DirectionalLight::new(
|
||||
/// Vector::x_axis(),
|
||||
/// LinearColor::new(1.0, 0.0, 1.0),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn new(direction: Unit<Vector>, color: LinearColor) -> Self {
|
||||
DirectionalLight { direction, color }
|
||||
pub fn new(direction: Vector, color: LinearColor) -> Self {
|
||||
DirectionalLight {
|
||||
direction: direction.normalize(),
|
||||
color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,8 +27,8 @@ impl Light for DirectionalLight {
|
|||
}
|
||||
|
||||
impl SpatialLight for DirectionalLight {
|
||||
fn to_source(&self, _: &Point) -> (Unit<Vector>, f32) {
|
||||
(-self.direction, std::f32::INFINITY)
|
||||
fn to_source(&self, _: &Point) -> (Vector, f32) {
|
||||
(self.direction * -1., std::f32::INFINITY)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +38,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn new_works() {
|
||||
let direction = Vector::x_axis();
|
||||
let direction = Vector::new(1., 0., 0.);
|
||||
let color = LinearColor::new(1., 1., 1.);
|
||||
let light = DirectionalLight::new(direction, color.clone());
|
||||
let res = DirectionalLight { direction, color };
|
||||
|
@ -58,7 +46,7 @@ mod test {
|
|||
}
|
||||
|
||||
fn simple_light() -> impl SpatialLight {
|
||||
let direction = Vector::x_axis();
|
||||
let direction = Vector::new(1., 0., 0.);
|
||||
let color = LinearColor::new(1., 1., 1.);
|
||||
DirectionalLight::new(direction, color)
|
||||
}
|
||||
|
@ -74,10 +62,7 @@ mod test {
|
|||
fn to_source_is_correct() {
|
||||
let light = simple_light();
|
||||
let ans = light.to_source(&Point::new(1., 0., 0.));
|
||||
let expected = (
|
||||
Unit::new_normalize(Vector::new(-1., 0., 0.)),
|
||||
std::f32::INFINITY,
|
||||
);
|
||||
let expected = (Vector::new(-1., 0., 0.), std::f32::INFINITY);
|
||||
assert_eq!(ans, expected)
|
||||
}
|
||||
|
||||
|
@ -87,7 +72,7 @@ mod test {
|
|||
let light: DirectionalLight = serde_yaml::from_str(yaml).unwrap();
|
||||
assert_eq!(
|
||||
light,
|
||||
DirectionalLight::new(Vector::x_axis(), LinearColor::new(1., 0.5, 0.2))
|
||||
DirectionalLight::new(Vector::new(1., 0., 0.), LinearColor::new(1., 0.5, 0.2))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,8 +1,5 @@
|
|||
//! Various light implementations
|
||||
|
||||
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 {
|
||||
|
@ -13,17 +10,17 @@ 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) -> (Unit<Vector>, f32);
|
||||
fn to_source(&self, origin: &Point) -> (Vector, f32);
|
||||
}
|
||||
|
||||
mod ambient_light;
|
||||
pub mod ambient_light;
|
||||
pub use ambient_light::*;
|
||||
|
||||
mod directional_light;
|
||||
pub mod directional_light;
|
||||
pub use directional_light::*;
|
||||
|
||||
mod point_light;
|
||||
pub mod point_light;
|
||||
pub use point_light::*;
|
||||
|
||||
mod spot_light;
|
||||
pub mod spot_light;
|
||||
pub use spot_light::*;
|
|
@ -1,7 +1,6 @@
|
|||
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.
|
||||
|
@ -12,20 +11,6 @@ pub struct PointLight {
|
|||
}
|
||||
|
||||
impl PointLight {
|
||||
/// Creates a new `PointLight`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::light::PointLight;
|
||||
/// # use pathtracer::core::color::LinearColor;
|
||||
/// # use pathtracer::Point;
|
||||
/// #
|
||||
/// let dir_light = PointLight::new(
|
||||
/// Point::origin(),
|
||||
/// LinearColor::new(1.0, 0.0, 1.0),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn new(position: Point, color: LinearColor) -> Self {
|
||||
PointLight { position, color }
|
||||
}
|
||||
|
@ -39,10 +24,10 @@ impl Light for PointLight {
|
|||
}
|
||||
|
||||
impl SpatialLight for PointLight {
|
||||
fn to_source(&self, point: &Point) -> (Unit<Vector>, f32) {
|
||||
fn to_source(&self, point: &Point) -> (Vector, f32) {
|
||||
let delt = self.position - point;
|
||||
let dist = delt.norm();
|
||||
(Unit::new_normalize(delt), dist)
|
||||
(delt.normalize(), dist)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +61,7 @@ mod test {
|
|||
fn to_source_is_correct() {
|
||||
let light = simple_light();
|
||||
let ans = light.to_source(&Point::new(1., 0., 0.));
|
||||
let expected = (Unit::new_normalize(Vector::new(-1., 0., 0.)), 1.);
|
||||
let expected = (Vector::new(-1., 0., 0.), 1.);
|
||||
assert_eq!(ans, expected);
|
||||
}
|
||||
|
|
@ -1,16 +1,14 @@
|
|||
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.
|
||||
///
|
||||
/// The illumination cone cannot have an FOV over 180°.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct SpotLight {
|
||||
position: Point,
|
||||
direction: Unit<Vector>,
|
||||
direction: Vector,
|
||||
cosine_value: f32,
|
||||
color: LinearColor,
|
||||
}
|
||||
|
@ -19,13 +17,13 @@ impl SpotLight {
|
|||
/// Construct a SpotLight with the given FOV in radian.
|
||||
pub fn radians_new(
|
||||
position: Point,
|
||||
direction: Unit<Vector>,
|
||||
direction: Vector,
|
||||
fov_rad: f32,
|
||||
color: LinearColor,
|
||||
) -> Self {
|
||||
SpotLight {
|
||||
position,
|
||||
direction,
|
||||
direction: direction.normalize(),
|
||||
cosine_value: (fov_rad / 2.).cos(),
|
||||
color,
|
||||
}
|
||||
|
@ -34,7 +32,7 @@ impl SpotLight {
|
|||
/// Construct a SpotLight with the given FOV in degrees.
|
||||
pub fn degrees_new(
|
||||
position: Point,
|
||||
direction: Unit<Vector>,
|
||||
direction: Vector,
|
||||
fov_deg: f32,
|
||||
color: LinearColor,
|
||||
) -> Self {
|
||||
|
@ -60,10 +58,10 @@ impl Light for SpotLight {
|
|||
}
|
||||
|
||||
impl SpatialLight for SpotLight {
|
||||
fn to_source(&self, point: &Point) -> (Unit<Vector>, f32) {
|
||||
fn to_source(&self, point: &Point) -> (Vector, f32) {
|
||||
let delt = self.position - point;
|
||||
let dist = delt.norm();
|
||||
(Unit::new_normalize(delt), dist)
|
||||
(delt.normalize(), dist)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +69,7 @@ impl SpatialLight for SpotLight {
|
|||
struct SerializedSpotLight {
|
||||
position: Point,
|
||||
#[serde(deserialize_with = "crate::serialize::vector_normalizer")]
|
||||
direction: Unit<Vector>,
|
||||
direction: Vector,
|
||||
fov: f32,
|
||||
color: LinearColor,
|
||||
}
|
||||
|
@ -100,7 +98,7 @@ mod test {
|
|||
fn radian_new_works() {
|
||||
let light = SpotLight::radians_new(
|
||||
Point::origin(),
|
||||
Vector::x_axis(),
|
||||
Vector::new(1., 0., 0.),
|
||||
std::f32::consts::PI / 2.,
|
||||
LinearColor::new(1., 1., 1.),
|
||||
);
|
||||
|
@ -110,7 +108,7 @@ mod test {
|
|||
light,
|
||||
SpotLight {
|
||||
position: Point::origin(),
|
||||
direction: Vector::x_axis(),
|
||||
direction: Vector::new(1., 0., 0.),
|
||||
cosine_value: calculated_cosine_value,
|
||||
color: LinearColor::new(1., 1., 1.),
|
||||
}
|
||||
|
@ -123,7 +121,7 @@ mod test {
|
|||
fn degrees_new_works() {
|
||||
let light = SpotLight::degrees_new(
|
||||
Point::origin(),
|
||||
Vector::x_axis(),
|
||||
Vector::new(1., 0., 0.),
|
||||
60.,
|
||||
LinearColor::new(1., 1., 1.),
|
||||
);
|
||||
|
@ -132,7 +130,7 @@ mod test {
|
|||
light,
|
||||
SpotLight {
|
||||
position: Point::origin(),
|
||||
direction: Vector::x_axis(),
|
||||
direction: Vector::new(1., 0., 0.),
|
||||
cosine_value: calculated_cosine_value,
|
||||
color: LinearColor::new(1., 1., 1.),
|
||||
}
|
||||
|
@ -144,7 +142,7 @@ mod test {
|
|||
fn simple_light() -> impl SpatialLight {
|
||||
SpotLight::degrees_new(
|
||||
Point::origin(),
|
||||
Vector::x_axis(),
|
||||
Vector::new(1., 0., 0.),
|
||||
90.,
|
||||
LinearColor::new(1., 1., 1.),
|
||||
)
|
||||
|
@ -182,7 +180,7 @@ mod test {
|
|||
fn to_source_is_correct() {
|
||||
let light = simple_light();
|
||||
let ans = light.to_source(&Point::new(1., 0., 0.));
|
||||
let expected = (Unit::new_normalize(Vector::new(-1., 0., 0.)), 1.);
|
||||
let expected = (Vector::new(-1., 0., 0.), 1.);
|
||||
assert_eq!(ans, expected);
|
||||
}
|
||||
|
||||
|
@ -199,7 +197,7 @@ mod test {
|
|||
light,
|
||||
SpotLight::degrees_new(
|
||||
Point::origin(),
|
||||
Vector::x_axis(),
|
||||
Vector::new(1., 0., 0.),
|
||||
90.,
|
||||
LinearColor::new(1., 0.5, 0.2)
|
||||
)
|
|
@ -1,5 +1,3 @@
|
|||
//! Various material implementations
|
||||
|
||||
use super::core::LightProperties;
|
||||
use super::Point2D;
|
||||
use serde::Deserialize;
|
||||
|
@ -7,7 +5,6 @@ use serde::Deserialize;
|
|||
/// All the existing `Material` implementation.
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[allow(missing_docs)]
|
||||
#[enum_dispatch::enum_dispatch]
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
pub enum MaterialEnum {
|
||||
|
@ -22,5 +19,5 @@ pub trait Material: std::fmt::Debug {
|
|||
fn properties(&self, point: Point2D) -> LightProperties;
|
||||
}
|
||||
|
||||
mod uniform;
|
||||
pub mod uniform;
|
||||
pub use uniform::*;
|
|
@ -11,22 +11,6 @@ pub struct UniformMaterial {
|
|||
}
|
||||
|
||||
impl UniformMaterial {
|
||||
/// Creates a new `UniformMaterial`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::material::UniformMaterial;
|
||||
/// # use pathtracer::core::{LightProperties, LinearColor};
|
||||
/// #
|
||||
/// let uni_mat = UniformMaterial::new(
|
||||
/// LightProperties::new(
|
||||
/// LinearColor::new(1.0, 0.0, 0.0), // diffuse component
|
||||
/// LinearColor::new(0.0, 0.0, 0.0), // specular component
|
||||
/// None,
|
||||
/// ),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn new(properties: LightProperties) -> Self {
|
||||
UniformMaterial { properties }
|
||||
}
|
|
@ -1,11 +1,8 @@
|
|||
//! Utility module to compute overall illumination
|
||||
|
||||
use crate::light::*;
|
||||
use serde::Deserialize;
|
||||
use std::iter::Iterator;
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
/// A struct centralizing the light computation logic.
|
||||
pub struct LightAggregate {
|
||||
#[serde(default)]
|
||||
ambients: Vec<AmbientLight>,
|
||||
|
@ -18,39 +15,10 @@ pub struct LightAggregate {
|
|||
}
|
||||
|
||||
impl LightAggregate {
|
||||
/// Creates a new empty `LightAggregate`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::render::LightAggregate;
|
||||
/// #
|
||||
/// let la = LightAggregate::empty();
|
||||
/// assert_eq!(la.ambient_lights_iter().count(), 0);
|
||||
/// assert_eq!(la.spatial_lights_iter().count(), 0);
|
||||
/// ```
|
||||
pub fn empty() -> Self {
|
||||
LightAggregate::new(vec![], vec![], vec![], vec![])
|
||||
}
|
||||
|
||||
/// Creates a new `LightAggregate` from `Vec`s of [`Light`]s.
|
||||
///
|
||||
/// [`Light`]: ../../light/trait.Light.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::render::LightAggregate;
|
||||
/// #
|
||||
/// let la = LightAggregate::new(
|
||||
/// Vec::new(),
|
||||
/// Vec::new(),
|
||||
/// Vec::new(),
|
||||
/// Vec::new(),
|
||||
/// );
|
||||
/// assert_eq!(la.ambient_lights_iter().count(), 0);
|
||||
/// assert_eq!(la.spatial_lights_iter().count(), 0);
|
||||
/// ```
|
||||
pub fn new(
|
||||
ambients: Vec<AmbientLight>,
|
||||
directionals: Vec<DirectionalLight>,
|
||||
|
@ -65,21 +33,10 @@ impl LightAggregate {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the aggregate's [`AmbientLight`]s.
|
||||
///
|
||||
/// [`AmbientLight`]: ../../light/ambient_light/struct.AmbientLight.html
|
||||
pub fn ambient_lights_iter(&self) -> impl Iterator<Item = &'_ dyn Light> {
|
||||
self.ambients.iter().map(|l| l as &dyn Light)
|
||||
}
|
||||
|
||||
/// Returns an iterator over the aggregate's [`SpatialLight`]s.
|
||||
///
|
||||
/// This simply merges iterators over [`DirectionalLight`], [`PointLight`] and [`SpotLight`].
|
||||
///
|
||||
/// [`SpatialLight`]: ../../light/trait.SpatialLight.html
|
||||
/// [`DirectionalLight`]: ../../light/directional_light/struct.DirectionalLight.html
|
||||
/// [`PointLight`]: ../../light/point_light/struct.PointLight.html
|
||||
/// [`Spotight`]: ../../light/spot_light/struct.Spotight.html
|
||||
pub fn spatial_lights_iter(&self) -> impl Iterator<Item = &'_ dyn SpatialLight> {
|
||||
self.directionals
|
||||
.iter()
|
||||
|
@ -141,7 +98,7 @@ mod test {
|
|||
let expected = LightAggregate::new(
|
||||
vec![AmbientLight::new(LinearColor::new(1., 0.5, 0.2))],
|
||||
vec![DirectionalLight::new(
|
||||
Vector::x_axis(),
|
||||
Vector::new(1., 0., 0.),
|
||||
LinearColor::new(1., 0.5, 0.2),
|
||||
)],
|
||||
vec![PointLight::new(
|
||||
|
@ -150,7 +107,7 @@ mod test {
|
|||
)],
|
||||
vec![SpotLight::degrees_new(
|
||||
Point::origin(),
|
||||
Vector::x_axis(),
|
||||
Vector::new(1., 0., 0.),
|
||||
90.,
|
||||
LinearColor::new(1., 0.5, 0.2),
|
||||
)],
|
|
@ -1,5 +1,3 @@
|
|||
//! Rendering logic
|
||||
|
||||
pub mod light_aggregate;
|
||||
pub use light_aggregate::*;
|
||||
|
||||
|
@ -8,5 +6,3 @@ pub use object::*;
|
|||
|
||||
pub mod scene;
|
||||
pub use scene::*;
|
||||
|
||||
pub(crate) mod utils;
|
|
@ -1,57 +1,27 @@
|
|||
//! Logic for the scene objects
|
||||
|
||||
use crate::material::MaterialEnum;
|
||||
use crate::shape::{Shape, ShapeEnum};
|
||||
use crate::texture::TextureEnum;
|
||||
use crate::Point;
|
||||
use beevee::{
|
||||
aabb::{Bounded, AABB},
|
||||
bvh::Intersected,
|
||||
ray::Ray,
|
||||
};
|
||||
use bvh::aabb::{Bounded, AABB};
|
||||
use bvh::bounding_hierarchy::BHShape;
|
||||
use serde::Deserialize;
|
||||
|
||||
/// An object being rendered in the scene.
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
pub struct Object {
|
||||
/// The `Object`'s physical shape
|
||||
pub shape: ShapeEnum,
|
||||
/// The `Object`'s material
|
||||
pub material: MaterialEnum,
|
||||
/// The `Object`'s texture
|
||||
pub texture: TextureEnum,
|
||||
#[serde(skip_deserializing)]
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl Object {
|
||||
/// Creates a new `Object`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::{LightProperties, LinearColor};
|
||||
/// # use pathtracer::material::UniformMaterial;
|
||||
/// # use pathtracer::render::Object;
|
||||
/// # use pathtracer::shape::Sphere;
|
||||
/// # use pathtracer::texture::UniformTexture;
|
||||
/// # use pathtracer::Point;
|
||||
/// #
|
||||
/// let obj = Object::new(
|
||||
/// Sphere::new(Point::origin(), 1.0).into(),
|
||||
/// UniformMaterial::new(
|
||||
/// LightProperties::new(
|
||||
/// LinearColor::new(1.0, 0.0, 0.0), // diffuse component
|
||||
/// LinearColor::new(0.0, 0.0, 0.0), // specular component
|
||||
/// None,
|
||||
/// ),
|
||||
/// ).into(),
|
||||
/// UniformTexture::new(LinearColor::new(0.5, 0.5, 0.5)).into(),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn new(shape: ShapeEnum, material: MaterialEnum, texture: TextureEnum) -> Self {
|
||||
Object {
|
||||
shape,
|
||||
material,
|
||||
texture,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,15 +30,14 @@ 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
|
||||
}
|
||||
|
||||
impl Intersected for Object {
|
||||
fn intersect(&self, ray: &Ray) -> Option<f32> {
|
||||
self.shape.intersect(ray)
|
||||
fn bh_node_index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,6 +49,7 @@ 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.);
|
||||
|
@ -107,6 +77,7 @@ mod test {
|
|||
shape: shape.into(),
|
||||
material: material.into(),
|
||||
texture: texture.into(),
|
||||
index: 0,
|
||||
}
|
||||
)
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
//! Scene rendering logic
|
||||
|
||||
use super::{light_aggregate::LightAggregate, object::Object, utils::*};
|
||||
use super::{light_aggregate::LightAggregate, object::Object};
|
||||
use crate::{
|
||||
core::{Camera, LightProperties, LinearColor, ReflTransEnum},
|
||||
material::Material,
|
||||
|
@ -8,9 +6,8 @@ use crate::{
|
|||
texture::Texture,
|
||||
{Point, Vector},
|
||||
};
|
||||
use beevee::{bvh::BVH, ray::Ray};
|
||||
use bvh::{bvh::BVH, ray::Ray};
|
||||
use image::RgbImage;
|
||||
use nalgebra::Unit;
|
||||
use rand::prelude::thread_rng;
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
@ -21,64 +18,26 @@ pub struct Scene {
|
|||
lights: LightAggregate,
|
||||
objects: Vec<Object>,
|
||||
bvh: BVH,
|
||||
background: LinearColor,
|
||||
aliasing_limit: u32,
|
||||
reflection_limit: u32,
|
||||
diffraction_index: f32,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
/// Creates a new `Scene`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::core::{Camera, LightProperties, LinearColor};
|
||||
/// # use pathtracer::material::UniformMaterial;
|
||||
/// # use pathtracer::render::{LightAggregate, Object, Scene};
|
||||
/// # use pathtracer::shape::Sphere;
|
||||
/// # use pathtracer::texture::UniformTexture;
|
||||
/// # use pathtracer::Point;
|
||||
/// #
|
||||
/// let scene = Scene::new(
|
||||
/// Camera::default(),
|
||||
/// LightAggregate::empty(),
|
||||
/// vec![
|
||||
/// Object::new(
|
||||
/// Sphere::new(Point::origin(), 1.0).into(),
|
||||
/// UniformMaterial::new(
|
||||
/// LightProperties::new(
|
||||
/// LinearColor::new(1.0, 0.0, 0.0), // diffuse component
|
||||
/// LinearColor::new(0.0, 0.0, 0.0), // specular component
|
||||
/// None,
|
||||
/// ),
|
||||
/// ).into(),
|
||||
/// UniformTexture::new(LinearColor::new(0.5, 0.5, 0.5)).into(),
|
||||
/// ),
|
||||
/// ],
|
||||
/// LinearColor::black(), // Background color
|
||||
/// 5, // aliasing limit
|
||||
/// 3, // reflection recursion limit
|
||||
/// 0.0, // diffraction index
|
||||
/// );
|
||||
/// ```
|
||||
pub fn new(
|
||||
camera: Camera,
|
||||
lights: LightAggregate,
|
||||
mut objects: Vec<Object>,
|
||||
background: LinearColor,
|
||||
aliasing_limit: u32,
|
||||
reflection_limit: u32,
|
||||
diffraction_index: f32,
|
||||
) -> Self {
|
||||
// NOTE(Antoine): fun fact: BVH::build stack overflows when given an empty slice :)
|
||||
let bvh = BVH::build(&mut objects);
|
||||
Scene {
|
||||
camera,
|
||||
lights,
|
||||
objects,
|
||||
bvh,
|
||||
background,
|
||||
aliasing_limit,
|
||||
reflection_limit,
|
||||
diffraction_index,
|
||||
|
@ -102,14 +61,17 @@ impl Scene {
|
|||
Self::pixel
|
||||
};
|
||||
|
||||
let mut rows: Vec<_> = image.enumerate_rows_mut().collect();
|
||||
rayon::scope(|s| {
|
||||
// FIXME(Bruno): it would go even faster to cut the image in blocks of rows, leading to
|
||||
// better cache-line behaviour...
|
||||
for (_, row) in image.enumerate_rows_mut() {
|
||||
let chunk_size = self.camera.film().height() as usize / rayon::current_num_threads();
|
||||
// `chunks_size + 1` to have exactly num_threads tasks spawned, even with rounding
|
||||
for chunk in rows.chunks_mut(chunk_size + 1) {
|
||||
s.spawn(|_| {
|
||||
for (x, y, pixel) in row {
|
||||
*pixel = pixel_func(&self, x as f32, y as f32).into();
|
||||
pb.inc(1);
|
||||
for (_, row) in chunk {
|
||||
for (x, y, pixel) in row {
|
||||
*pixel = pixel_func(&self, x as f32, y as f32).into();
|
||||
pb.inc(1);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -123,20 +85,17 @@ 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 = 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(
|
||||
|| self.background.clone(),
|
||||
|(t, obj)| {
|
||||
let direction = (pixel - self.camera.origin()).normalize();
|
||||
self.cast_ray(Ray::new(pixel, direction))
|
||||
.map_or_else(LinearColor::black, |(t, obj)| {
|
||||
self.color_at(
|
||||
pixel + direction.as_ref() * t,
|
||||
pixel + direction * t,
|
||||
obj,
|
||||
direction,
|
||||
self.reflection_limit,
|
||||
indices,
|
||||
self.diffraction_index,
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get pixel color with anti-aliasing
|
||||
|
@ -155,46 +114,64 @@ impl Scene {
|
|||
}
|
||||
|
||||
fn cast_ray(&self, ray: Ray) -> Option<(f32, &Object)> {
|
||||
self.bvh.walk(&ray, &self.objects)
|
||||
// NOTE(Bruno): should be written using iterators
|
||||
let mut shot_obj: Option<&Object> = None;
|
||||
let mut t = std::f32::INFINITY;
|
||||
// NOTE: we don't care about all objects... Only the closest one
|
||||
for object in self.bvh.traverse(&ray, &self.objects).iter() {
|
||||
match object.shape.intersect(&ray) {
|
||||
Some(dist) if dist < t => {
|
||||
t = dist;
|
||||
shot_obj = Some(&object);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
shot_obj.map(|obj| (t, obj))
|
||||
}
|
||||
|
||||
fn color_at(
|
||||
&self,
|
||||
point: Point,
|
||||
object: &Object,
|
||||
incident_ray: Unit<Vector>,
|
||||
incident_ray: Vector,
|
||||
reflection_limit: u32,
|
||||
mut indices: RefractionInfo,
|
||||
diffraction_index: f32,
|
||||
) -> LinearColor {
|
||||
let texel = object.shape.project_texel(&point);
|
||||
let properties = object.material.properties(texel);
|
||||
let object_color = object.texture.texel_color(texel);
|
||||
|
||||
let normal = object.shape.normal(&point);
|
||||
let reflected_ray = reflected(incident_ray, normal);
|
||||
let reflected = reflected(incident_ray, normal);
|
||||
|
||||
let lighting = self.illuminate(point, object_color, &properties, normal, reflected_ray);
|
||||
if properties.refl_trans.is_none() {
|
||||
// Avoid calculating reflection when not needed
|
||||
return lighting;
|
||||
}
|
||||
let reflected = self.reflection(point, reflected_ray, reflection_limit, indices.clone());
|
||||
// We can unwrap safely thanks to the check for None before
|
||||
match properties.refl_trans.unwrap() {
|
||||
ReflTransEnum::Transparency { coef, index } => {
|
||||
// Calculate the refracted ray, if it was refracted, and mutate indices accordingly
|
||||
refracted(incident_ray, normal, &mut indices, index).map_or_else(
|
||||
let lighting = self.illuminate(point, object_color, &properties, normal, reflected);
|
||||
match properties.refl_trans {
|
||||
None => lighting,
|
||||
Some(ReflTransEnum::Transparency { coef, index }) => {
|
||||
// Calculate the refracted ray, if it was refracted
|
||||
refracted(incident_ray, normal, diffraction_index, index).map_or_else(
|
||||
// Total reflection
|
||||
|| reflected.clone(),
|
||||
|| self.reflection(point, 1., reflected, reflection_limit, diffraction_index),
|
||||
// Refraction (refracted ray, amount of *reflection*)
|
||||
|(r, refl_t)| {
|
||||
let refracted = self.refraction(point, coef, r, reflection_limit, indices);
|
||||
let refr_light = refracted * (1. - refl_t) + reflected.clone() * refl_t;
|
||||
let refr_light = self.refraction(point, coef, r, reflection_limit, index)
|
||||
* (1. - refl_t)
|
||||
+ self.reflection(
|
||||
point,
|
||||
refl_t,
|
||||
reflected,
|
||||
reflection_limit,
|
||||
diffraction_index,
|
||||
) * refl_t;
|
||||
refr_light * coef + lighting * (1. - coef)
|
||||
},
|
||||
)
|
||||
}
|
||||
ReflTransEnum::Reflectivity { coef } => reflected * coef + lighting * (1. - coef),
|
||||
Some(ReflTransEnum::Reflectivity { coef }) => {
|
||||
self.reflection(point, coef, reflected, reflection_limit, diffraction_index)
|
||||
+ lighting * (1. - coef)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,20 +179,20 @@ impl Scene {
|
|||
&self,
|
||||
point: Point,
|
||||
transparency: f32,
|
||||
refracted: Unit<Vector>,
|
||||
refracted: Vector,
|
||||
reflection_limit: u32,
|
||||
indices: RefractionInfo,
|
||||
new_index: f32,
|
||||
) -> LinearColor {
|
||||
if transparency > 1e-5 && reflection_limit > 0 {
|
||||
let refraction_start = point + refracted.as_ref() * 0.001;
|
||||
let refraction_start = point + refracted * 0.001;
|
||||
if let Some((t, obj)) = self.cast_ray(Ray::new(refraction_start, refracted)) {
|
||||
let resulting_position = refraction_start + refracted.as_ref() * t;
|
||||
let resulting_position = refraction_start + refracted * t;
|
||||
let refracted = self.color_at(
|
||||
resulting_position,
|
||||
obj,
|
||||
refracted,
|
||||
reflection_limit - 1,
|
||||
indices,
|
||||
new_index,
|
||||
);
|
||||
return refracted * transparency;
|
||||
}
|
||||
|
@ -226,22 +203,23 @@ impl Scene {
|
|||
fn reflection(
|
||||
&self,
|
||||
point: Point,
|
||||
reflected: Unit<Vector>,
|
||||
reflectivity: f32,
|
||||
reflected: Vector,
|
||||
reflection_limit: u32,
|
||||
indices: RefractionInfo,
|
||||
diffraction_index: f32,
|
||||
) -> LinearColor {
|
||||
if reflection_limit > 0 {
|
||||
let reflection_start = point + reflected.as_ref() * 0.001;
|
||||
if reflectivity > 1e-5 && reflection_limit > 0 {
|
||||
let reflection_start = point + reflected * 0.001;
|
||||
if let Some((t, obj)) = self.cast_ray(Ray::new(reflection_start, reflected)) {
|
||||
let resulting_position = reflection_start + reflected.as_ref() * t;
|
||||
let resulting_position = reflection_start + reflected * t;
|
||||
let color = self.color_at(
|
||||
resulting_position,
|
||||
obj,
|
||||
reflected,
|
||||
reflection_limit - 1,
|
||||
indices,
|
||||
diffraction_index,
|
||||
);
|
||||
return color;
|
||||
return color * reflectivity;
|
||||
}
|
||||
};
|
||||
LinearColor::black()
|
||||
|
@ -252,8 +230,8 @@ impl Scene {
|
|||
point: Point,
|
||||
object_color: LinearColor,
|
||||
properties: &LightProperties,
|
||||
normal: Unit<Vector>,
|
||||
reflected: Unit<Vector>,
|
||||
normal: Vector,
|
||||
reflected: Vector,
|
||||
) -> LinearColor {
|
||||
let ambient = self.illuminate_ambient(object_color.clone());
|
||||
let spatial = self.illuminate_spatial(point, properties, normal, reflected);
|
||||
|
@ -272,14 +250,14 @@ impl Scene {
|
|||
&self,
|
||||
point: Point,
|
||||
properties: &LightProperties,
|
||||
normal: Unit<Vector>,
|
||||
reflected: Unit<Vector>,
|
||||
normal: Vector,
|
||||
reflected: 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.as_ref(), direction);
|
||||
let light_ray = Ray::new(point + 0.001 * direction, direction);
|
||||
match self.cast_ray(light_ray) {
|
||||
// Take shadows into account
|
||||
Some((obstacle_t, _)) if obstacle_t < t => return LinearColor::black(),
|
||||
|
@ -295,6 +273,31 @@ impl Scene {
|
|||
}
|
||||
}
|
||||
|
||||
fn reflected(incident: Vector, normal: Vector) -> Vector {
|
||||
let proj = incident.dot(&normal);
|
||||
let delt = normal * (proj * 2.);
|
||||
incident - delt
|
||||
}
|
||||
|
||||
/// Returns None if the ray was totally reflected, Some(refracted_ray, reflected_amount) if not
|
||||
fn refracted(incident: Vector, normal: Vector, n_1: f32, n_2: f32) -> Option<(Vector, f32)> {
|
||||
let cos1 = incident.dot(&normal);
|
||||
let normal = if cos1 < 0. { normal } else { -normal };
|
||||
let eta = n_1 / n_2;
|
||||
let k = 1. - eta * eta * (1. - cos1 * cos1);
|
||||
if k < 0. {
|
||||
return None;
|
||||
}
|
||||
let cos1 = cos1.abs();
|
||||
let refracted = eta * incident + (eta * cos1 - f32::sqrt(k)) * normal;
|
||||
let cos2 = -refracted.dot(&normal); // Take the negation because we're on the other side
|
||||
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, refl_t))
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
struct SerializedScene {
|
||||
camera: Camera,
|
||||
|
@ -303,8 +306,6 @@ struct SerializedScene {
|
|||
#[serde(default)]
|
||||
objects: Vec<Object>,
|
||||
#[serde(default)]
|
||||
background: LinearColor,
|
||||
#[serde(default)]
|
||||
aliasing_limit: u32,
|
||||
#[serde(default)]
|
||||
reflection_limit: u32,
|
||||
|
@ -318,7 +319,6 @@ impl From<SerializedScene> for Scene {
|
|||
scene.camera,
|
||||
scene.lights,
|
||||
scene.objects,
|
||||
scene.background,
|
||||
scene.aliasing_limit,
|
||||
scene.reflection_limit,
|
||||
scene.starting_diffraction,
|
||||
|
@ -346,21 +346,4 @@ mod test {
|
|||
let _: Scene = serde_yaml::from_str(yaml).unwrap();
|
||||
// FIXME: actually test the equality ?
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // stack overflow because of BVH :(
|
||||
fn bvh_fails() {
|
||||
use crate::core::Camera;
|
||||
use crate::render::{LightAggregate, Scene};
|
||||
|
||||
let _scene = Scene::new(
|
||||
Camera::default(),
|
||||
LightAggregate::empty(),
|
||||
Vec::new(), // Objects list
|
||||
LinearColor::black(), // Background color
|
||||
5, // aliasing limit
|
||||
3, // reflection recursion limit
|
||||
0.0, // diffraction index
|
||||
);
|
||||
}
|
||||
}
|
3
src/serialize/coefficient.rs
Normal file
3
src/serialize/coefficient.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub fn default_identity() -> f32 {
|
||||
1.
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
//! Helper functions to help scene (de)serialization
|
||||
|
||||
pub mod vector;
|
||||
pub use vector::*;
|
||||
|
10
src/serialize/vector.rs
Normal file
10
src/serialize/vector.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use crate::Vector;
|
||||
use serde::de::{Deserialize, Deserializer};
|
||||
|
||||
pub fn vector_normalizer<'de, D>(deserializer: D) -> Result<Vector, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let v: Vector = Deserialize::deserialize(deserializer)?;
|
||||
Ok(v.normalize())
|
||||
}
|
|
@ -1,18 +1,13 @@
|
|||
//! Various shape implementations
|
||||
|
||||
use super::{Point, Point2D, Vector};
|
||||
use beevee::{
|
||||
use bvh::{
|
||||
aabb::{Bounded, AABB},
|
||||
bvh::Intersected,
|
||||
ray::Ray,
|
||||
};
|
||||
use nalgebra::Unit;
|
||||
use serde::Deserialize;
|
||||
|
||||
/// All the existing `Shape` implementation.
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[allow(missing_docs)]
|
||||
#[enum_dispatch::enum_dispatch]
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
pub enum ShapeEnum {
|
||||
|
@ -26,33 +21,21 @@ 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) -> Unit<Vector>;
|
||||
fn normal(&self, point: &Point) -> 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;
|
||||
pub mod sphere;
|
||||
pub use sphere::*;
|
||||
|
||||
mod triangle;
|
||||
pub mod triangle;
|
||||
pub use triangle::*;
|
|
@ -1,8 +1,7 @@
|
|||
use super::Shape;
|
||||
use crate::{Point, Point2D, Vector};
|
||||
use beevee::aabb::AABB;
|
||||
use beevee::ray::Ray;
|
||||
use nalgebra::Unit;
|
||||
use bvh::aabb::AABB;
|
||||
use bvh::ray::Ray;
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Represent a sphere shape inside the scene.
|
||||
|
@ -68,13 +67,13 @@ impl Shape for Sphere {
|
|||
}
|
||||
}
|
||||
|
||||
fn normal(&self, point: &Point) -> Unit<Vector> {
|
||||
fn normal(&self, point: &Point) -> Vector {
|
||||
let delt = if self.inverted {
|
||||
self.center - point
|
||||
} else {
|
||||
point - self.center
|
||||
};
|
||||
Unit::new_normalize(delt)
|
||||
delt.normalize()
|
||||
}
|
||||
|
||||
fn project_texel(&self, point: &Point) -> Point2D {
|
||||
|
@ -91,10 +90,6 @@ impl Shape for Sphere {
|
|||
let max = self.center + delt;
|
||||
AABB::with_bounds(min, max)
|
||||
}
|
||||
|
||||
fn centroid(&self) -> Point {
|
||||
self.center
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -108,30 +103,21 @@ mod test {
|
|||
#[test]
|
||||
fn intersect_along_axis_works() {
|
||||
let sphere = simple_sphere();
|
||||
let ray = Ray::new(
|
||||
Point::new(-2., 0., 0.),
|
||||
Unit::new_normalize(Vector::new(1., 0., 0.)),
|
||||
);
|
||||
let ray = Ray::new(Point::new(-2., 0., 0.), 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.),
|
||||
Unit::new_normalize(Vector::new(-1., 0., 0.)),
|
||||
);
|
||||
let ray = Ray::new(Point::new(-2., 0., 0.), 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.),
|
||||
Unit::new_normalize(Vector::new(-1., -1., -1.)),
|
||||
);
|
||||
let ray = Ray::new(Point::new(1., 1., 1.), Vector::new(-1., -1., -1.));
|
||||
assert_eq!(sphere.intersect(&ray), Some(f32::sqrt(3.) - 1.))
|
||||
}
|
||||
|
||||
|
@ -140,7 +126,7 @@ mod test {
|
|||
let sphere = simple_sphere();
|
||||
assert_eq!(
|
||||
sphere.normal(&Point::new(-1., 0., 0.)),
|
||||
Unit::new_normalize(Vector::new(-1., 0., 0.))
|
||||
Vector::new(-1., 0., 0.)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -149,7 +135,7 @@ mod test {
|
|||
let sphere = Sphere::inverted_new(Point::origin(), 1.);
|
||||
assert_eq!(
|
||||
sphere.normal(&Point::new(-1., 0., 0.)),
|
||||
Unit::new_normalize(Vector::new(1., 0., 0.))
|
||||
Vector::new(1., 0., 0.)
|
||||
)
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
use super::Shape;
|
||||
use crate::{Point, Point2D, Vector};
|
||||
use beevee::aabb::AABB;
|
||||
use beevee::ray::Ray;
|
||||
use nalgebra::Unit;
|
||||
use bvh::aabb::AABB;
|
||||
use bvh::ray::Ray;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
/// Represent a triangle inside the scene.
|
||||
|
@ -14,22 +13,6 @@ pub struct Triangle {
|
|||
}
|
||||
|
||||
impl Triangle {
|
||||
/// Creates a new `Triangle` from 3 [`Point`]s.
|
||||
///
|
||||
/// [`Point`]: ../../type.Point.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::shape::Triangle;
|
||||
/// # use pathtracer::Point;
|
||||
/// #
|
||||
/// let t = Triangle::new(
|
||||
/// Point::new(1.0, 0.0, 0.0),
|
||||
/// Point::new(0.0, 1.0, 0.0),
|
||||
/// Point::new(0.0, 0.0, 1.0),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn new(c0: Point, c1: Point, c2: Point) -> Self {
|
||||
Triangle {
|
||||
c0,
|
||||
|
@ -89,8 +72,8 @@ impl Shape for Triangle {
|
|||
}
|
||||
}
|
||||
|
||||
fn normal(&self, _: &Point) -> Unit<Vector> {
|
||||
Unit::new_normalize(self.c0c1.cross(&self.c0c2))
|
||||
fn normal(&self, _: &Point) -> Vector {
|
||||
self.c0c1.cross(&self.c0c2).normalize()
|
||||
}
|
||||
|
||||
fn project_texel(&self, point: &Point) -> Point2D {
|
||||
|
@ -103,10 +86,6 @@ 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)]
|
||||
|
@ -137,7 +116,6 @@ impl<'de> Deserialize<'de> for Triangle {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use nalgebra::Unit;
|
||||
|
||||
fn simple_triangle() -> Triangle {
|
||||
Triangle::new(
|
||||
|
@ -152,7 +130,7 @@ mod test {
|
|||
let triangle = simple_triangle();
|
||||
let ans = triangle.intersect(&Ray::new(
|
||||
Point::new(-1., 0.5, 0.5),
|
||||
Unit::new_normalize(Vector::new(1., 0., 0.)),
|
||||
Vector::new(1., 0., 0.),
|
||||
));
|
||||
assert_eq!(ans, Some(1.0))
|
||||
}
|
||||
|
@ -162,7 +140,7 @@ mod test {
|
|||
let triangle = simple_triangle();
|
||||
let ans = triangle.intersect(&Ray::new(
|
||||
Point::new(-1., 0.5, 0.),
|
||||
Unit::new_normalize(Vector::new(1., 0., 0.5)),
|
||||
Vector::new(1., 0., 0.5),
|
||||
));
|
||||
assert!(ans.is_some());
|
||||
assert!((ans.unwrap() - f32::sqrt(1.0 + 0.25)).abs() < 1e-5)
|
||||
|
@ -171,10 +149,7 @@ 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.),
|
||||
Unit::new_normalize(Vector::new(1., 1., 1.)),
|
||||
));
|
||||
let ans = triangle.intersect(&Ray::new(Point::new(-1., 0.5, 0.), Vector::new(1., 1., 1.)));
|
||||
assert_eq!(ans, None)
|
||||
}
|
||||
|
||||
|
@ -182,7 +157,7 @@ mod test {
|
|||
fn normal_works() {
|
||||
let triangle = simple_triangle();
|
||||
let normal = triangle.normal(&Point::origin());
|
||||
assert_eq!(normal, Unit::new_normalize(Vector::new(-1., 0., 0.)));
|
||||
assert_eq!(normal, Vector::new(-1., 0., 0.));
|
||||
}
|
||||
|
||||
#[test]
|
|
@ -1,5 +1,3 @@
|
|||
//! Various texture implementations
|
||||
|
||||
use super::core::LinearColor;
|
||||
use super::Point2D;
|
||||
use serde::Deserialize;
|
||||
|
@ -7,7 +5,6 @@ use serde::Deserialize;
|
|||
/// All the existing `Texture` implementation.
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[allow(missing_docs)]
|
||||
#[enum_dispatch::enum_dispatch]
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
pub enum TextureEnum {
|
||||
|
@ -22,5 +19,5 @@ pub trait Texture: std::fmt::Debug {
|
|||
fn texel_color(&self, point: Point2D) -> LinearColor;
|
||||
}
|
||||
|
||||
mod uniform;
|
||||
pub mod uniform;
|
||||
pub use uniform::*;
|
|
@ -10,16 +10,6 @@ pub struct UniformTexture {
|
|||
}
|
||||
|
||||
impl UniformTexture {
|
||||
/// Creates a new `UniformTexture`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pathtracer::texture::UniformTexture;
|
||||
/// # use pathtracer::core::LinearColor;
|
||||
/// #
|
||||
/// let uni_text = UniformTexture::new(LinearColor::new(0.5, 0.5, 0.5));
|
||||
/// ```
|
||||
pub fn new(color: LinearColor) -> Self {
|
||||
UniformTexture { color }
|
||||
}
|
Loading…
Reference in a new issue