Merge branch 'doc' into 'master'

Document public APIs

See merge request EPITA_bruno.belanyi/image/ing2/pathtracer!1
This commit is contained in:
Bruno BELANYI 2020-03-23 12:06:13 +00:00
commit 61db9c0cd4
24 changed files with 478 additions and 27 deletions

View file

@ -1,3 +1,5 @@
//! Camera related logic
use super::film::Film;
use crate::{Point, Vector};
use serde::{Deserialize, Deserializer};
@ -12,6 +14,24 @@ 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,
@ -28,15 +48,73 @@ 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,

View file

@ -1,3 +1,5 @@
//! 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};
@ -19,12 +21,34 @@ 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.,
@ -33,11 +57,30 @@ 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. {
@ -109,19 +152,6 @@ 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())
@ -304,12 +334,6 @@ 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}";

View file

@ -1,3 +1,5 @@
//! Camera film logic
use crate::{Point, Vector};
/// Represent an abstract camera film, to know where each pixel is in space.
@ -11,6 +13,23 @@ 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)
@ -26,30 +45,103 @@ 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::*;

View file

@ -1,9 +1,13 @@
//! 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")]
@ -11,6 +15,7 @@ pub enum ReflTransEnum {
/// The diffraction index.
index: f32,
},
/// Reflectivity properties.
Reflectivity {
/// The reflectivity coefficient.
#[serde(rename = "reflectivity")]
@ -31,6 +36,20 @@ 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,

View file

@ -1,3 +1,5 @@
//! Core pathtracing pipeline elements
pub mod camera;
pub use camera::*;

View file

@ -1,7 +1,14 @@
#![warn(missing_docs)]
//! A pathtracing crate
use bvh::nalgebra::{Point2, Point3, Vector3};
/// A 2D point coordinate
pub type Point2D = Point2<f32>;
/// A 3D point coordinate
pub type Point = Point3<f32>;
/// A 3D vector
pub type Vector = Vector3<f32>;
pub mod core;

View file

@ -10,6 +10,16 @@ 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 }
}

View file

@ -12,6 +12,20 @@ pub struct DirectionalLight {
}
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::new(1.0, 0.0, 0.0),
/// LinearColor::new(1.0, 0.0, 1.0),
/// );
/// ```
pub fn new(direction: Vector, color: LinearColor) -> Self {
DirectionalLight {
direction: direction.normalize(),

View file

@ -1,3 +1,5 @@
//! Various light implementations
use super::core::LinearColor;
use super::{Point, Vector};
@ -13,14 +15,14 @@ pub trait SpatialLight: Light {
fn to_source(&self, origin: &Point) -> (Vector, f32);
}
pub mod ambient_light;
mod ambient_light;
pub use ambient_light::*;
pub mod directional_light;
mod directional_light;
pub use directional_light::*;
pub mod point_light;
mod point_light;
pub use point_light::*;
pub mod spot_light;
mod spot_light;
pub use spot_light::*;

View file

@ -11,6 +11,20 @@ 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 }
}

View file

@ -4,6 +4,7 @@ use crate::{Point, Vector};
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 {

View file

@ -1,3 +1,5 @@
//! Various material implementations
use super::core::LightProperties;
use super::Point2D;
use serde::Deserialize;
@ -5,6 +7,7 @@ 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 {
@ -19,5 +22,5 @@ pub trait Material: std::fmt::Debug {
fn properties(&self, point: Point2D) -> LightProperties;
}
pub mod uniform;
mod uniform;
pub use uniform::*;

View file

@ -11,6 +11,22 @@ 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 }
}

View file

@ -1,8 +1,11 @@
//! 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>,
@ -15,10 +18,39 @@ 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>,
@ -33,10 +65,21 @@ 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()

View file

@ -1,3 +1,5 @@
//! Rendering logic
pub mod light_aggregate;
pub use light_aggregate::*;

View file

@ -1,3 +1,5 @@
//! Logic for the scene objects
use crate::material::MaterialEnum;
use crate::shape::{Shape, ShapeEnum};
use crate::texture::TextureEnum;
@ -8,14 +10,42 @@ 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 inside the `BVH`
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,

View file

@ -1,3 +1,5 @@
//! Scene rendering logic
use std::cmp::Ordering;
use super::{light_aggregate::LightAggregate, object::Object};
@ -26,6 +28,39 @@ pub struct Scene {
}
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(),
/// ),
/// ],
/// 5, // aliasing limit
/// 3, // reflection recursion limit
/// 0.0, // diffraction index
/// );
/// ```
pub fn new(
camera: Camera,
lights: LightAggregate,
@ -34,6 +69,7 @@ impl Scene {
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,
@ -338,4 +374,20 @@ 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
5, // aliasing limit
3, // reflection recursion limit
0.0, // diffraction index
);
}
}

View file

@ -1,3 +1,6 @@
//! Helper functions deserialize coefficients.
/// Returns the identity for a f32, i.e. 1.0.
pub fn default_identity() -> f32 {
1.
}

View file

@ -1,3 +1,5 @@
//! Helper functions to help scene (de)serialization
pub mod vector;
pub use vector::*;

View file

@ -1,6 +1,11 @@
//! Helper functions to deserialize `Vector` values.
use crate::Vector;
use serde::de::{Deserialize, Deserializer};
/// Deserialize a vector.
///
/// Needs a custom implementation to make sur the vector is normalized when deserialized.
pub fn vector_normalizer<'de, D>(deserializer: D) -> Result<Vector, D::Error>
where
D: Deserializer<'de>,

View file

@ -1,3 +1,5 @@
//! Various shape implementations
use super::{Point, Point2D, Vector};
use bvh::{
aabb::{Bounded, AABB},
@ -8,6 +10,7 @@ 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 {
@ -34,8 +37,8 @@ impl Bounded for dyn Shape {
}
}
pub mod sphere;
mod sphere;
pub use sphere::*;
pub mod triangle;
mod triangle;
pub use triangle::*;

View file

@ -13,6 +13,22 @@ 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,

View file

@ -1,3 +1,5 @@
//! Various texture implementations
use super::core::LinearColor;
use super::Point2D;
use serde::Deserialize;
@ -5,6 +7,7 @@ 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 {
@ -19,5 +22,5 @@ pub trait Texture: std::fmt::Debug {
fn texel_color(&self, point: Point2D) -> LinearColor;
}
pub mod uniform;
mod uniform;
pub use uniform::*;

View file

@ -10,6 +10,16 @@ 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 }
}