library: use beevee instead of rust-bvh

I also used this opportunity to use `nalgebra::Unit` as much as
possible.
This commit is contained in:
Bruno BELANYI 2020-03-24 20:31:48 +01:00
parent d8a4a2eaad
commit 48bb3550cb
14 changed files with 124 additions and 103 deletions

View file

@ -19,7 +19,7 @@ name = "pathtracer"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
bvh = "0.3.2" beevee = { path = "../beevee" }
derive_more = "0.99.3" derive_more = "0.99.3"
enum_dispatch = "0.2.1" enum_dispatch = "0.2.1"
image = "0.23.0" image = "0.23.0"

View file

@ -2,14 +2,11 @@
//! A pathtracing crate //! A pathtracing crate
use bvh::nalgebra::{Point2, Point3, Vector3}; /// 3D points and vectors
pub use beevee::{Point, Vector};
/// A 2D point coordinate /// A 2D point coordinate
pub type Point2D = Point2<f32>; pub type Point2D = nalgebra::Point2<f32>;
/// A 3D point coordinate
pub type Point = Point3<f32>;
/// A 3D vector
pub type Vector = Vector3<f32>;
pub mod core; pub mod core;
pub mod light; pub mod light;

View file

@ -1,13 +1,14 @@
use super::{Light, SpatialLight}; use super::{Light, SpatialLight};
use crate::core::LinearColor; use crate::core::LinearColor;
use crate::{Point, Vector}; use crate::{Point, Vector};
use nalgebra::Unit;
use serde::Deserialize; use serde::Deserialize;
/// Represent a light emanating from a far away source, with parallel rays on all points. /// Represent a light emanating from a far away source, with parallel rays on all points.
#[derive(Debug, PartialEq, Deserialize)] #[derive(Debug, PartialEq, Deserialize)]
pub struct DirectionalLight { pub struct DirectionalLight {
#[serde(deserialize_with = "crate::serialize::vector_normalizer")] #[serde(deserialize_with = "crate::serialize::vector_normalizer")]
direction: Vector, direction: Unit<Vector>,
color: LinearColor, color: LinearColor,
} }
@ -22,15 +23,12 @@ impl DirectionalLight {
/// # use pathtracer::Vector; /// # use pathtracer::Vector;
/// # /// #
/// let dir_light = DirectionalLight::new( /// let dir_light = DirectionalLight::new(
/// Vector::new(1.0, 0.0, 0.0), /// Vector::x_axis(),
/// LinearColor::new(1.0, 0.0, 1.0), /// LinearColor::new(1.0, 0.0, 1.0),
/// ); /// );
/// ``` /// ```
pub fn new(direction: Vector, color: LinearColor) -> Self { pub fn new(direction: Unit<Vector>, color: LinearColor) -> Self {
DirectionalLight { DirectionalLight { direction, color }
direction: direction.normalize(),
color,
}
} }
} }
@ -41,8 +39,8 @@ impl Light for DirectionalLight {
} }
impl SpatialLight for DirectionalLight { impl SpatialLight for DirectionalLight {
fn to_source(&self, _: &Point) -> (Vector, f32) { fn to_source(&self, _: &Point) -> (Unit<Vector>, f32) {
(self.direction * -1., std::f32::INFINITY) (-self.direction, std::f32::INFINITY)
} }
} }
@ -52,7 +50,7 @@ mod test {
#[test] #[test]
fn new_works() { fn new_works() {
let direction = Vector::new(1., 0., 0.); let direction = Vector::x_axis();
let color = LinearColor::new(1., 1., 1.); let color = LinearColor::new(1., 1., 1.);
let light = DirectionalLight::new(direction, color.clone()); let light = DirectionalLight::new(direction, color.clone());
let res = DirectionalLight { direction, color }; let res = DirectionalLight { direction, color };
@ -60,7 +58,7 @@ mod test {
} }
fn simple_light() -> impl SpatialLight { fn simple_light() -> impl SpatialLight {
let direction = Vector::new(1., 0., 0.); let direction = Vector::x_axis();
let color = LinearColor::new(1., 1., 1.); let color = LinearColor::new(1., 1., 1.);
DirectionalLight::new(direction, color) DirectionalLight::new(direction, color)
} }
@ -76,7 +74,10 @@ mod test {
fn to_source_is_correct() { fn to_source_is_correct() {
let light = simple_light(); let light = simple_light();
let ans = light.to_source(&Point::new(1., 0., 0.)); let ans = light.to_source(&Point::new(1., 0., 0.));
let expected = (Vector::new(-1., 0., 0.), std::f32::INFINITY); let expected = (
Unit::new_normalize(Vector::new(-1., 0., 0.)),
std::f32::INFINITY,
);
assert_eq!(ans, expected) assert_eq!(ans, expected)
} }
@ -86,7 +87,7 @@ mod test {
let light: DirectionalLight = serde_yaml::from_str(yaml).unwrap(); let light: DirectionalLight = serde_yaml::from_str(yaml).unwrap();
assert_eq!( assert_eq!(
light, light,
DirectionalLight::new(Vector::new(1., 0., 0.), LinearColor::new(1., 0.5, 0.2)) DirectionalLight::new(Vector::x_axis(), LinearColor::new(1., 0.5, 0.2))
) )
} }
} }

View file

@ -2,6 +2,7 @@
use super::core::LinearColor; use super::core::LinearColor;
use super::{Point, Vector}; use super::{Point, Vector};
use nalgebra::Unit;
/// Represent a light in the scene being rendered. /// Represent a light in the scene being rendered.
pub trait Light: std::fmt::Debug { pub trait Light: std::fmt::Debug {
@ -12,7 +13,7 @@ pub trait Light: std::fmt::Debug {
/// Represent a light which has an abstract position in the scene being rendered. /// Represent a light which has an abstract position in the scene being rendered.
pub trait SpatialLight: Light { pub trait SpatialLight: Light {
/// Get a unit vector from the origin to the position of the light, and its distance /// Get a unit vector from the origin to the position of the light, and its distance
fn to_source(&self, origin: &Point) -> (Vector, f32); fn to_source(&self, origin: &Point) -> (Unit<Vector>, f32);
} }
mod ambient_light; mod ambient_light;

View file

@ -1,6 +1,7 @@
use super::{Light, SpatialLight}; use super::{Light, SpatialLight};
use crate::core::LinearColor; use crate::core::LinearColor;
use crate::{Point, Vector}; use crate::{Point, Vector};
use nalgebra::Unit;
use serde::Deserialize; use serde::Deserialize;
/// Represent a light emanating from a point in space, following the square distance law. /// Represent a light emanating from a point in space, following the square distance law.
@ -38,10 +39,10 @@ impl Light for PointLight {
} }
impl SpatialLight for PointLight { impl SpatialLight for PointLight {
fn to_source(&self, point: &Point) -> (Vector, f32) { fn to_source(&self, point: &Point) -> (Unit<Vector>, f32) {
let delt = self.position - point; let delt = self.position - point;
let dist = delt.norm(); let dist = delt.norm();
(delt.normalize(), dist) (Unit::new_normalize(delt), dist)
} }
} }
@ -75,7 +76,7 @@ mod test {
fn to_source_is_correct() { fn to_source_is_correct() {
let light = simple_light(); let light = simple_light();
let ans = light.to_source(&Point::new(1., 0., 0.)); let ans = light.to_source(&Point::new(1., 0., 0.));
let expected = (Vector::new(-1., 0., 0.), 1.); let expected = (Unit::new_normalize(Vector::new(-1., 0., 0.)), 1.);
assert_eq!(ans, expected); assert_eq!(ans, expected);
} }

View file

@ -1,6 +1,7 @@
use super::{Light, SpatialLight}; use super::{Light, SpatialLight};
use crate::core::LinearColor; use crate::core::LinearColor;
use crate::{Point, Vector}; use crate::{Point, Vector};
use nalgebra::Unit;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
/// Represent a light emanating from a directed light-source, outputting rays in a cone. /// Represent a light emanating from a directed light-source, outputting rays in a cone.
@ -9,7 +10,7 @@ use serde::{Deserialize, Deserializer};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct SpotLight { pub struct SpotLight {
position: Point, position: Point,
direction: Vector, direction: Unit<Vector>,
cosine_value: f32, cosine_value: f32,
color: LinearColor, color: LinearColor,
} }
@ -18,13 +19,13 @@ impl SpotLight {
/// Construct a SpotLight with the given FOV in radian. /// Construct a SpotLight with the given FOV in radian.
pub fn radians_new( pub fn radians_new(
position: Point, position: Point,
direction: Vector, direction: Unit<Vector>,
fov_rad: f32, fov_rad: f32,
color: LinearColor, color: LinearColor,
) -> Self { ) -> Self {
SpotLight { SpotLight {
position, position,
direction: direction.normalize(), direction,
cosine_value: (fov_rad / 2.).cos(), cosine_value: (fov_rad / 2.).cos(),
color, color,
} }
@ -33,7 +34,7 @@ impl SpotLight {
/// Construct a SpotLight with the given FOV in degrees. /// Construct a SpotLight with the given FOV in degrees.
pub fn degrees_new( pub fn degrees_new(
position: Point, position: Point,
direction: Vector, direction: Unit<Vector>,
fov_deg: f32, fov_deg: f32,
color: LinearColor, color: LinearColor,
) -> Self { ) -> Self {
@ -59,10 +60,10 @@ impl Light for SpotLight {
} }
impl SpatialLight for SpotLight { impl SpatialLight for SpotLight {
fn to_source(&self, point: &Point) -> (Vector, f32) { fn to_source(&self, point: &Point) -> (Unit<Vector>, f32) {
let delt = self.position - point; let delt = self.position - point;
let dist = delt.norm(); let dist = delt.norm();
(delt.normalize(), dist) (Unit::new_normalize(delt), dist)
} }
} }
@ -70,7 +71,7 @@ impl SpatialLight for SpotLight {
struct SerializedSpotLight { struct SerializedSpotLight {
position: Point, position: Point,
#[serde(deserialize_with = "crate::serialize::vector_normalizer")] #[serde(deserialize_with = "crate::serialize::vector_normalizer")]
direction: Vector, direction: Unit<Vector>,
fov: f32, fov: f32,
color: LinearColor, color: LinearColor,
} }
@ -99,7 +100,7 @@ mod test {
fn radian_new_works() { fn radian_new_works() {
let light = SpotLight::radians_new( let light = SpotLight::radians_new(
Point::origin(), Point::origin(),
Vector::new(1., 0., 0.), Vector::x_axis(),
std::f32::consts::PI / 2., std::f32::consts::PI / 2.,
LinearColor::new(1., 1., 1.), LinearColor::new(1., 1., 1.),
); );
@ -109,7 +110,7 @@ mod test {
light, light,
SpotLight { SpotLight {
position: Point::origin(), position: Point::origin(),
direction: Vector::new(1., 0., 0.), direction: Vector::x_axis(),
cosine_value: calculated_cosine_value, cosine_value: calculated_cosine_value,
color: LinearColor::new(1., 1., 1.), color: LinearColor::new(1., 1., 1.),
} }
@ -122,7 +123,7 @@ mod test {
fn degrees_new_works() { fn degrees_new_works() {
let light = SpotLight::degrees_new( let light = SpotLight::degrees_new(
Point::origin(), Point::origin(),
Vector::new(1., 0., 0.), Vector::x_axis(),
60., 60.,
LinearColor::new(1., 1., 1.), LinearColor::new(1., 1., 1.),
); );
@ -131,7 +132,7 @@ mod test {
light, light,
SpotLight { SpotLight {
position: Point::origin(), position: Point::origin(),
direction: Vector::new(1., 0., 0.), direction: Vector::x_axis(),
cosine_value: calculated_cosine_value, cosine_value: calculated_cosine_value,
color: LinearColor::new(1., 1., 1.), color: LinearColor::new(1., 1., 1.),
} }
@ -143,7 +144,7 @@ mod test {
fn simple_light() -> impl SpatialLight { fn simple_light() -> impl SpatialLight {
SpotLight::degrees_new( SpotLight::degrees_new(
Point::origin(), Point::origin(),
Vector::new(1., 0., 0.), Vector::x_axis(),
90., 90.,
LinearColor::new(1., 1., 1.), LinearColor::new(1., 1., 1.),
) )
@ -181,7 +182,7 @@ mod test {
fn to_source_is_correct() { fn to_source_is_correct() {
let light = simple_light(); let light = simple_light();
let ans = light.to_source(&Point::new(1., 0., 0.)); let ans = light.to_source(&Point::new(1., 0., 0.));
let expected = (Vector::new(-1., 0., 0.), 1.); let expected = (Unit::new_normalize(Vector::new(-1., 0., 0.)), 1.);
assert_eq!(ans, expected); assert_eq!(ans, expected);
} }
@ -198,7 +199,7 @@ mod test {
light, light,
SpotLight::degrees_new( SpotLight::degrees_new(
Point::origin(), Point::origin(),
Vector::new(1., 0., 0.), Vector::x_axis(),
90., 90.,
LinearColor::new(1., 0.5, 0.2) LinearColor::new(1., 0.5, 0.2)
) )

View file

@ -141,7 +141,7 @@ mod test {
let expected = LightAggregate::new( let expected = LightAggregate::new(
vec![AmbientLight::new(LinearColor::new(1., 0.5, 0.2))], vec![AmbientLight::new(LinearColor::new(1., 0.5, 0.2))],
vec![DirectionalLight::new( vec![DirectionalLight::new(
Vector::new(1., 0., 0.), Vector::x_axis(),
LinearColor::new(1., 0.5, 0.2), LinearColor::new(1., 0.5, 0.2),
)], )],
vec![PointLight::new( vec![PointLight::new(
@ -150,7 +150,7 @@ mod test {
)], )],
vec![SpotLight::degrees_new( vec![SpotLight::degrees_new(
Point::origin(), Point::origin(),
Vector::new(1., 0., 0.), Vector::x_axis(),
90., 90.,
LinearColor::new(1., 0.5, 0.2), LinearColor::new(1., 0.5, 0.2),
)], )],

View file

@ -3,8 +3,8 @@
use crate::material::MaterialEnum; use crate::material::MaterialEnum;
use crate::shape::{Shape, ShapeEnum}; use crate::shape::{Shape, ShapeEnum};
use crate::texture::TextureEnum; use crate::texture::TextureEnum;
use bvh::aabb::{Bounded, AABB}; use crate::Point;
use bvh::bounding_hierarchy::BHShape; use beevee::aabb::{Bounded, AABB};
use serde::Deserialize; use serde::Deserialize;
/// An object being rendered in the scene. /// An object being rendered in the scene.
@ -60,14 +60,9 @@ impl Bounded for Object {
fn aabb(&self) -> AABB { fn aabb(&self) -> AABB {
self.shape.aabb() self.shape.aabb()
} }
}
impl BHShape for Object {
fn set_bh_node_index(&mut self, index: usize) {
self.index = index
}
fn bh_node_index(&self) -> usize { fn centroid(&self) -> Point {
self.index self.shape.centroid()
} }
} }
@ -79,7 +74,6 @@ mod test {
use crate::material::UniformMaterial; use crate::material::UniformMaterial;
use crate::shape::Sphere; use crate::shape::Sphere;
use crate::texture::UniformTexture; use crate::texture::UniformTexture;
use crate::Point;
fn simple_object() -> Object { fn simple_object() -> Object {
let shape = Sphere::new(Point::new(5., 0., 0.), 1.); let shape = Sphere::new(Point::new(5., 0., 0.), 1.);

View file

@ -1,7 +1,5 @@
//! Scene rendering logic //! Scene rendering logic
use std::cmp::Ordering;
use super::{light_aggregate::LightAggregate, object::Object, utils::*}; use super::{light_aggregate::LightAggregate, object::Object, utils::*};
use crate::{ use crate::{
core::{Camera, LightProperties, LinearColor, ReflTransEnum}, core::{Camera, LightProperties, LinearColor, ReflTransEnum},
@ -10,8 +8,9 @@ use crate::{
texture::Texture, texture::Texture,
{Point, Vector}, {Point, Vector},
}; };
use bvh::{bvh::BVH, ray::Ray}; use beevee::{bvh::BVH, ray::Ray};
use image::RgbImage; use image::RgbImage;
use nalgebra::Unit;
use rand::prelude::thread_rng; use rand::prelude::thread_rng;
use rand::Rng; use rand::Rng;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
@ -120,12 +119,12 @@ impl Scene {
fn pixel(&self, x: f32, y: f32) -> LinearColor { fn pixel(&self, x: f32, y: f32) -> LinearColor {
let (x, y) = self.camera.film().pixel_ratio(x, y); let (x, y) = self.camera.film().pixel_ratio(x, y);
let pixel = self.camera.film().pixel_at_ratio(x, y); let pixel = self.camera.film().pixel_at_ratio(x, y);
let direction = (pixel - self.camera.origin()).normalize(); let direction = Unit::new_normalize(pixel - self.camera.origin());
let indices = RefractionInfo::with_index(self.diffraction_index); let indices = RefractionInfo::with_index(self.diffraction_index);
self.cast_ray(Ray::new(pixel, direction)) self.cast_ray(Ray::new(pixel, direction))
.map_or_else(LinearColor::black, |(t, obj)| { .map_or_else(LinearColor::black, |(t, obj)| {
self.color_at( self.color_at(
pixel + direction * t, pixel + direction.as_ref() * t,
obj, obj,
direction, direction,
self.reflection_limit, self.reflection_limit,
@ -151,19 +150,15 @@ impl Scene {
fn cast_ray(&self, ray: Ray) -> Option<(f32, &Object)> { fn cast_ray(&self, ray: Ray) -> Option<(f32, &Object)> {
self.bvh self.bvh
.traverse(&ray, &self.objects) .walk(&ray, &self.objects)
.iter() .and_then(|o| o.shape.intersect(&ray).map(|t| (t, o)))
.filter_map(|obj| obj.shape.intersect(&ray).map(|distance| (distance, *obj)))
.min_by(|(dist_a, _), (dist_b, _)| {
dist_a.partial_cmp(dist_b).unwrap_or(Ordering::Equal)
})
} }
fn color_at( fn color_at(
&self, &self,
point: Point, point: Point,
object: &Object, object: &Object,
incident_ray: Vector, incident_ray: Unit<Vector>,
reflection_limit: u32, reflection_limit: u32,
mut indices: RefractionInfo, mut indices: RefractionInfo,
) -> LinearColor { ) -> LinearColor {
@ -203,14 +198,14 @@ impl Scene {
&self, &self,
point: Point, point: Point,
transparency: f32, transparency: f32,
refracted: Vector, refracted: Unit<Vector>,
reflection_limit: u32, reflection_limit: u32,
indices: RefractionInfo, indices: RefractionInfo,
) -> LinearColor { ) -> LinearColor {
if transparency > 1e-5 && reflection_limit > 0 { if transparency > 1e-5 && reflection_limit > 0 {
let refraction_start = point + refracted * 0.001; let refraction_start = point + refracted.as_ref() * 0.001;
if let Some((t, obj)) = self.cast_ray(Ray::new(refraction_start, refracted)) { if let Some((t, obj)) = self.cast_ray(Ray::new(refraction_start, refracted)) {
let resulting_position = refraction_start + refracted * t; let resulting_position = refraction_start + refracted.as_ref() * t;
let refracted = self.color_at( let refracted = self.color_at(
resulting_position, resulting_position,
obj, obj,
@ -227,14 +222,14 @@ impl Scene {
fn reflection( fn reflection(
&self, &self,
point: Point, point: Point,
reflected: Vector, reflected: Unit<Vector>,
reflection_limit: u32, reflection_limit: u32,
indices: RefractionInfo, indices: RefractionInfo,
) -> LinearColor { ) -> LinearColor {
if reflection_limit > 0 { if reflection_limit > 0 {
let reflection_start = point + reflected * 0.001; let reflection_start = point + reflected.as_ref() * 0.001;
if let Some((t, obj)) = self.cast_ray(Ray::new(reflection_start, reflected)) { if let Some((t, obj)) = self.cast_ray(Ray::new(reflection_start, reflected)) {
let resulting_position = reflection_start + reflected * t; let resulting_position = reflection_start + reflected.as_ref() * t;
let color = self.color_at( let color = self.color_at(
resulting_position, resulting_position,
obj, obj,
@ -253,8 +248,8 @@ impl Scene {
point: Point, point: Point,
object_color: LinearColor, object_color: LinearColor,
properties: &LightProperties, properties: &LightProperties,
normal: Vector, normal: Unit<Vector>,
reflected: Vector, reflected: Unit<Vector>,
) -> LinearColor { ) -> LinearColor {
let ambient = self.illuminate_ambient(object_color.clone()); let ambient = self.illuminate_ambient(object_color.clone());
let spatial = self.illuminate_spatial(point, properties, normal, reflected); let spatial = self.illuminate_spatial(point, properties, normal, reflected);
@ -273,14 +268,14 @@ impl Scene {
&self, &self,
point: Point, point: Point,
properties: &LightProperties, properties: &LightProperties,
normal: Vector, normal: Unit<Vector>,
reflected: Vector, reflected: Unit<Vector>,
) -> LinearColor { ) -> LinearColor {
self.lights self.lights
.spatial_lights_iter() .spatial_lights_iter()
.map(|light| { .map(|light| {
let (direction, t) = light.to_source(&point); let (direction, t) = light.to_source(&point);
let light_ray = Ray::new(point + 0.001 * direction, direction); let light_ray = Ray::new(point + 0.001 * direction.as_ref(), direction);
match self.cast_ray(light_ray) { match self.cast_ray(light_ray) {
// Take shadows into account // Take shadows into account
Some((obstacle_t, _)) if obstacle_t < t => return LinearColor::black(), Some((obstacle_t, _)) if obstacle_t < t => return LinearColor::black(),

View file

@ -1,19 +1,19 @@
use crate::Vector; use crate::Vector;
use nalgebra::Unit;
pub fn reflected(incident: Vector, normal: Vector) -> Vector { pub fn reflected(incident: Unit<Vector>, normal: Unit<Vector>) -> Unit<Vector> {
let proj = incident.dot(&normal); let proj = incident.dot(&normal);
let delt = normal * (proj * 2.); let delt = normal.into_inner() * (proj * 2.);
(incident - delt).normalize() Unit::new_normalize(incident.as_ref() - delt)
} }
/// Returns None if the ray was totally reflected, Some(refracted_ray, reflected_amount) if not /// Returns None if the ray was totally reflected, Some(refracted_ray, reflected_amount) if not
/// Adds an element to the top of indices that should be removed
pub fn refracted( pub fn refracted(
incident: Vector, incident: Unit<Vector>,
normal: Vector, normal: Unit<Vector>,
indices: &mut RefractionInfo, indices: &mut RefractionInfo,
new_index: f32, new_index: f32,
) -> Option<(Vector, f32)> { ) -> Option<(Unit<Vector>, f32)> {
let cos1 = incident.dot(&normal); let cos1 = incident.dot(&normal);
let normal = if cos1 < 0. { let normal = if cos1 < 0. {
// Entering object, change the medium // Entering object, change the medium
@ -32,12 +32,12 @@ pub fn refracted(
} }
let cos1 = cos1.abs(); let cos1 = cos1.abs();
let cos2 = k.sqrt(); let cos2 = k.sqrt();
let refracted = eta * incident + (eta * cos1 - cos2) * normal; let refracted = eta * incident.as_ref() + (eta * cos1 - cos2) * normal.as_ref();
let f_r = (n_2 * cos1 - n_1 * cos2) / (n_2 * cos1 + n_1 * cos2); let f_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 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.; let refl_t = (f_r * f_r + f_t * f_t) / 2.;
//Some((refracted, 0.)) //Some((refracted, 0.))
Some((refracted.normalize(), refl_t)) Some((Unit::new_normalize(refracted), refl_t))
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]

View file

@ -1,15 +1,16 @@
//! Helper functions to deserialize `Vector` values. //! Helper functions to deserialize `Vector` values.
use crate::Vector; use crate::Vector;
use nalgebra::Unit;
use serde::de::{Deserialize, Deserializer}; use serde::de::{Deserialize, Deserializer};
/// Deserialize a vector. /// Deserialize a vector.
/// ///
/// Needs a custom implementation to make sur the vector is normalized when deserialized. /// Needs a custom implementation to make sur the vector is normalized when deserialized.
pub fn vector_normalizer<'de, D>(deserializer: D) -> Result<Vector, D::Error> pub fn vector_normalizer<'de, D>(deserializer: D) -> Result<Unit<Vector>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let v: Vector = Deserialize::deserialize(deserializer)?; let v: Vector = Deserialize::deserialize(deserializer)?;
Ok(v.normalize()) Ok(Unit::new_normalize(v))
} }

View file

@ -1,10 +1,11 @@
//! Various shape implementations //! Various shape implementations
use super::{Point, Point2D, Vector}; use super::{Point, Point2D, Vector};
use bvh::{ use beevee::{
aabb::{Bounded, AABB}, aabb::{Bounded, AABB},
ray::Ray, ray::Ray,
}; };
use nalgebra::Unit;
use serde::Deserialize; use serde::Deserialize;
/// All the existing `Shape` implementation. /// All the existing `Shape` implementation.
@ -24,17 +25,23 @@ pub trait Shape: std::fmt::Debug {
/// Return the distance at which the object intersects with the ray, or None if it does not. /// Return the distance at which the object intersects with the ray, or None if it does not.
fn intersect(&self, ray: &Ray) -> Option<f32>; fn intersect(&self, ray: &Ray) -> Option<f32>;
/// Return the unit vector corresponding to the normal at this point of the shape. /// Return the unit vector corresponding to the normal at this point of the shape.
fn normal(&self, point: &Point) -> Vector; fn normal(&self, point: &Point) -> Unit<Vector>;
/// Project the point from the shape's surface to its texel coordinates. /// Project the point from the shape's surface to its texel coordinates.
fn project_texel(&self, point: &Point) -> Point2D; fn project_texel(&self, point: &Point) -> Point2D;
/// Enclose the `Shape` in an axi-aligned bounding-box. /// Enclose the `Shape` in an axi-aligned bounding-box.
fn aabb(&self) -> AABB; fn aabb(&self) -> AABB;
/// Return the centroid of the shape.
fn centroid(&self) -> Point;
} }
impl Bounded for dyn Shape { impl Bounded for dyn Shape {
fn aabb(&self) -> AABB { fn aabb(&self) -> AABB {
self.aabb() self.aabb()
} }
fn centroid(&self) -> Point {
self.centroid()
}
} }
mod sphere; mod sphere;

View file

@ -1,7 +1,8 @@
use super::Shape; use super::Shape;
use crate::{Point, Point2D, Vector}; use crate::{Point, Point2D, Vector};
use bvh::aabb::AABB; use beevee::aabb::AABB;
use bvh::ray::Ray; use beevee::ray::Ray;
use nalgebra::Unit;
use serde::Deserialize; use serde::Deserialize;
/// Represent a sphere shape inside the scene. /// Represent a sphere shape inside the scene.
@ -67,13 +68,13 @@ impl Shape for Sphere {
} }
} }
fn normal(&self, point: &Point) -> Vector { fn normal(&self, point: &Point) -> Unit<Vector> {
let delt = if self.inverted { let delt = if self.inverted {
self.center - point self.center - point
} else { } else {
point - self.center point - self.center
}; };
delt.normalize() Unit::new_normalize(delt)
} }
fn project_texel(&self, point: &Point) -> Point2D { fn project_texel(&self, point: &Point) -> Point2D {
@ -90,6 +91,10 @@ impl Shape for Sphere {
let max = self.center + delt; let max = self.center + delt;
AABB::with_bounds(min, max) AABB::with_bounds(min, max)
} }
fn centroid(&self) -> Point {
self.center
}
} }
#[cfg(test)] #[cfg(test)]
@ -103,21 +108,30 @@ mod test {
#[test] #[test]
fn intersect_along_axis_works() { fn intersect_along_axis_works() {
let sphere = simple_sphere(); let sphere = simple_sphere();
let ray = Ray::new(Point::new(-2., 0., 0.), Vector::new(1., 0., 0.)); let ray = Ray::new(
Point::new(-2., 0., 0.),
Unit::new_normalize(Vector::new(1., 0., 0.)),
);
assert_eq!(sphere.intersect(&ray), Some(1.)) assert_eq!(sphere.intersect(&ray), Some(1.))
} }
#[test] #[test]
fn non_intersect_along_axis_works() { fn non_intersect_along_axis_works() {
let sphere = simple_sphere(); let sphere = simple_sphere();
let ray = Ray::new(Point::new(-2., 0., 0.), Vector::new(-1., 0., 0.)); let ray = Ray::new(
Point::new(-2., 0., 0.),
Unit::new_normalize(Vector::new(-1., 0., 0.)),
);
assert_eq!(sphere.intersect(&ray), None) assert_eq!(sphere.intersect(&ray), None)
} }
#[test] #[test]
fn intersect_not_on_axis() { fn intersect_not_on_axis() {
let sphere = simple_sphere(); let sphere = simple_sphere();
let ray = Ray::new(Point::new(1., 1., 1.), Vector::new(-1., -1., -1.)); let ray = Ray::new(
Point::new(1., 1., 1.),
Unit::new_normalize(Vector::new(-1., -1., -1.)),
);
assert_eq!(sphere.intersect(&ray), Some(f32::sqrt(3.) - 1.)) assert_eq!(sphere.intersect(&ray), Some(f32::sqrt(3.) - 1.))
} }
@ -126,7 +140,7 @@ mod test {
let sphere = simple_sphere(); let sphere = simple_sphere();
assert_eq!( assert_eq!(
sphere.normal(&Point::new(-1., 0., 0.)), sphere.normal(&Point::new(-1., 0., 0.)),
Vector::new(-1., 0., 0.) Unit::new_normalize(Vector::new(-1., 0., 0.))
) )
} }
@ -135,7 +149,7 @@ mod test {
let sphere = Sphere::inverted_new(Point::origin(), 1.); let sphere = Sphere::inverted_new(Point::origin(), 1.);
assert_eq!( assert_eq!(
sphere.normal(&Point::new(-1., 0., 0.)), sphere.normal(&Point::new(-1., 0., 0.)),
Vector::new(1., 0., 0.) Unit::new_normalize(Vector::new(1., 0., 0.))
) )
} }

View file

@ -1,7 +1,8 @@
use super::Shape; use super::Shape;
use crate::{Point, Point2D, Vector}; use crate::{Point, Point2D, Vector};
use bvh::aabb::AABB; use beevee::aabb::AABB;
use bvh::ray::Ray; use beevee::ray::Ray;
use nalgebra::Unit;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
/// Represent a triangle inside the scene. /// Represent a triangle inside the scene.
@ -88,8 +89,8 @@ impl Shape for Triangle {
} }
} }
fn normal(&self, _: &Point) -> Vector { fn normal(&self, _: &Point) -> Unit<Vector> {
self.c0c1.cross(&self.c0c2).normalize() Unit::new_normalize(self.c0c1.cross(&self.c0c2))
} }
fn project_texel(&self, point: &Point) -> Point2D { fn project_texel(&self, point: &Point) -> Point2D {
@ -102,6 +103,10 @@ impl Shape for Triangle {
.grow(&(self.c0 + self.c0c1)) .grow(&(self.c0 + self.c0c1))
.grow(&(self.c0 + self.c0c2)) .grow(&(self.c0 + self.c0c2))
} }
fn centroid(&self) -> Point {
self.c0 + (self.c0c1 + self.c0c2) / 2.
}
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -132,6 +137,7 @@ impl<'de> Deserialize<'de> for Triangle {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use nalgebra::Unit;
fn simple_triangle() -> Triangle { fn simple_triangle() -> Triangle {
Triangle::new( Triangle::new(
@ -146,7 +152,7 @@ mod test {
let triangle = simple_triangle(); let triangle = simple_triangle();
let ans = triangle.intersect(&Ray::new( let ans = triangle.intersect(&Ray::new(
Point::new(-1., 0.5, 0.5), Point::new(-1., 0.5, 0.5),
Vector::new(1., 0., 0.), Unit::new_normalize(Vector::new(1., 0., 0.)),
)); ));
assert_eq!(ans, Some(1.0)) assert_eq!(ans, Some(1.0))
} }
@ -156,7 +162,7 @@ mod test {
let triangle = simple_triangle(); let triangle = simple_triangle();
let ans = triangle.intersect(&Ray::new( let ans = triangle.intersect(&Ray::new(
Point::new(-1., 0.5, 0.), Point::new(-1., 0.5, 0.),
Vector::new(1., 0., 0.5), Unit::new_normalize(Vector::new(1., 0., 0.5)),
)); ));
assert!(ans.is_some()); assert!(ans.is_some());
assert!((ans.unwrap() - f32::sqrt(1.0 + 0.25)).abs() < 1e-5) assert!((ans.unwrap() - f32::sqrt(1.0 + 0.25)).abs() < 1e-5)
@ -165,7 +171,10 @@ mod test {
#[test] #[test]
fn intersect_out_of_bounds_is_none() { fn intersect_out_of_bounds_is_none() {
let triangle = simple_triangle(); let triangle = simple_triangle();
let ans = triangle.intersect(&Ray::new(Point::new(-1., 0.5, 0.), Vector::new(1., 1., 1.))); let ans = triangle.intersect(&Ray::new(
Point::new(-1., 0.5, 0.),
Unit::new_normalize(Vector::new(1., 1., 1.)),
));
assert_eq!(ans, None) assert_eq!(ans, None)
} }
@ -173,7 +182,7 @@ mod test {
fn normal_works() { fn normal_works() {
let triangle = simple_triangle(); let triangle = simple_triangle();
let normal = triangle.normal(&Point::origin()); let normal = triangle.normal(&Point::origin());
assert_eq!(normal, Vector::new(-1., 0., 0.)); assert_eq!(normal, Unit::new_normalize(Vector::new(-1., 0., 0.)));
} }
#[test] #[test]