From bde635e19e77c43d20b57c3afaf1c69d0db2fcd2 Mon Sep 17 00:00:00 2001 From: Walther Date: Sat, 2 Mar 2024 14:22:14 +0200 Subject: [PATCH 1/2] improvement: introduce Direction, type alias for Unit --- clovers/benches/aabb.rs | 3 ++- clovers/benches/triangle.rs | 3 ++- clovers/src/bvhnode.rs | 20 +++++++++----- clovers/src/camera.rs | 8 +++--- clovers/src/colorize.rs | 5 +++- clovers/src/hitable.rs | 12 ++++----- clovers/src/lib.rs | 7 ++++- clovers/src/materials.rs | 18 +++++++------ clovers/src/materials/dielectric.rs | 14 +++++----- clovers/src/materials/dispersive.rs | 14 +++++----- clovers/src/materials/gltf.rs | 14 +++++----- clovers/src/materials/metal.rs | 9 ++++--- clovers/src/normals.rs | 10 +++---- clovers/src/objects/boxy.rs | 6 ++--- clovers/src/objects/constant_medium.rs | 8 +++--- clovers/src/objects/gltf.rs | 19 ++++++------- clovers/src/objects/moving_sphere.rs | 10 ++++--- clovers/src/objects/quad.rs | 15 ++++++----- clovers/src/objects/rotate.rs | 13 ++++++--- clovers/src/objects/sphere.rs | 19 ++++++++----- clovers/src/objects/stl.rs | 6 ++--- clovers/src/objects/translate.rs | 6 ++--- clovers/src/objects/triangle.rs | 37 +++++++++++++++++--------- clovers/src/onb.rs | 28 ++++++++++--------- clovers/src/pdf.rs | 22 +++++++-------- clovers/src/random.rs | 18 +++++++------ clovers/src/ray.rs | 6 ++--- 27 files changed, 205 insertions(+), 145 deletions(-) diff --git a/clovers/benches/aabb.rs b/clovers/benches/aabb.rs index e8b56f77..fa3f2502 100644 --- a/clovers/benches/aabb.rs +++ b/clovers/benches/aabb.rs @@ -1,6 +1,7 @@ use std::f32::{INFINITY, NEG_INFINITY}; use clovers::interval::Interval; +use clovers::random::random_unit_vector; use clovers::ray::Ray; use clovers::wavelength::random_wavelength; use clovers::{aabb::*, Vec3}; @@ -81,7 +82,7 @@ fn random_aabb(rng: &mut SmallRng) -> AABB { fn random_ray(rng: &mut SmallRng) -> Ray { black_box(Ray { origin: Vec3::new(0.0, 0.0, 0.0), - direction: Vec3::new(rng.gen(), rng.gen(), rng.gen()), + direction: random_unit_vector(rng), time: rng.gen(), wavelength: random_wavelength(rng), }) diff --git a/clovers/benches/triangle.rs b/clovers/benches/triangle.rs index 3828e477..e7b38d1b 100644 --- a/clovers/benches/triangle.rs +++ b/clovers/benches/triangle.rs @@ -3,6 +3,7 @@ use std::f32::{INFINITY, NEG_INFINITY}; use clovers::hitable::HitableTrait; use clovers::materials::Material; use clovers::objects::Triangle; +use clovers::random::random_unit_vector; use clovers::ray::Ray; use clovers::wavelength::random_wavelength; use clovers::Vec3; @@ -71,7 +72,7 @@ fn random_ray() -> Ray { let mut rng = SmallRng::from_entropy(); black_box(Ray { origin: Vec3::new(0.0, 0.0, 0.0), - direction: Vec3::new(rng.gen(), rng.gen(), rng.gen()), + direction: random_unit_vector(&mut rng), time: rng.gen(), wavelength: random_wavelength(&mut rng), }) diff --git a/clovers/src/bvhnode.rs b/clovers/src/bvhnode.rs index ae8cc56c..393a3e49 100644 --- a/clovers/src/bvhnode.rs +++ b/clovers/src/bvhnode.rs @@ -9,7 +9,7 @@ use crate::{ hitable::{Empty, HitRecord, Hitable, HitableTrait}, ray::Ray, wavelength::Wavelength, - Box, Float, Vec, Vec3, + Box, Direction, Float, Vec, Vec3, }; /// Bounding Volume Hierarchy Node. @@ -188,17 +188,25 @@ impl<'scene> HitableTrait for BVHNode<'scene> { fn pdf_value( &self, origin: Vec3, - vector: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, ) -> Float { match (&*self.left, &*self.right) { - (_, Hitable::Empty(_)) => self.left.pdf_value(origin, vector, wavelength, time, rng), - (Hitable::Empty(_), _) => self.right.pdf_value(origin, vector, wavelength, time, rng), + (_, Hitable::Empty(_)) => self + .left + .pdf_value(origin, direction, wavelength, time, rng), + (Hitable::Empty(_), _) => self + .right + .pdf_value(origin, direction, wavelength, time, rng), (_, _) => { - (self.left.pdf_value(origin, vector, wavelength, time, rng) - + self.right.pdf_value(origin, vector, wavelength, time, rng)) + (self + .left + .pdf_value(origin, direction, wavelength, time, rng) + + self + .right + .pdf_value(origin, direction, wavelength, time, rng)) / 2.0 } } diff --git a/clovers/src/camera.rs b/clovers/src/camera.rs index 85414bd4..631d0517 100644 --- a/clovers/src/camera.rs +++ b/clovers/src/camera.rs @@ -4,6 +4,7 @@ use crate::wavelength::random_wavelength; use crate::{random::random_in_unit_disk, ray::Ray, Float, Vec3, PI}; +use nalgebra::Unit; use rand::rngs::SmallRng; use rand::Rng; @@ -109,11 +110,12 @@ impl Camera { let time: Float = rng.gen_range(self.time_0..self.time_1); // Random wavelength for spectral rendering let wavelength = random_wavelength(rng); + let direction = + self.lower_left_corner + s * self.horizontal + t * self.vertical - self.origin - offset; + let direction = Unit::new_normalize(direction); Ray { origin: self.origin + offset, - direction: self.lower_left_corner + s * self.horizontal + t * self.vertical - - self.origin - - offset, + direction, time, wavelength, } diff --git a/clovers/src/colorize.rs b/clovers/src/colorize.rs index 0cf700f3..86e57704 100644 --- a/clovers/src/colorize.rs +++ b/clovers/src/colorize.rs @@ -10,6 +10,7 @@ use crate::{ wavelength::{wavelength_into_xyz, Wavelength}, Float, EPSILON_SHADOW_ACNE, }; +use nalgebra::Unit; use palette::{ chromatic_adaptation::AdaptInto, convert::IntoColorUnclamped, white_point::E, Clamp, Xyz, }; @@ -83,9 +84,11 @@ pub fn colorize( hit_record.position, )); let mixture_pdf = MixturePDF::new(light_ptr, scatter_record.pdf_ptr); + let direction = mixture_pdf.generate(rng); + let direction = Unit::new_normalize(direction); let scatter_ray = Ray { origin: hit_record.position, - direction: mixture_pdf.generate(rng), + direction, time: ray.time, wavelength: ray.wavelength, }; diff --git a/clovers/src/hitable.rs b/clovers/src/hitable.rs index f4634868..861e21cf 100644 --- a/clovers/src/hitable.rs +++ b/clovers/src/hitable.rs @@ -14,7 +14,7 @@ use crate::{ objects::{Boxy, ConstantMedium, MovingSphere, Quad, RotateY, Sphere, Translate, Triangle}, ray::Ray, wavelength::Wavelength, - Float, Vec3, + Direction, Float, Vec3, }; use enum_dispatch::enum_dispatch; @@ -28,7 +28,7 @@ pub struct HitRecord<'a> { /// 3D coordinate of the hitpoint pub position: Vec3, /// Surface normal from the hitpoint - pub normal: Vec3, + pub normal: Direction, /// U surface coordinate of the hitpoint pub u: Float, /// V surface coordinate of the hitpoint @@ -41,7 +41,7 @@ pub struct HitRecord<'a> { impl<'a> HitRecord<'a> { /// Helper function for getting normals pointing at the correct direction. TODO: consider removal? - pub fn set_face_normal(&mut self, ray: &Ray, outward_normal: Vec3) { + pub fn set_face_normal(&mut self, ray: &Ray, outward_normal: Direction) { self.front_face = ray.direction.dot(&outward_normal) < 0.0; if self.front_face { self.normal = outward_normal; @@ -95,7 +95,7 @@ impl HitableTrait for Empty { fn pdf_value( &self, _origin: Vec3, - _vector: Vec3, + _direction: Direction, _wavelength: Wavelength, _time: Float, _rng: &mut SmallRng, @@ -126,7 +126,7 @@ pub trait HitableTrait { fn pdf_value( &self, origin: Vec3, - vector: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, @@ -138,7 +138,7 @@ pub trait HitableTrait { /// Returns a tuple of `(front_face, normal)`. Used in lieu of `set_face_normal` in the Ray Tracing for the Rest Of Your Life book. #[must_use] -pub fn get_orientation(ray: &Ray, outward_normal: Vec3) -> (bool, Vec3) { +pub fn get_orientation(ray: &Ray, outward_normal: Direction) -> (bool, Direction) { let front_face = ray.direction.dot(&outward_normal) < 0.0; let normal = if front_face { outward_normal diff --git a/clovers/src/lib.rs b/clovers/src/lib.rs index cf46510c..5fdfc1f7 100644 --- a/clovers/src/lib.rs +++ b/clovers/src/lib.rs @@ -71,7 +71,10 @@ pub use alloc::boxed::Box; pub use alloc::vec::Vec; // Externals -use nalgebra::base::{Vector2, Vector3, Vector4}; +use nalgebra::{ + base::{Vector2, Vector3, Vector4}, + Unit, +}; // Internals pub mod aabb; @@ -123,6 +126,8 @@ pub type Vec2 = Vector2; pub type Vec3 = Vector3; /// Internal type alias: a nalgebra [Vector4] which is a vector with four dimensions, containing four of our internal [Float] types pub type Vec4 = Vector4; +/// Internal type alias: a nalgebra [Unit] of a [Vector3] +pub type Direction = Unit; /// Internal const: epsilon used for avoiding "shadow acne". This is mostly used for the initial minimum distance for ray hits after reflecting or scattering from a surface. pub const EPSILON_SHADOW_ACNE: Float = 0.001; /// Internal const: epsilon used for having a finitely-sized thickness for the bounding box of an infinitely-thin rectangle. Shouldn't be too small. diff --git a/clovers/src/materials.rs b/clovers/src/materials.rs index ba37c168..9eecb203 100644 --- a/clovers/src/materials.rs +++ b/clovers/src/materials.rs @@ -2,8 +2,9 @@ use alloc::string::String; use core::fmt::Debug; +use nalgebra::Unit; -use crate::{hitable::HitRecord, pdf::PDF, ray::Ray, Float, Vec3}; +use crate::{hitable::HitRecord, pdf::PDF, ray::Ray, Direction, Float, Vec3}; pub mod cone_light; pub mod dielectric; pub mod diffuse_light; @@ -139,17 +140,18 @@ pub struct ScatterRecord<'ray> { // TODO: are these up to date / correct? #[must_use] -fn reflect(vector: Vec3, normal: Vec3) -> Vec3 { - vector - 2.0 * vector.dot(&normal) * normal +fn reflect(vector: Direction, normal: Direction) -> Direction { + let v: Vec3 = *vector - 2.0 * vector.dot(&normal) * *normal; + Unit::new_normalize(v) } #[must_use] -fn refract(uv: Vec3, normal: Vec3, refraction_ratio: Float) -> Vec3 { - let cos_theta: Float = -uv.dot(&normal); +fn refract(vector: Direction, normal: Direction, refraction_ratio: Float) -> Direction { + let cos_theta: Float = -vector.dot(&normal); let cos_theta = cos_theta.min(1.0); // Clamp - let r_out_parallel: Vec3 = refraction_ratio * (uv + cos_theta * normal); - let r_out_perp: Vec3 = -(1.0 - r_out_parallel.norm_squared()).sqrt() * normal; - r_out_parallel + r_out_perp + let r_out_parallel: Vec3 = refraction_ratio * (*vector + cos_theta * *normal); + let r_out_perp: Vec3 = -(1.0 - r_out_parallel.norm_squared()).sqrt() * *normal; + Unit::new_normalize(r_out_parallel + r_out_perp) } #[must_use] diff --git a/clovers/src/materials/dielectric.rs b/clovers/src/materials/dielectric.rs index 785be4dd..a66a81a9 100644 --- a/clovers/src/materials/dielectric.rs +++ b/clovers/src/materials/dielectric.rs @@ -5,7 +5,7 @@ use crate::{ hitable::HitRecord, pdf::{ZeroPDF, PDF}, ray::Ray, - Float, Vec3, + Direction, Float, }; use palette::{white_point::E, Xyz}; use rand::rngs::SmallRng; @@ -46,18 +46,18 @@ impl MaterialTrait for Dielectric { self.refractive_index }; - let unit_direction: Vec3 = ray.direction.normalize(); - let cos_theta: Float = (-unit_direction.dot(&hit_record.normal)).min(1.0); + let direction: Direction = ray.direction; + let cos_theta: Float = (-direction.dot(&hit_record.normal)).min(1.0); let sin_theta: Float = (1.0 - cos_theta * cos_theta).sqrt(); - let specular_direction: Vec3 = if refraction_ratio * sin_theta > 1.0 { - reflect(unit_direction, hit_record.normal) + let specular_direction: Direction = if refraction_ratio * sin_theta > 1.0 { + reflect(direction, hit_record.normal) } else { let reflect_probability: Float = schlick(cos_theta, refraction_ratio); if rng.gen::() < reflect_probability { - reflect(unit_direction, hit_record.normal) + reflect(direction, hit_record.normal) } else { // Refracted - refract(unit_direction, hit_record.normal, refraction_ratio) + refract(direction, hit_record.normal, refraction_ratio) } }; let specular_ray = Ray { diff --git a/clovers/src/materials/dispersive.rs b/clovers/src/materials/dispersive.rs index 8e9e5d30..8c7473a1 100644 --- a/clovers/src/materials/dispersive.rs +++ b/clovers/src/materials/dispersive.rs @@ -21,7 +21,7 @@ use crate::{ pdf::{ZeroPDF, PDF}, ray::Ray, wavelength::Wavelength, - Float, Vec3, + Direction, Float, }; use super::{reflect, refract, schlick, MaterialTrait, MaterialType, ScatterRecord}; @@ -87,18 +87,18 @@ impl MaterialTrait for Dispersive { }; // Copied from Dielectric, is this correct? - let unit_direction: Vec3 = ray.direction.normalize(); - let cos_theta: Float = (-unit_direction.dot(&hit_record.normal)).min(1.0); + let direction: Direction = ray.direction; + let cos_theta: Float = (-direction.dot(&hit_record.normal)).min(1.0); let sin_theta: Float = (1.0 - cos_theta * cos_theta).sqrt(); - let specular_direction: Vec3 = if refraction_ratio * sin_theta > 1.0 { - reflect(unit_direction, hit_record.normal) + let specular_direction: Direction = if refraction_ratio * sin_theta > 1.0 { + reflect(direction, hit_record.normal) } else { let reflect_probability: Float = schlick(cos_theta, refraction_ratio); if rng.gen::() < reflect_probability { - reflect(unit_direction, hit_record.normal) + reflect(direction, hit_record.normal) } else { // Refracted - refract(unit_direction, hit_record.normal, refraction_ratio) + refract(direction, hit_record.normal, refraction_ratio) } }; let specular_ray = Ray { diff --git a/clovers/src/materials/gltf.rs b/clovers/src/materials/gltf.rs index ac25e66c..455bf254 100644 --- a/clovers/src/materials/gltf.rs +++ b/clovers/src/materials/gltf.rs @@ -4,6 +4,7 @@ #[cfg(feature = "gl_tf")] use gltf::{image::Data, Material}; +use nalgebra::Unit; use palette::{ chromatic_adaptation::AdaptInto, convert::IntoColorUnclamped, white_point::E, LinSrgb, Srgb, Srgba, Xyz, @@ -15,7 +16,7 @@ use crate::{ pdf::{ZeroPDF, PDF}, random::random_unit_vector, ray::Ray, - Float, Vec2, Vec3, Vec4, PI, + Direction, Float, Vec2, Vec3, Vec4, PI, }; use super::{reflect, MaterialTrait, MaterialType, ScatterRecord}; @@ -90,7 +91,7 @@ impl<'scene> MaterialTrait for GLTFMaterial<'scene> { let base_color: LinSrgb = self.sample_base_color(hit_record); let emissive: LinSrgb = self.sample_emissive(hit_record).into_color_unclamped(); let (metalness, roughness) = self.sample_metalness_roughness(hit_record); - let normal: Vec3 = self.sample_normal(hit_record); + let normal: Direction = self.sample_normal(hit_record); let occlusion: Float = self.sample_occlusion(hit_record); // TODO: full color model @@ -100,8 +101,9 @@ impl<'scene> MaterialTrait for GLTFMaterial<'scene> { // TODO: better metalness model if metalness > 0.0 { // TODO: borrowed from metal, should this be different? - let reflected: Vec3 = reflect(ray.direction.normalize(), normal); - let direction = reflected + roughness * random_unit_vector(rng); + let reflected: Direction = reflect(ray.direction, normal); + let direction = *reflected + roughness * *random_unit_vector(rng); + let direction = Unit::new_normalize(direction); Some(ScatterRecord { specular_ray: Some(Ray { @@ -222,7 +224,7 @@ impl<'scene> GLTFMaterial<'scene> { } } - fn sample_normal(&self, hit_record: &HitRecord) -> Vec3 { + fn sample_normal(&self, hit_record: &HitRecord) -> Direction { let Some(normals) = self.normals else { // If we don't have normals, early return with the triangle normal return hit_record.normal; @@ -273,7 +275,7 @@ impl<'scene> GLTFMaterial<'scene> { nalgebra::Matrix3::from_columns(&[tangent, bitangent, normal]); // Transform the texture normal from tangent space to world space - (matrix * texture_normal).normalize() + Unit::new_normalize(matrix * texture_normal) } /// Find the correct texture coordinates in pixel space diff --git a/clovers/src/materials/metal.rs b/clovers/src/materials/metal.rs index 7bf2295d..71f1d2fd 100644 --- a/clovers/src/materials/metal.rs +++ b/clovers/src/materials/metal.rs @@ -7,8 +7,9 @@ use crate::{ random::random_unit_vector, ray::Ray, textures::{Texture, TextureTrait}, - Float, Vec3, + Direction, Float, }; +use nalgebra::Unit; use rand::prelude::SmallRng; #[derive(Debug, Clone)] @@ -30,11 +31,13 @@ impl MaterialTrait for Metal { hit_record: &HitRecord, rng: &mut SmallRng, ) -> Option { - let reflected: Vec3 = reflect(ray.direction.normalize(), hit_record.normal); + let reflected: Direction = reflect(ray.direction, hit_record.normal); + let direction = *reflected + self.fuzz * *random_unit_vector(rng); + let direction = Unit::new_normalize(direction); Some(ScatterRecord { specular_ray: Some(Ray { origin: hit_record.position, - direction: reflected + self.fuzz * random_unit_vector(rng), + direction, time: ray.time, wavelength: ray.wavelength, }), diff --git a/clovers/src/normals.rs b/clovers/src/normals.rs index e840cd8c..e5c52fa9 100644 --- a/clovers/src/normals.rs +++ b/clovers/src/normals.rs @@ -1,6 +1,8 @@ //! Alternative render method to [colorize](crate::colorize::colorize). -use crate::{hitable::HitableTrait, ray::Ray, scenes::Scene, Float, Vec3, EPSILON_SHADOW_ACNE}; +use crate::{ + hitable::HitableTrait, ray::Ray, scenes::Scene, Direction, Float, Vec3, EPSILON_SHADOW_ACNE, +}; use palette::LinSrgb; use rand::rngs::SmallRng; @@ -15,15 +17,13 @@ pub fn normal_map(ray: &Ray, scene: &Scene, rng: &mut SmallRng) -> LinSrgb { return LinSrgb::new(0.0, 0.0, 0.0); }; - let normal: Vec3 = hit_record.normal; + let normal: Direction = hit_record.normal; normal_to_color(normal) } /// Given a surface normal, return a color based on normal mapping colorization. #[must_use] -pub fn normal_to_color(normal: Vec3) -> LinSrgb { - // normalize just in case - let normal: Vec3 = normal.normalize(); +pub fn normal_to_color(normal: Direction) -> LinSrgb { // flip the Z and X axes because the wikipedia example uses left-handed coordinate system and my renderer uses a right-handed one for some reason. // TODO: figure out a good coordinate system to use... See also https://twitter.com/FreyaHolmer/status/1325556229410861056 let normal: Vec3 = Vec3::new(-normal.x, normal.y, -normal.z); diff --git a/clovers/src/objects/boxy.rs b/clovers/src/objects/boxy.rs index eedf2c32..9a30cb6c 100644 --- a/clovers/src/objects/boxy.rs +++ b/clovers/src/objects/boxy.rs @@ -7,7 +7,7 @@ use crate::{ materials::{Material, MaterialInit}, ray::Ray, wavelength::Wavelength, - Box, Float, Vec3, + Box, Direction, Float, Vec3, }; use rand::{rngs::SmallRng, Rng}; @@ -115,7 +115,7 @@ impl<'scene> HitableTrait for Boxy<'scene> { fn pdf_value( &self, origin: Vec3, - vector: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, @@ -123,7 +123,7 @@ impl<'scene> HitableTrait for Boxy<'scene> { let mut sum = 0.0; self.sides.iter().for_each(|object| { - sum += object.pdf_value(origin, vector, wavelength, time, rng) / 6.0; + sum += object.pdf_value(origin, direction, wavelength, time, rng) / 6.0; }); sum diff --git a/clovers/src/objects/constant_medium.rs b/clovers/src/objects/constant_medium.rs index 508df179..436cc219 100644 --- a/clovers/src/objects/constant_medium.rs +++ b/clovers/src/objects/constant_medium.rs @@ -8,7 +8,7 @@ use crate::{ ray::Ray, textures::Texture, wavelength::Wavelength, - Box, Float, Vec3, EPSILON_CONSTANT_MEDIUM, + Box, Direction, Float, Vec3, EPSILON_CONSTANT_MEDIUM, }; use rand::rngs::SmallRng; use rand::Rng; @@ -114,7 +114,7 @@ impl<'scene> HitableTrait for ConstantMedium<'scene> { let distance = rec1.distance + hit_distance / ray_length; let position = ray.evaluate(distance); - let normal: Vec3 = random_unit_vector(rng); // tutorial says: arbitrary + let normal: Direction = random_unit_vector(rng); // tutorial says: arbitrary let front_face: bool = true; // tutorial says: also arbitrary let u = rec1.u; @@ -142,13 +142,13 @@ impl<'scene> HitableTrait for ConstantMedium<'scene> { fn pdf_value( &self, origin: Vec3, - vector: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, ) -> Float { self.boundary - .pdf_value(origin, vector, wavelength, time, rng) + .pdf_value(origin, direction, wavelength, time, rng) } /// Returns a random point on the surface of the boundary of the fog diff --git a/clovers/src/objects/gltf.rs b/clovers/src/objects/gltf.rs index eed09906..0d1fcdbf 100644 --- a/clovers/src/objects/gltf.rs +++ b/clovers/src/objects/gltf.rs @@ -4,6 +4,7 @@ use alloc::string::String; use alloc::vec::Vec; #[cfg(feature = "gl_tf")] use gltf::{image::Data, Mesh, Node}; +use nalgebra::Unit; use rand::rngs::SmallRng; use rand::Rng; #[cfg(feature = "traces")] @@ -17,7 +18,7 @@ use crate::{ materials::gltf::GLTFMaterial, ray::Ray, wavelength::Wavelength, - Float, Vec3, EPSILON_RECT_THICKNESS, EPSILON_SHADOW_ACNE, + Direction, Float, Vec3, EPSILON_RECT_THICKNESS, EPSILON_SHADOW_ACNE, }; /// GLTF initialization structure @@ -101,13 +102,13 @@ impl<'scene> HitableTrait for GLTF<'scene> { fn pdf_value( &self, origin: Vec3, - vector: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, ) -> Float { self.bvhnode - .pdf_value(origin, vector, wavelength, time, rng) + .pdf_value(origin, direction, wavelength, time, rng) } /// Returns a random point on the ssurface of the object @@ -239,7 +240,7 @@ pub struct GLTFTriangle<'scene> { d: Float, w: Vec3, area: Float, - normal: Vec3, + normal: Direction, } impl<'scene> GLTFTriangle<'scene> { @@ -261,7 +262,7 @@ impl<'scene> GLTFTriangle<'scene> { let v = c - q; let n: Vec3 = u.cross(&v); - let normal: Vec3 = n.normalize(); + let normal = Unit::new_normalize(n); // TODO: what is this? let d = -(normal.dot(&q)); // TODO: what is this? @@ -339,14 +340,14 @@ impl<'scene> HitableTrait for GLTFTriangle<'scene> { fn pdf_value( &self, origin: Vec3, - vector: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, ) -> Float { let ray = Ray { origin, - direction: vector, + direction, time, wavelength, }; @@ -354,8 +355,8 @@ impl<'scene> HitableTrait for GLTFTriangle<'scene> { match self.hit(&ray, EPSILON_SHADOW_ACNE, Float::INFINITY, rng) { Some(hit_record) => { let distance_squared = - hit_record.distance * hit_record.distance * vector.norm_squared(); - let cosine = vector.dot(&hit_record.normal).abs() / vector.magnitude(); + hit_record.distance * hit_record.distance * direction.norm_squared(); + let cosine = direction.dot(&hit_record.normal).abs() / direction.magnitude(); distance_squared / (cosine * self.area) } diff --git a/clovers/src/objects/moving_sphere.rs b/clovers/src/objects/moving_sphere.rs index cc0519e7..bf6739b6 100644 --- a/clovers/src/objects/moving_sphere.rs +++ b/clovers/src/objects/moving_sphere.rs @@ -7,8 +7,9 @@ use crate::{ random::random_unit_vector, ray::Ray, wavelength::Wavelength, - Float, Vec3, PI, + Direction, Float, Vec3, PI, }; +use nalgebra::Unit; use rand::rngs::SmallRng; #[derive(Clone, Debug)] @@ -123,6 +124,7 @@ impl<'scene> HitableTrait for MovingSphere<'scene> { if distance < distance_max && distance > distance_min { let position: Vec3 = ray.evaluate(distance); let outward_normal = (position - self.center(ray.time)) / self.radius; + let outward_normal = Unit::new_normalize(outward_normal); let (u, v) = self.get_uv(position, ray.time); let mut record = HitRecord { distance, @@ -141,6 +143,7 @@ impl<'scene> HitableTrait for MovingSphere<'scene> { if distance < distance_max && distance > distance_min { let position: Vec3 = ray.evaluate(distance); let outward_normal = (position - self.center(ray.time)) / self.radius; + let outward_normal = Unit::new_normalize(outward_normal); let (u, v) = self.get_uv(position, ray.time); let mut record = HitRecord { distance, @@ -167,7 +170,7 @@ impl<'scene> HitableTrait for MovingSphere<'scene> { fn pdf_value( &self, _origin: Vec3, - _vector: Vec3, + _direction: Direction, _wavelength: Wavelength, _time: Float, _rng: &mut SmallRng, @@ -177,6 +180,7 @@ impl<'scene> HitableTrait for MovingSphere<'scene> { } fn random(&self, _origin: Vec3, rng: &mut SmallRng) -> Vec3 { - random_unit_vector(rng) + // FIXME: this is incorrect! does not take into account sphere size, moving sphere position + *random_unit_vector(rng) } } diff --git a/clovers/src/objects/quad.rs b/clovers/src/objects/quad.rs index 80dfab5a..f4841dd5 100644 --- a/clovers/src/objects/quad.rs +++ b/clovers/src/objects/quad.rs @@ -4,11 +4,12 @@ use crate::hitable::HitableTrait; use crate::materials::MaterialInit; use crate::wavelength::Wavelength; -use crate::EPSILON_SHADOW_ACNE; use crate::{ aabb::AABB, hitable::get_orientation, hitable::HitRecord, materials::Material, ray::Ray, Float, Vec3, EPSILON_RECT_THICKNESS, }; +use crate::{Direction, EPSILON_SHADOW_ACNE}; +use nalgebra::Unit; use rand::rngs::SmallRng; use rand::Rng; @@ -44,7 +45,7 @@ pub struct Quad<'scene> { /// Area of the surface pub area: Float, /// Normal vector of the surface - pub normal: Vec3, + pub normal: Direction, /// What is this? // TODO: understand, explain pub d: Float, /// What is this? // TODO: understand, explain @@ -58,7 +59,7 @@ impl<'scene> Quad<'scene> { #[must_use] pub fn new(q: Vec3, u: Vec3, v: Vec3, material: &'scene Material) -> Quad<'scene> { let n: Vec3 = u.cross(&v); - let normal: Vec3 = n.normalize(); + let normal = Unit::new_normalize(n); // TODO: what is this? let d = -(normal.dot(&q)); // TODO: what is this? @@ -141,22 +142,22 @@ impl<'scene> HitableTrait for Quad<'scene> { fn pdf_value( &self, origin: Vec3, - vector: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, ) -> Float { let ray = Ray { origin, - direction: vector, + direction, time, wavelength, }; match self.hit(&ray, EPSILON_SHADOW_ACNE, Float::INFINITY, rng) { Some(hit_record) => { let distance_squared = - hit_record.distance * hit_record.distance * vector.norm_squared(); - let cosine = vector.dot(&hit_record.normal).abs() / vector.magnitude(); + hit_record.distance * hit_record.distance * direction.norm_squared(); + let cosine = direction.dot(&hit_record.normal).abs() / direction.magnitude(); distance_squared / (cosine * self.area) } diff --git a/clovers/src/objects/rotate.rs b/clovers/src/objects/rotate.rs index ebb5e4fa..0baae362 100644 --- a/clovers/src/objects/rotate.rs +++ b/clovers/src/objects/rotate.rs @@ -5,8 +5,9 @@ use crate::{ hitable::{HitRecord, Hitable, HitableTrait}, ray::Ray, wavelength::Wavelength, - Box, Float, Vec3, + Box, Direction, Float, Vec3, }; +use nalgebra::Unit; use rand::rngs::SmallRng; use super::Object; @@ -109,7 +110,7 @@ impl<'scene> HitableTrait for RotateY<'scene> { rng: &mut SmallRng, ) -> Option { let mut origin: Vec3 = ray.origin; - let mut direction: Vec3 = ray.direction; + let mut direction: Vec3 = *ray.direction; origin[0] = self.cos_theta * ray.origin[0] - self.sin_theta * ray.origin[2]; origin[2] = self.sin_theta * ray.origin[0] + self.cos_theta * ray.origin[2]; @@ -117,6 +118,8 @@ impl<'scene> HitableTrait for RotateY<'scene> { direction[0] = self.cos_theta * ray.direction[0] - self.sin_theta * ray.direction[2]; direction[2] = self.sin_theta * ray.direction[0] + self.cos_theta * ray.direction[2]; + let direction = Unit::new_normalize(direction); + let rotated_r: Ray = Ray { origin, direction, @@ -132,7 +135,7 @@ impl<'scene> HitableTrait for RotateY<'scene> { // Determine where the intersection is // TODO: understand and explain let mut position: Vec3 = hit_record.position; - let mut normal: Vec3 = hit_record.normal; + let mut normal: Vec3 = *hit_record.normal; let distance: Float = hit_record.distance; position[0] = @@ -143,6 +146,8 @@ impl<'scene> HitableTrait for RotateY<'scene> { normal[0] = self.cos_theta * hit_record.normal[0] + self.sin_theta * hit_record.normal[2]; normal[2] = -self.sin_theta * hit_record.normal[0] + self.cos_theta * hit_record.normal[2]; + let normal = Unit::new_normalize(normal); + let mut record = HitRecord { distance, position, @@ -165,7 +170,7 @@ impl<'scene> HitableTrait for RotateY<'scene> { fn pdf_value( &self, _origin: Vec3, - _vector: Vec3, + _direction: Direction, _wavelength: Wavelength, _time: Float, _rng: &mut SmallRng, diff --git a/clovers/src/objects/sphere.rs b/clovers/src/objects/sphere.rs index 8dacfde4..ab9913c6 100644 --- a/clovers/src/objects/sphere.rs +++ b/clovers/src/objects/sphere.rs @@ -7,8 +7,9 @@ use crate::{ onb::ONB, ray::Ray, wavelength::Wavelength, - Float, Vec3, EPSILON_SHADOW_ACNE, PI, + Direction, Float, Vec3, EPSILON_SHADOW_ACNE, PI, }; +use nalgebra::Unit; use rand::{rngs::SmallRng, Rng}; #[derive(Clone, Debug)] @@ -87,6 +88,7 @@ impl<'scene> HitableTrait for Sphere<'scene> { if distance < distance_max && distance > distance_min { let position: Vec3 = ray.evaluate(distance); let outward_normal = (position - self.center) / self.radius; + let outward_normal = Unit::new_normalize(outward_normal); let (u, v) = self.get_uv(position, ray.time); let mut record = HitRecord { distance, @@ -105,6 +107,7 @@ impl<'scene> HitableTrait for Sphere<'scene> { if distance < distance_max && distance > distance_min { let position: Vec3 = ray.evaluate(distance); let outward_normal = (position - self.center) / self.radius; + let outward_normal = Unit::new_normalize(outward_normal); let (u, v) = self.get_uv(position, ray.time); let mut record = HitRecord { distance, @@ -133,14 +136,14 @@ impl<'scene> HitableTrait for Sphere<'scene> { fn pdf_value( &self, origin: Vec3, - vector: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, ) -> Float { let ray = Ray { origin, - direction: vector, + direction, time, wavelength, }; @@ -161,10 +164,12 @@ impl<'scene> HitableTrait for Sphere<'scene> { /// Utility function from Ray Tracing: The Rest of Your Life. TODO: understand, document #[must_use] fn random(&self, origin: Vec3, rng: &mut SmallRng) -> Vec3 { - let direction: Vec3 = self.center - origin; - let distance_squared: Float = direction.norm_squared(); - let uvw = ONB::build_from_w(direction); - uvw.local(random_to_sphere(self.radius, distance_squared, rng)) + let offset: Vec3 = self.center - origin; + let distance_squared: Float = offset.norm_squared(); + let uvw = ONB::build_from_w(Unit::new_normalize(offset)); + let vec = random_to_sphere(self.radius, distance_squared, rng); + let vec = Unit::new_normalize(vec); + *uvw.local(vec) } } diff --git a/clovers/src/objects/stl.rs b/clovers/src/objects/stl.rs index 62fe4cab..e195d6f3 100644 --- a/clovers/src/objects/stl.rs +++ b/clovers/src/objects/stl.rs @@ -13,7 +13,7 @@ use crate::{ objects::Triangle, ray::Ray, wavelength::Wavelength, - Float, Vec3, + Direction, Float, Vec3, }; /// Internal STL object representation after initialization. Contains the material for all triangles in it to avoid having n copies. @@ -51,13 +51,13 @@ impl<'scene> HitableTrait for STL<'scene> { fn pdf_value( &self, origin: Vec3, - vector: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, ) -> Float { self.bvhnode - .pdf_value(origin, vector, wavelength, time, rng) + .pdf_value(origin, direction, wavelength, time, rng) } /// Returns a random point on the ssurface of the object diff --git a/clovers/src/objects/translate.rs b/clovers/src/objects/translate.rs index 672f1bf4..7348eab5 100644 --- a/clovers/src/objects/translate.rs +++ b/clovers/src/objects/translate.rs @@ -5,7 +5,7 @@ use crate::{ hitable::{HitRecord, Hitable, HitableTrait}, ray::Ray, wavelength::Wavelength, - Box, Float, Vec3, + Box, Direction, Float, Vec3, }; use rand::rngs::SmallRng; @@ -86,14 +86,14 @@ impl<'scene> HitableTrait for Translate<'scene> { fn pdf_value( &self, origin: Vec3, - vector: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, ) -> Float { // TODO: is this correct? self.object - .pdf_value(origin + self.offset, vector, wavelength, time, rng) + .pdf_value(origin + self.offset, direction, wavelength, time, rng) } /// Returns a random point on the surface of the moved object diff --git a/clovers/src/objects/triangle.rs b/clovers/src/objects/triangle.rs index 0653cf10..56895b76 100644 --- a/clovers/src/objects/triangle.rs +++ b/clovers/src/objects/triangle.rs @@ -5,11 +5,12 @@ use crate::hitable::HitableTrait; use crate::interval::Interval; use crate::materials::MaterialInit; use crate::wavelength::Wavelength; -use crate::EPSILON_SHADOW_ACNE; use crate::{ aabb::AABB, hitable::HitRecord, materials::Material, ray::Ray, Float, Vec3, EPSILON_RECT_THICKNESS, }; +use crate::{Direction, EPSILON_SHADOW_ACNE}; +use nalgebra::Unit; use rand::rngs::SmallRng; use rand::Rng; @@ -45,7 +46,7 @@ pub struct Triangle<'scene> { /// Area of the surface pub area: Float, /// Normal vector of the surface - pub normal: Vec3, + pub normal: Direction, /// What is this? // TODO: understand, explain pub d: Float, /// What is this? // TODO: understand, explain @@ -59,7 +60,7 @@ impl<'scene> Triangle<'scene> { #[must_use] pub fn new(q: Vec3, u: Vec3, v: Vec3, material: &'scene Material) -> Triangle<'scene> { let n: Vec3 = u.cross(&v); - let normal: Vec3 = n.normalize(); + let normal = Unit::new_normalize(n); // TODO: what is this? let d = -(normal.dot(&q)); // TODO: what is this? @@ -177,14 +178,14 @@ impl<'scene> HitableTrait for Triangle<'scene> { fn pdf_value( &self, origin: Vec3, - vector: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, ) -> Float { let ray = Ray { origin, - direction: vector, + direction, time, wavelength, }; @@ -192,8 +193,8 @@ impl<'scene> HitableTrait for Triangle<'scene> { match self.hit(&ray, EPSILON_SHADOW_ACNE, Float::INFINITY, rng) { Some(hit_record) => { let distance_squared = - hit_record.distance * hit_record.distance * vector.norm_squared(); - let cosine = vector.dot(&hit_record.normal).abs() / vector.magnitude(); + hit_record.distance * hit_record.distance * direction.norm_squared(); + let cosine = direction.dot(&hit_record.normal).abs() / direction.magnitude(); distance_squared / (cosine * self.area) } @@ -238,7 +239,7 @@ mod tests { const TIME_1: Float = 1.0; const RAY: Ray = Ray { origin: Vec3::new(0.0, 0.0, -1.0), - direction: Vec3::new(0.0, 0.0, 1.0), + direction: Unit::new_unchecked(Vec3::new(0.0, 0.0, 1.0)), time: TIME_0, wavelength: 600, }; @@ -277,7 +278,10 @@ mod tests { assert!(hit_record.distance - 1.0 <= Float::EPSILON); assert_eq!(hit_record.position, Vec3::new(0.0, 0.0, 0.0)); - assert_eq!(hit_record.normal, Vec3::new(0.0, 0.0, -1.0)); + assert_eq!( + hit_record.normal, + Unit::new_normalize(Vec3::new(0.0, 0.0, -1.0)) + ); assert!(!hit_record.front_face); } @@ -315,7 +319,10 @@ mod tests { assert!(hit_record.distance - 1.0 <= Float::EPSILON); assert_eq!(hit_record.position, Vec3::new(0.0, 0.0, 0.0)); - assert_eq!(hit_record.normal, Vec3::new(0.0, 0.0, -1.0)); + assert_eq!( + hit_record.normal, + Unit::new_normalize(Vec3::new(0.0, 0.0, -1.0)) + ); assert!(hit_record.front_face); } @@ -353,7 +360,10 @@ mod tests { assert!(hit_record.distance - 1.0 <= Float::EPSILON); assert_eq!(hit_record.position, Vec3::new(0.0, 0.0, 0.0)); - assert_eq!(hit_record.normal, Vec3::new(0.0, 0.0, -1.0)); + assert_eq!( + hit_record.normal, + Unit::new_normalize(Vec3::new(0.0, 0.0, -1.0)) + ); assert!(!hit_record.front_face); } @@ -391,7 +401,10 @@ mod tests { assert!(hit_record.distance - 1.0 <= Float::EPSILON); assert_eq!(hit_record.position, Vec3::new(0.0, 0.0, 0.0)); - assert_eq!(hit_record.normal, Vec3::new(0.0, 0.0, -1.0)); + assert_eq!( + hit_record.normal, + Unit::new_normalize(Vec3::new(0.0, 0.0, -1.0)) + ); assert!(hit_record.front_face); } } diff --git a/clovers/src/onb.rs b/clovers/src/onb.rs index 98266e11..55ce5dd1 100644 --- a/clovers/src/onb.rs +++ b/clovers/src/onb.rs @@ -1,16 +1,18 @@ //! Orthonormal bases -use crate::Vec3; +use nalgebra::Unit; + +use crate::{Direction, Vec3}; #[derive(Debug, Clone)] /// An orthonormal basis structure. pub struct ONB { /// U - pub u: Vec3, + pub u: Direction, /// V - pub v: Vec3, + pub v: Direction, /// W - pub w: Vec3, + pub w: Direction, } // TODO: understand, explain @@ -18,22 +20,22 @@ pub struct ONB { impl ONB { /// Builds a new [ONB] structure given a normal vector. #[must_use] - pub fn build_from_w(normal: Vec3) -> ONB { - let w = (normal).normalize(); - let a: Vec3 = if (w.x).abs() > 0.9 { - Vec3::new(0.0, 1.0, 0.0) + pub fn build_from_w(w: Direction) -> ONB { + let a: Direction = if (w.x).abs() > 0.9 { + Unit::new_normalize(Vec3::new(0.0, 1.0, 0.0)) } else { - Vec3::new(1.0, 0.0, 0.0) + Unit::new_normalize(Vec3::new(1.0, 0.0, 0.0)) }; - let v = (w.cross(&a)).normalize(); - let u = w.cross(&v); + let v = Unit::new_normalize(w.cross(&a)); + let u = Unit::new_normalize(w.cross(&v)); ONB { u, v, w } } /// Returns the ONB-projected version of the provided vector? #[must_use] - pub fn local(&self, vec: Vec3) -> Vec3 { - vec.x * self.u + vec.y * self.v + vec.z * self.w + pub fn local(&self, vec: Direction) -> Direction { + let d = vec.x * *self.u + vec.y * *self.v + vec.z * *self.w; + Unit::new_normalize(d) } } diff --git a/clovers/src/pdf.rs b/clovers/src/pdf.rs index 0ed7910e..c3abe22d 100644 --- a/clovers/src/pdf.rs +++ b/clovers/src/pdf.rs @@ -7,7 +7,7 @@ use crate::{ onb::ONB, random::{random_cosine_direction, random_unit_vector}, wavelength::Wavelength, - Box, Float, Vec3, PI, + Box, Direction, Float, Vec3, PI, }; use enum_dispatch::enum_dispatch; use rand::rngs::SmallRng; @@ -28,7 +28,7 @@ pub(crate) trait PDFTrait { #[must_use] fn value( &self, - direction: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, @@ -45,7 +45,7 @@ pub struct CosinePDF { impl CosinePDF { #[must_use] - pub fn new(w: Vec3) -> Self { + pub fn new(w: Direction) -> Self { CosinePDF { uvw: ONB::build_from_w(w), } @@ -56,7 +56,7 @@ impl PDFTrait for CosinePDF { #[must_use] fn value( &self, - direction: Vec3, + direction: Direction, _wavelength: Wavelength, _time: Float, _rng: &mut SmallRng, @@ -71,7 +71,7 @@ impl PDFTrait for CosinePDF { #[must_use] fn generate(&self, rng: &mut SmallRng) -> Vec3 { - self.uvw.local(random_cosine_direction(rng)) + *self.uvw.local(random_cosine_direction(rng)) } } @@ -92,7 +92,7 @@ impl<'scene> PDFTrait for HitablePDF<'scene> { #[must_use] fn value( &self, - direction: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, @@ -128,7 +128,7 @@ impl<'scene> PDFTrait for MixturePDF<'scene> { #[must_use] fn value( &self, - direction: Vec3, + direction: Direction, wavelength: Wavelength, time: Float, rng: &mut SmallRng, @@ -161,7 +161,7 @@ impl PDFTrait for SpherePDF { #[must_use] fn value( &self, - _direction: Vec3, + _direction: Direction, _wavelength: Wavelength, _time: Float, _rng: &mut SmallRng, @@ -171,7 +171,7 @@ impl PDFTrait for SpherePDF { #[must_use] fn generate(&self, rng: &mut SmallRng) -> Vec3 { - random_unit_vector(rng) + *random_unit_vector(rng) } } @@ -190,7 +190,7 @@ impl PDFTrait for ZeroPDF { #[must_use] fn value( &self, - _direction: Vec3, + _direction: Direction, _wavelength: Wavelength, _time: Float, _rng: &mut SmallRng, @@ -200,7 +200,7 @@ impl PDFTrait for ZeroPDF { #[must_use] fn generate(&self, rng: &mut SmallRng) -> Vec3 { - random_unit_vector(rng) + *random_unit_vector(rng) } } diff --git a/clovers/src/random.rs b/clovers/src/random.rs index 22a09818..f7a70cfa 100644 --- a/clovers/src/random.rs +++ b/clovers/src/random.rs @@ -1,14 +1,16 @@ //! Various internal helper functions for getting specific kinds of random values. -use crate::{Float, Vec3, PI}; +use crate::{Direction, Float, Vec3, PI}; +use nalgebra::Unit; use rand::rngs::SmallRng; use rand::Rng; use rand_distr::{Distribution, UnitDisc, UnitSphere}; /// Internal helper. #[must_use] -pub fn random_unit_vector(rng: &mut SmallRng) -> Vec3 { - UnitSphere.sample(rng).into() +pub fn random_unit_vector(rng: &mut SmallRng) -> Direction { + let v = UnitSphere.sample(rng).into(); + Unit::new_normalize(v) } /// Internal helper. @@ -20,7 +22,7 @@ pub fn random_in_unit_disk(rng: &mut SmallRng) -> Vec3 { /// Internal helper. #[must_use] -pub fn random_cosine_direction(rng: &mut SmallRng) -> Vec3 { +pub fn random_cosine_direction(rng: &mut SmallRng) -> Direction { let r1: Float = rng.gen(); let r2: Float = rng.gen(); let z = (1.0 - r2).sqrt(); @@ -29,14 +31,14 @@ pub fn random_cosine_direction(rng: &mut SmallRng) -> Vec3 { let x = phi.cos() * r2.sqrt(); let y = phi.sin() * r2.sqrt(); - // TODO: should this be normalized? - Vec3::new(x, y, z) + let v: Vec3 = Vec3::new(x, y, z); + Unit::new_normalize(v) } /// Internal helper. #[must_use] -pub fn random_on_hemisphere(normal: Vec3, rng: &mut SmallRng) -> Vec3 { - let in_unit_sphere: Vec3 = random_unit_vector(rng); +pub fn random_on_hemisphere(normal: Vec3, rng: &mut SmallRng) -> Direction { + let in_unit_sphere: Direction = random_unit_vector(rng); if in_unit_sphere.dot(&normal) > 0.0 { // In the same hemisphere as the normal in_unit_sphere diff --git a/clovers/src/ray.rs b/clovers/src/ray.rs index 41c4a203..6fa9689f 100644 --- a/clovers/src/ray.rs +++ b/clovers/src/ray.rs @@ -1,6 +1,6 @@ //! The very core of the ray tracing rendering itself: the [Ray] -use crate::{wavelength::Wavelength, Float, Vec3}; +use crate::{wavelength::Wavelength, Direction, Float, Vec3}; /// A Ray has an origin and a direction, as well as an instant in time it exists in. Motion blur is achieved by creating multiple rays with slightly different times. #[derive(Clone, Debug, PartialEq)] @@ -8,7 +8,7 @@ pub struct Ray { /// The origin of the ray. pub origin: Vec3, /// The direction of the ray. - pub direction: Vec3, + pub direction: Direction, /// The time instant at which the ray exists. pub time: Float, /// Wavelength of the ray @@ -19,6 +19,6 @@ impl Ray { /// Evaluates the position (coordinate) at which the ray is at the given parameter, considering the origin and direction. Considering a default unit speed of 1 per unit time, this function can be given either a time or a distance. #[must_use] pub fn evaluate(&self, parameter: Float) -> Vec3 { - self.origin + parameter * self.direction + self.origin + parameter * *self.direction } } From 765b24da5c2ad7a7e80c4f4075e855e7c923a3ad Mon Sep 17 00:00:00 2001 From: Walther Date: Sat, 2 Mar 2024 16:53:36 +0200 Subject: [PATCH 2/2] improvement: introduce Position, type alias for Vec3 --- clovers/src/aabb.rs | 4 +-- clovers/src/bvhnode.rs | 6 ++-- clovers/src/camera.rs | 39 +++++++++++++------------ clovers/src/hitable.rs | 12 ++++---- clovers/src/lib.rs | 2 ++ clovers/src/materials.rs | 4 +-- clovers/src/materials/cone_light.rs | 4 +-- clovers/src/materials/diffuse_light.rs | 4 +-- clovers/src/objects/boxy.rs | 16 +++++----- clovers/src/objects/constant_medium.rs | 6 ++-- clovers/src/objects/gltf.rs | 12 ++++---- clovers/src/objects/moving_sphere.rs | 36 +++++++++++------------ clovers/src/objects/quad.rs | 20 +++++++------ clovers/src/objects/rotate.rs | 10 +++---- clovers/src/objects/sphere.rs | 28 +++++++++--------- clovers/src/objects/stl.rs | 8 ++--- clovers/src/objects/translate.rs | 6 ++-- clovers/src/objects/triangle.rs | 30 +++++++++---------- clovers/src/pdf.rs | 19 ++++++------ clovers/src/ray.rs | 6 ++-- clovers/src/textures.rs | 4 +-- clovers/src/textures/solid_color.rs | 4 +-- clovers/src/textures/spatial_checker.rs | 4 +-- clovers/src/textures/surface_checker.rs | 4 +-- 24 files changed, 147 insertions(+), 141 deletions(-) diff --git a/clovers/src/aabb.rs b/clovers/src/aabb.rs index 33fc6955..2378039a 100644 --- a/clovers/src/aabb.rs +++ b/clovers/src/aabb.rs @@ -2,7 +2,7 @@ use core::ops::Add; -use crate::{interval::Interval, ray::Ray, Float, Vec3, EPSILON_RECT_THICKNESS}; +use crate::{interval::Interval, ray::Ray, Float, Position, Vec3, EPSILON_RECT_THICKNESS}; /// Axis-aligned bounding box Defined by two opposing corners, each of which are a [Vec3]. /// @@ -31,7 +31,7 @@ impl AABB { /// Creates a new axis-aligned bounding box from two coordinates. Treats the two points a and b as extrema for the bounding box, so we don't require a particular minimum/maximum coordinate order. #[must_use] - pub fn new_from_coords(a: Vec3, b: Vec3) -> AABB { + pub fn new_from_coords(a: Position, b: Position) -> AABB { AABB { x: Interval::new(a[0].min(b[0]), a[0].max(b[0])), y: Interval::new(a[1].min(b[1]), a[1].max(b[1])), diff --git a/clovers/src/bvhnode.rs b/clovers/src/bvhnode.rs index 393a3e49..a701b202 100644 --- a/clovers/src/bvhnode.rs +++ b/clovers/src/bvhnode.rs @@ -9,7 +9,7 @@ use crate::{ hitable::{Empty, HitRecord, Hitable, HitableTrait}, ray::Ray, wavelength::Wavelength, - Box, Direction, Float, Vec, Vec3, + Box, Direction, Float, Position, Vec, }; /// Bounding Volume Hierarchy Node. @@ -187,7 +187,7 @@ impl<'scene> HitableTrait for BVHNode<'scene> { #[must_use] fn pdf_value( &self, - origin: Vec3, + origin: Position, direction: Direction, wavelength: Wavelength, time: Float, @@ -214,7 +214,7 @@ impl<'scene> HitableTrait for BVHNode<'scene> { /// Returns a random point on the surface of one of the children #[must_use] - fn random(&self, origin: Vec3, rng: &mut SmallRng) -> Vec3 { + fn random(&self, origin: Position, rng: &mut SmallRng) -> Position { match (&*self.left, &*self.right) { (_, Hitable::Empty(_)) => self.left.random(origin, rng), (Hitable::Empty(_), _) => self.right.random(origin, rng), diff --git a/clovers/src/camera.rs b/clovers/src/camera.rs index 631d0517..5ba6befe 100644 --- a/clovers/src/camera.rs +++ b/clovers/src/camera.rs @@ -4,6 +4,7 @@ use crate::wavelength::random_wavelength; use crate::{random::random_in_unit_disk, ray::Ray, Float, Vec3, PI}; +use crate::{Direction, Position}; use nalgebra::Unit; use rand::rngs::SmallRng; use rand::Rng; @@ -13,13 +14,13 @@ use rand::Rng; /// The main [Camera] object used in the ray tracing. pub struct Camera { /// Coordinate of the lower left corner of the camera. - pub lower_left_corner: Vec3, + pub lower_left_corner: Position, /// Defines the horizontal axis for the camera. pub horizontal: Vec3, /// Defines the vertical axis for the camera. pub vertical: Vec3, /// Defines the origin of the camera. - pub origin: Vec3, + pub origin: Position, /// Defines the lens radius for the camera. TODO: understand and explain better pub lens_radius: Float, /// Defines the earliest starting time for the camera, used when generating [Rays](Ray). @@ -28,11 +29,11 @@ pub struct Camera { pub time_1: Float, // TODO: clarify these odd one-letter variables /// U - pub u: Vec3, + pub u: Direction, /// V - pub v: Vec3, + pub v: Direction, /// W - pub w: Vec3, + pub w: Direction, } #[derive(Clone, Debug)] @@ -40,9 +41,9 @@ pub struct Camera { /// Represents the fields that can be described in a Scene file. Some other fields the main Camera struct requires (such as `aspect_ratio`) are derived from other info (such as width, height) pub struct CameraInit { /// Describes where the camera is - pub look_from: Vec3, + pub look_from: Position, /// Describes where the camera is looking at - pub look_at: Vec3, + pub look_at: Position, /// Describes the subjective "up" direction for the camera to define the orientation pub up: Vec3, /// Describes the vertical field of view for the camera @@ -58,8 +59,8 @@ impl Camera { /// Creates a new [Camera] with the given parameters. #[must_use] pub fn new( - look_from: Vec3, - look_at: Vec3, + look_from: Position, + look_at: Position, up: Vec3, vertical_fov: Float, aspect_ratio: Float, @@ -72,18 +73,18 @@ impl Camera { let theta: Float = vertical_fov * PI / 180.0; let half_height: Float = (theta / 2.0).tan(); let half_width: Float = aspect_ratio * half_height; - let origin: Vec3 = look_from; - let w: Vec3 = (look_from - look_at).normalize(); - let u: Vec3 = (up.cross(&w)).normalize(); - let v: Vec3 = w.cross(&u); + let origin: Position = look_from; + let w: Direction = Unit::new_normalize(look_from - look_at); + let u: Direction = Unit::new_normalize(up.cross(&w)); + let v: Direction = Unit::new_normalize(w.cross(&u)); // TODO: understand this defocus let lower_left_corner: Vec3 = origin - - half_width * focus_distance * u - - half_height * focus_distance * v - - focus_distance * w; - let horizontal: Vec3 = 2.0 * half_width * focus_distance * u; - let vertical: Vec3 = 2.0 * half_height * focus_distance * v; + - half_width * focus_distance * *u + - half_height * focus_distance * *v + - focus_distance * *w; + let horizontal: Vec3 = 2.0 * half_width * focus_distance * *u; + let vertical: Vec3 = 2.0 * half_height * focus_distance * *v; Camera { lower_left_corner, @@ -105,7 +106,7 @@ impl Camera { pub fn get_ray(self, s: Float, t: Float, rng: &mut SmallRng) -> Ray { // TODO: add a better defocus blur / depth of field implementation let rd: Vec3 = self.lens_radius * random_in_unit_disk(rng); - let offset: Vec3 = self.u * rd.x + self.v * rd.y; + let offset: Vec3 = *self.u * rd.x + *self.v * rd.y; // Randomized time used for motion blur let time: Float = rng.gen_range(self.time_0..self.time_1); // Random wavelength for spectral rendering diff --git a/clovers/src/hitable.rs b/clovers/src/hitable.rs index 861e21cf..ba237f1b 100644 --- a/clovers/src/hitable.rs +++ b/clovers/src/hitable.rs @@ -14,7 +14,7 @@ use crate::{ objects::{Boxy, ConstantMedium, MovingSphere, Quad, RotateY, Sphere, Translate, Triangle}, ray::Ray, wavelength::Wavelength, - Direction, Float, Vec3, + Direction, Float, Position, }; use enum_dispatch::enum_dispatch; @@ -26,7 +26,7 @@ pub struct HitRecord<'a> { /// Distance from the ray origin to the hitpoint pub distance: Float, /// 3D coordinate of the hitpoint - pub position: Vec3, + pub position: Position, /// Surface normal from the hitpoint pub normal: Direction, /// U surface coordinate of the hitpoint @@ -94,7 +94,7 @@ impl HitableTrait for Empty { fn pdf_value( &self, - _origin: Vec3, + _origin: Position, _direction: Direction, _wavelength: Wavelength, _time: Float, @@ -103,7 +103,7 @@ impl HitableTrait for Empty { 0.0 } - fn random(&self, _origin: Vec3, _rng: &mut SmallRng) -> Vec3 { + fn random(&self, _origin: Position, _rng: &mut SmallRng) -> Position { panic!("Hitable::Empty::random called!") } } @@ -125,7 +125,7 @@ pub trait HitableTrait { #[must_use] fn pdf_value( &self, - origin: Vec3, + origin: Position, direction: Direction, wavelength: Wavelength, time: Float, @@ -133,7 +133,7 @@ pub trait HitableTrait { ) -> Float; #[must_use] - fn random(&self, origin: Vec3, rng: &mut SmallRng) -> Vec3; + fn random(&self, origin: Position, rng: &mut SmallRng) -> Position; } /// Returns a tuple of `(front_face, normal)`. Used in lieu of `set_face_normal` in the Ray Tracing for the Rest Of Your Life book. diff --git a/clovers/src/lib.rs b/clovers/src/lib.rs index 5fdfc1f7..9e81f4b8 100644 --- a/clovers/src/lib.rs +++ b/clovers/src/lib.rs @@ -128,6 +128,8 @@ pub type Vec3 = Vector3; pub type Vec4 = Vector4; /// Internal type alias: a nalgebra [Unit] of a [Vector3] pub type Direction = Unit; +/// Internal type alias: a nalgebra [Vector3] +pub type Position = Vec3; /// Internal const: epsilon used for avoiding "shadow acne". This is mostly used for the initial minimum distance for ray hits after reflecting or scattering from a surface. pub const EPSILON_SHADOW_ACNE: Float = 0.001; /// Internal const: epsilon used for having a finitely-sized thickness for the bounding box of an infinitely-thin rectangle. Shouldn't be too small. diff --git a/clovers/src/materials.rs b/clovers/src/materials.rs index 9eecb203..a7ddccdd 100644 --- a/clovers/src/materials.rs +++ b/clovers/src/materials.rs @@ -4,7 +4,7 @@ use alloc::string::String; use core::fmt::Debug; use nalgebra::Unit; -use crate::{hitable::HitRecord, pdf::PDF, ray::Ray, Direction, Float, Vec3}; +use crate::{hitable::HitRecord, pdf::PDF, ray::Ray, Direction, Float, Position, Vec3}; pub mod cone_light; pub mod dielectric; pub mod diffuse_light; @@ -80,7 +80,7 @@ pub trait MaterialTrait: Debug { _hit_record: &HitRecord, _u: Float, _v: Float, - _position: Vec3, + _position: Position, ) -> Xyz { Xyz::new(0.0, 0.0, 0.0) } diff --git a/clovers/src/materials/cone_light.rs b/clovers/src/materials/cone_light.rs index ef6511bb..65943e13 100644 --- a/clovers/src/materials/cone_light.rs +++ b/clovers/src/materials/cone_light.rs @@ -5,7 +5,7 @@ use crate::{ hitable::HitRecord, ray::Ray, textures::{SolidColor, Texture, TextureTrait}, - Float, Vec3, + Float, Position, }; use palette::{white_point::E, Xyz}; use rand::prelude::SmallRng; @@ -59,7 +59,7 @@ impl MaterialTrait for ConeLight { hit_record: &HitRecord, u: Float, v: Float, - position: Vec3, + position: Position, ) -> Xyz { // If we don't hit the front face, return black if !hit_record.front_face { diff --git a/clovers/src/materials/diffuse_light.rs b/clovers/src/materials/diffuse_light.rs index 7cb2c7d7..242d2273 100644 --- a/clovers/src/materials/diffuse_light.rs +++ b/clovers/src/materials/diffuse_light.rs @@ -5,7 +5,7 @@ use crate::{ hitable::HitRecord, ray::Ray, textures::{SolidColor, Texture, TextureTrait}, - Float, Vec3, + Float, Position, }; use palette::{white_point::E, Xyz}; use rand::prelude::SmallRng; @@ -57,7 +57,7 @@ impl MaterialTrait for DiffuseLight { hit_record: &HitRecord, u: Float, v: Float, - position: Vec3, + position: Position, ) -> Xyz { if hit_record.front_face { self.emit.color(u, v, position) diff --git a/clovers/src/objects/boxy.rs b/clovers/src/objects/boxy.rs index 9a30cb6c..f48a2295 100644 --- a/clovers/src/objects/boxy.rs +++ b/clovers/src/objects/boxy.rs @@ -7,7 +7,7 @@ use crate::{ materials::{Material, MaterialInit}, ray::Ray, wavelength::Wavelength, - Box, Direction, Float, Vec3, + Box, Direction, Float, Position, Vec3, }; use rand::{rngs::SmallRng, Rng}; @@ -19,9 +19,9 @@ pub struct BoxyInit { #[cfg_attr(feature = "serde-derive", serde(default))] pub priority: bool, /// First corner for the box - pub corner_0: Vec3, + pub corner_0: Position, /// Second, opposing corner for the box - pub corner_1: Vec3, + pub corner_1: Position, #[cfg_attr(feature = "serde-derive", serde(default))] /// Material used for the box pub material: MaterialInit, @@ -40,14 +40,14 @@ pub struct Boxy<'scene> { impl<'scene> Boxy<'scene> { /// Initializes a new instance of a box, given two opposing [Vec3] corners `corner_0` and `corner_1`, and a [Material] `material`. #[must_use] - pub fn new(corner_0: Vec3, corner_1: Vec3, material: &'scene Material) -> Self { + pub fn new(corner_0: Position, corner_1: Position, material: &'scene Material) -> Self { // Construct the two opposite vertices with the minimum and maximum coordinates. - let min: Vec3 = Vec3::new( + let min: Position = Position::new( corner_0.x.min(corner_1.x), corner_0.y.min(corner_1.y), corner_0.z.min(corner_1.z), ); - let max: Vec3 = Vec3::new( + let max: Position = Position::new( corner_0.x.max(corner_1.x), corner_0.y.max(corner_1.y), corner_0.z.max(corner_1.z), @@ -114,7 +114,7 @@ impl<'scene> HitableTrait for Boxy<'scene> { #[must_use] fn pdf_value( &self, - origin: Vec3, + origin: Position, direction: Direction, wavelength: Wavelength, time: Float, @@ -131,7 +131,7 @@ impl<'scene> HitableTrait for Boxy<'scene> { /// Returns a random point on the box #[must_use] - fn random(&self, origin: Vec3, rng: &mut SmallRng) -> Vec3 { + fn random(&self, origin: Position, rng: &mut SmallRng) -> Vec3 { let index: usize = rng.gen_range(0..6); self.sides[index].random(origin, rng) } diff --git a/clovers/src/objects/constant_medium.rs b/clovers/src/objects/constant_medium.rs index 436cc219..5805bd8d 100644 --- a/clovers/src/objects/constant_medium.rs +++ b/clovers/src/objects/constant_medium.rs @@ -8,7 +8,7 @@ use crate::{ ray::Ray, textures::Texture, wavelength::Wavelength, - Box, Direction, Float, Vec3, EPSILON_CONSTANT_MEDIUM, + Box, Direction, Float, Position, EPSILON_CONSTANT_MEDIUM, }; use rand::rngs::SmallRng; use rand::Rng; @@ -141,7 +141,7 @@ impl<'scene> HitableTrait for ConstantMedium<'scene> { #[must_use] fn pdf_value( &self, - origin: Vec3, + origin: Position, direction: Direction, wavelength: Wavelength, time: Float, @@ -154,7 +154,7 @@ impl<'scene> HitableTrait for ConstantMedium<'scene> { /// Returns a random point on the surface of the boundary of the fog // TODO: should this return a random point inside the volume instead? #[must_use] - fn random(&self, origin: Vec3, rng: &mut SmallRng) -> Vec3 { + fn random(&self, origin: Position, rng: &mut SmallRng) -> Position { self.boundary.random(origin, rng) } } diff --git a/clovers/src/objects/gltf.rs b/clovers/src/objects/gltf.rs index 0d1fcdbf..ba7efce4 100644 --- a/clovers/src/objects/gltf.rs +++ b/clovers/src/objects/gltf.rs @@ -18,7 +18,7 @@ use crate::{ materials::gltf::GLTFMaterial, ray::Ray, wavelength::Wavelength, - Direction, Float, Vec3, EPSILON_RECT_THICKNESS, EPSILON_SHADOW_ACNE, + Direction, Float, Position, Vec3, EPSILON_RECT_THICKNESS, EPSILON_SHADOW_ACNE, }; /// GLTF initialization structure @@ -101,7 +101,7 @@ impl<'scene> HitableTrait for GLTF<'scene> { #[must_use] fn pdf_value( &self, - origin: Vec3, + origin: Position, direction: Direction, wavelength: Wavelength, time: Float, @@ -111,9 +111,9 @@ impl<'scene> HitableTrait for GLTF<'scene> { .pdf_value(origin, direction, wavelength, time, rng) } - /// Returns a random point on the ssurface of the object + /// Returns a random point on the surface of the object #[must_use] - fn random(&self, origin: Vec3, rng: &mut SmallRng) -> Vec3 { + fn random(&self, origin: Position, rng: &mut SmallRng) -> Position { self.bvhnode.random(origin, rng) } } @@ -339,7 +339,7 @@ impl<'scene> HitableTrait for GLTFTriangle<'scene> { fn pdf_value( &self, - origin: Vec3, + origin: Position, direction: Direction, wavelength: Wavelength, time: Float, @@ -364,7 +364,7 @@ impl<'scene> HitableTrait for GLTFTriangle<'scene> { } } - fn random(&self, origin: Vec3, rng: &mut SmallRng) -> Vec3 { + fn random(&self, origin: Position, rng: &mut SmallRng) -> Vec3 { let mut a = rng.gen::(); let mut b = rng.gen::(); if a + b > 1.0 { diff --git a/clovers/src/objects/moving_sphere.rs b/clovers/src/objects/moving_sphere.rs index bf6739b6..a8d4a77e 100644 --- a/clovers/src/objects/moving_sphere.rs +++ b/clovers/src/objects/moving_sphere.rs @@ -7,7 +7,7 @@ use crate::{ random::random_unit_vector, ray::Ray, wavelength::Wavelength, - Direction, Float, Vec3, PI, + Direction, Float, Position, PI, }; use nalgebra::Unit; use rand::rngs::SmallRng; @@ -20,9 +20,9 @@ pub struct MovingSphereInit { #[cfg_attr(feature = "serde-derive", serde(default))] pub priority: bool, /// Center point of the sphere at time_0 - pub center_0: Vec3, + pub center_0: Position, /// Center point of the sphere at time_1 - pub center_1: Vec3, + pub center_1: Position, /// Radius of the sphere. pub radius: Float, #[cfg_attr(feature = "serde-derive", serde(default))] @@ -34,9 +34,9 @@ pub struct MovingSphereInit { /// A moving sphere object. This is represented by one `radius`, two center points `center_0` `center_1`, two times `time_0` `time_1`, and a [Material]. Any [Rays](Ray) hitting the object will also have an internal `time` value, which will be used for determining the interpolated position of the sphere at that time. With lots of rays hitting every pixel but at randomized times, we get temporal multiplexing and an approximation of perceived motion blur. pub struct MovingSphere<'scene> { /// Center point of the sphere at time_0 - pub center_0: Vec3, + pub center_0: Position, /// Center point of the sphere at time_1 - pub center_1: Vec3, + pub center_1: Position, /// Time 0 pub time_0: Float, /// Time 1 @@ -53,20 +53,20 @@ impl<'scene> MovingSphere<'scene> { /// Creates a new `MovingSphere` object. See the struct documentation for more information: [`MovingSphere`]. #[must_use] pub fn new( - center_0: Vec3, - center_1: Vec3, + center_0: Position, + center_1: Position, time_0: Float, time_1: Float, radius: Float, material: &'scene Material, ) -> Self { let box0: AABB = AABB::new_from_coords( - center_0 - Vec3::new(radius, radius, radius), - center_0 + Vec3::new(radius, radius, radius), + center_0 - Position::new(radius, radius, radius), + center_0 + Position::new(radius, radius, radius), ); let box1: AABB = AABB::new_from_coords( - center_1 - Vec3::new(radius, radius, radius), - center_1 + Vec3::new(radius, radius, radius), + center_1 - Position::new(radius, radius, radius), + center_1 + Position::new(radius, radius, radius), ); let aabb = AABB::surrounding_box(&box0, &box1); @@ -84,7 +84,7 @@ impl<'scene> MovingSphere<'scene> { /// Returns the interpolated center of the moving sphere at the given time. #[must_use] - pub fn center(&self, time: Float) -> Vec3 { + pub fn center(&self, time: Float) -> Position { self.center_0 + ((time - self.time_0) / (self.time_1 - self.time_0)) * (self.center_1 - self.center_0) } @@ -92,8 +92,8 @@ impl<'scene> MovingSphere<'scene> { /// Returns the U,V surface coordinates of a hitpoint // TODO: verify this is up to date with the sphere get_uv methods and matches a surface coordinate instead of volumetric space cordinate #[must_use] - pub fn get_uv(&self, hit_position: Vec3, time: Float) -> (Float, Float) { - let translated: Vec3 = (hit_position - self.center(time)) / self.radius; + pub fn get_uv(&self, hit_position: Position, time: Float) -> (Float, Float) { + let translated: Position = (hit_position - self.center(time)) / self.radius; let phi: Float = translated.z.atan2(translated.x); let theta: Float = translated.y.asin(); let u: Float = 1.0 - (phi + PI) / (2.0 * PI); @@ -122,7 +122,7 @@ impl<'scene> HitableTrait for MovingSphere<'scene> { let distance: Float = (-half_b - root) / a; // First possible root if distance < distance_max && distance > distance_min { - let position: Vec3 = ray.evaluate(distance); + let position: Position = ray.evaluate(distance); let outward_normal = (position - self.center(ray.time)) / self.radius; let outward_normal = Unit::new_normalize(outward_normal); let (u, v) = self.get_uv(position, ray.time); @@ -141,7 +141,7 @@ impl<'scene> HitableTrait for MovingSphere<'scene> { // Second possible root let distance: Float = (-half_b + root) / a; if distance < distance_max && distance > distance_min { - let position: Vec3 = ray.evaluate(distance); + let position: Position = ray.evaluate(distance); let outward_normal = (position - self.center(ray.time)) / self.radius; let outward_normal = Unit::new_normalize(outward_normal); let (u, v) = self.get_uv(position, ray.time); @@ -169,7 +169,7 @@ impl<'scene> HitableTrait for MovingSphere<'scene> { fn pdf_value( &self, - _origin: Vec3, + _origin: Position, _direction: Direction, _wavelength: Wavelength, _time: Float, @@ -179,7 +179,7 @@ impl<'scene> HitableTrait for MovingSphere<'scene> { 0.0 } - fn random(&self, _origin: Vec3, rng: &mut SmallRng) -> Vec3 { + fn random(&self, _origin: Position, rng: &mut SmallRng) -> Position { // FIXME: this is incorrect! does not take into account sphere size, moving sphere position *random_unit_vector(rng) } diff --git a/clovers/src/objects/quad.rs b/clovers/src/objects/quad.rs index f4841dd5..75b856f4 100644 --- a/clovers/src/objects/quad.rs +++ b/clovers/src/objects/quad.rs @@ -8,7 +8,7 @@ use crate::{ aabb::AABB, hitable::get_orientation, hitable::HitRecord, materials::Material, ray::Ray, Float, Vec3, EPSILON_RECT_THICKNESS, }; -use crate::{Direction, EPSILON_SHADOW_ACNE}; +use crate::{Direction, Position, EPSILON_SHADOW_ACNE}; use nalgebra::Unit; use rand::rngs::SmallRng; use rand::Rng; @@ -21,7 +21,7 @@ pub struct QuadInit { #[cfg_attr(feature = "serde-derive", serde(default))] pub priority: bool, /// Corner point - pub q: Vec3, + pub q: Position, /// Vector describing the u side pub u: Vec3, /// Vector describing the v side @@ -35,7 +35,7 @@ pub struct QuadInit { #[derive(Clone, Debug)] pub struct Quad<'scene> { /// Corner point - pub q: Vec3, + pub q: Position, /// Vector describing the u side pub u: Vec3, /// Vector describing the v side @@ -57,7 +57,7 @@ pub struct Quad<'scene> { impl<'scene> Quad<'scene> { /// Creates a new quad #[must_use] - pub fn new(q: Vec3, u: Vec3, v: Vec3, material: &'scene Material) -> Quad<'scene> { + pub fn new(q: Position, u: Vec3, v: Vec3, material: &'scene Material) -> Quad<'scene> { let n: Vec3 = u.cross(&v); let normal = Unit::new_normalize(n); // TODO: what is this? @@ -106,8 +106,8 @@ impl<'scene> HitableTrait for Quad<'scene> { } // Determine the hit point lies within the planar shape using its plane coordinates. - let intersection: Vec3 = ray.evaluate(t); - let planar_hitpt_vector: Vec3 = intersection - self.q; + let intersection: Position = ray.evaluate(t); + let planar_hitpt_vector: Position = intersection - self.q; let alpha: Float = self.w.dot(&planar_hitpt_vector.cross(&self.v)); let beta: Float = self.w.dot(&self.u.cross(&planar_hitpt_vector)); @@ -141,7 +141,7 @@ impl<'scene> HitableTrait for Quad<'scene> { #[must_use] fn pdf_value( &self, - origin: Vec3, + origin: Position, direction: Direction, wavelength: Wavelength, time: Float, @@ -167,8 +167,10 @@ impl<'scene> HitableTrait for Quad<'scene> { /// Returns a random point on the quadrilateral surface #[must_use] - fn random(&self, origin: Vec3, rng: &mut SmallRng) -> Vec3 { - let point: Vec3 = self.q + (rng.gen::() * self.u) + (rng.gen::() * self.v); + fn random(&self, origin: Position, rng: &mut SmallRng) -> Position { + let point: Position = + self.q + (rng.gen::() * self.u) + (rng.gen::() * self.v); + // TODO: why point minus origin? point - origin } } diff --git a/clovers/src/objects/rotate.rs b/clovers/src/objects/rotate.rs index 0baae362..891dc86e 100644 --- a/clovers/src/objects/rotate.rs +++ b/clovers/src/objects/rotate.rs @@ -5,7 +5,7 @@ use crate::{ hitable::{HitRecord, Hitable, HitableTrait}, ray::Ray, wavelength::Wavelength, - Box, Direction, Float, Vec3, + Box, Direction, Float, Position, Vec3, }; use nalgebra::Unit; use rand::rngs::SmallRng; @@ -109,7 +109,7 @@ impl<'scene> HitableTrait for RotateY<'scene> { distance_max: Float, rng: &mut SmallRng, ) -> Option { - let mut origin: Vec3 = ray.origin; + let mut origin: Position = ray.origin; let mut direction: Vec3 = *ray.direction; origin[0] = self.cos_theta * ray.origin[0] - self.sin_theta * ray.origin[2]; @@ -134,7 +134,7 @@ impl<'scene> HitableTrait for RotateY<'scene> { // Determine where the intersection is // TODO: understand and explain - let mut position: Vec3 = hit_record.position; + let mut position: Position = hit_record.position; let mut normal: Vec3 = *hit_record.normal; let distance: Float = hit_record.distance; @@ -169,7 +169,7 @@ impl<'scene> HitableTrait for RotateY<'scene> { fn pdf_value( &self, - _origin: Vec3, + _origin: Position, _direction: Direction, _wavelength: Wavelength, _time: Float, @@ -179,7 +179,7 @@ impl<'scene> HitableTrait for RotateY<'scene> { 0.0 } - fn random(&self, origin: Vec3, rng: &mut SmallRng) -> Vec3 { + fn random(&self, origin: Position, rng: &mut SmallRng) -> Vec3 { // TODO: fix, take rotation into account self.object.random(origin, rng) } diff --git a/clovers/src/objects/sphere.rs b/clovers/src/objects/sphere.rs index ab9913c6..8a73b830 100644 --- a/clovers/src/objects/sphere.rs +++ b/clovers/src/objects/sphere.rs @@ -7,7 +7,7 @@ use crate::{ onb::ONB, ray::Ray, wavelength::Wavelength, - Direction, Float, Vec3, EPSILON_SHADOW_ACNE, PI, + Direction, Float, Position, Vec3, EPSILON_SHADOW_ACNE, PI, }; use nalgebra::Unit; use rand::{rngs::SmallRng, Rng}; @@ -20,7 +20,7 @@ pub struct SphereInit { #[cfg_attr(feature = "serde-derive", serde(default))] pub priority: bool, /// Center of the sphere. - pub center: Vec3, + pub center: Position, /// Radius of the sphere. pub radius: Float, #[cfg_attr(feature = "serde-derive", serde(default))] @@ -31,7 +31,7 @@ pub struct SphereInit { #[derive(Debug, Clone)] /// A sphere object. pub struct Sphere<'scene> { - center: Vec3, + center: Position, radius: Float, material: &'scene Material, aabb: AABB, @@ -40,10 +40,10 @@ pub struct Sphere<'scene> { impl<'scene> Sphere<'scene> { /// Creates a new `Sphere` object with the given center, radius and material. #[must_use] - pub fn new(center: Vec3, radius: Float, material: &'scene Material) -> Self { + pub fn new(center: Position, radius: Float, material: &'scene Material) -> Self { let aabb = AABB::new_from_coords( - center - Vec3::new(radius, radius, radius), - center + Vec3::new(radius, radius, radius), + center - Position::new(radius, radius, radius), + center + Position::new(radius, radius, radius), ); Sphere { center, @@ -55,8 +55,8 @@ impl<'scene> Sphere<'scene> { /// Returns the U,V surface coordinates of a hitpoint #[must_use] - pub fn get_uv(&self, hit_position: Vec3, _time: Float) -> (Float, Float) { - let translated: Vec3 = (hit_position - self.center) / self.radius; + pub fn get_uv(&self, hit_position: Position, _time: Float) -> (Float, Float) { + let translated: Position = (hit_position - self.center) / self.radius; let phi: Float = translated.z.atan2(translated.x); let theta: Float = translated.y.asin(); let u: Float = 1.0 - (phi + PI) / (2.0 * PI); @@ -75,7 +75,7 @@ impl<'scene> HitableTrait for Sphere<'scene> { distance_max: Float, _rng: &mut SmallRng, ) -> Option { - let oc: Vec3 = ray.origin - self.center; + let oc: Position = ray.origin - self.center; let a: Float = ray.direction.norm_squared(); let half_b: Float = oc.dot(&ray.direction); let c: Float = oc.norm_squared() - self.radius * self.radius; @@ -86,7 +86,7 @@ impl<'scene> HitableTrait for Sphere<'scene> { // First possible root let distance: Float = (-half_b - root) / a; if distance < distance_max && distance > distance_min { - let position: Vec3 = ray.evaluate(distance); + let position: Position = ray.evaluate(distance); let outward_normal = (position - self.center) / self.radius; let outward_normal = Unit::new_normalize(outward_normal); let (u, v) = self.get_uv(position, ray.time); @@ -105,7 +105,7 @@ impl<'scene> HitableTrait for Sphere<'scene> { // Second possible root let distance: Float = (-half_b + root) / a; if distance < distance_max && distance > distance_min { - let position: Vec3 = ray.evaluate(distance); + let position = ray.evaluate(distance); let outward_normal = (position - self.center) / self.radius; let outward_normal = Unit::new_normalize(outward_normal); let (u, v) = self.get_uv(position, ray.time); @@ -135,7 +135,7 @@ impl<'scene> HitableTrait for Sphere<'scene> { #[must_use] fn pdf_value( &self, - origin: Vec3, + origin: Position, direction: Direction, wavelength: Wavelength, time: Float, @@ -163,8 +163,8 @@ impl<'scene> HitableTrait for Sphere<'scene> { // TODO: understand, document /// Utility function from Ray Tracing: The Rest of Your Life. TODO: understand, document #[must_use] - fn random(&self, origin: Vec3, rng: &mut SmallRng) -> Vec3 { - let offset: Vec3 = self.center - origin; + fn random(&self, origin: Position, rng: &mut SmallRng) -> Position { + let offset: Position = self.center - origin; let distance_squared: Float = offset.norm_squared(); let uvw = ONB::build_from_w(Unit::new_normalize(offset)); let vec = random_to_sphere(self.radius, distance_squared, rng); diff --git a/clovers/src/objects/stl.rs b/clovers/src/objects/stl.rs index e195d6f3..0b974c97 100644 --- a/clovers/src/objects/stl.rs +++ b/clovers/src/objects/stl.rs @@ -13,7 +13,7 @@ use crate::{ objects::Triangle, ray::Ray, wavelength::Wavelength, - Direction, Float, Vec3, + Direction, Float, Position, Vec3, }; /// Internal STL object representation after initialization. Contains the material for all triangles in it to avoid having n copies. @@ -50,7 +50,7 @@ impl<'scene> HitableTrait for STL<'scene> { #[must_use] fn pdf_value( &self, - origin: Vec3, + origin: Position, direction: Direction, wavelength: Wavelength, time: Float, @@ -62,7 +62,7 @@ impl<'scene> HitableTrait for STL<'scene> { /// Returns a random point on the ssurface of the object #[must_use] - fn random(&self, origin: Vec3, rng: &mut SmallRng) -> Vec3 { + fn random(&self, origin: Position, rng: &mut SmallRng) -> Vec3 { self.bvhnode.random(origin, rng) } } @@ -82,7 +82,7 @@ pub struct STLInit { /// Scaling factor for the object pub scale: Float, /// Location of the object in the rendered scene - pub center: Vec3, + pub center: Position, /// Rotation of the object. Described as three angles, `roll`, `pitch`, `yaw`, applied in that order. pub rotation: Vec3, } diff --git a/clovers/src/objects/translate.rs b/clovers/src/objects/translate.rs index 7348eab5..e6c30024 100644 --- a/clovers/src/objects/translate.rs +++ b/clovers/src/objects/translate.rs @@ -5,7 +5,7 @@ use crate::{ hitable::{HitRecord, Hitable, HitableTrait}, ray::Ray, wavelength::Wavelength, - Box, Direction, Float, Vec3, + Box, Direction, Float, Position, Vec3, }; use rand::rngs::SmallRng; @@ -85,7 +85,7 @@ impl<'scene> HitableTrait for Translate<'scene> { #[must_use] fn pdf_value( &self, - origin: Vec3, + origin: Position, direction: Direction, wavelength: Wavelength, time: Float, @@ -98,7 +98,7 @@ impl<'scene> HitableTrait for Translate<'scene> { /// Returns a random point on the surface of the moved object #[must_use] - fn random(&self, origin: Vec3, rng: &mut SmallRng) -> Vec3 { + fn random(&self, origin: Position, rng: &mut SmallRng) -> Vec3 { self.object.random(origin, rng) + self.offset } } diff --git a/clovers/src/objects/triangle.rs b/clovers/src/objects/triangle.rs index 56895b76..1e21f718 100644 --- a/clovers/src/objects/triangle.rs +++ b/clovers/src/objects/triangle.rs @@ -9,7 +9,7 @@ use crate::{ aabb::AABB, hitable::HitRecord, materials::Material, ray::Ray, Float, Vec3, EPSILON_RECT_THICKNESS, }; -use crate::{Direction, EPSILON_SHADOW_ACNE}; +use crate::{Direction, Position, EPSILON_SHADOW_ACNE}; use nalgebra::Unit; use rand::rngs::SmallRng; use rand::Rng; @@ -22,7 +22,7 @@ pub struct TriangleInit { #[cfg_attr(feature = "serde-derive", serde(default))] pub priority: bool, /// Corner point - pub q: Vec3, + pub q: Position, /// Vector describing the u side pub u: Vec3, /// Vector describing the v side @@ -36,7 +36,7 @@ pub struct TriangleInit { #[derive(Clone, Debug)] pub struct Triangle<'scene> { /// Corner point - pub q: Vec3, + pub q: Position, /// Vector describing the u side pub u: Vec3, /// Vector describing the v side @@ -58,9 +58,9 @@ pub struct Triangle<'scene> { impl<'scene> Triangle<'scene> { /// Creates a new triangle from a coordinate point and two side vectors relative to the point #[must_use] - pub fn new(q: Vec3, u: Vec3, v: Vec3, material: &'scene Material) -> Triangle<'scene> { + pub fn new(q: Position, u: Vec3, v: Vec3, material: &'scene Material) -> Triangle<'scene> { let n: Vec3 = u.cross(&v); - let normal = Unit::new_normalize(n); + let normal: Direction = Unit::new_normalize(n); // TODO: what is this? let d = -(normal.dot(&q)); // TODO: what is this? @@ -109,7 +109,7 @@ impl<'scene> Triangle<'scene> { material: &'scene Material, ) -> Triangle<'scene> { // Coordinate transform: from absolute coordinates to relative coordinates - let q: Vec3 = a; + let q: Position = a; let u: Vec3 = b - q; let v: Vec3 = c - q; Triangle::new(q, u, v, material) @@ -140,8 +140,8 @@ impl<'scene> HitableTrait for Triangle<'scene> { } // Determine the hit point lies within the planar shape using its plane coordinates. - let intersection: Vec3 = ray.evaluate(t); - let planar_hitpt_vector: Vec3 = intersection - self.q; + let intersection: Position = ray.evaluate(t); + let planar_hitpt_vector: Position = intersection - self.q; let alpha: Float = self.w.dot(&planar_hitpt_vector.cross(&self.v)); let beta: Float = self.w.dot(&self.u.cross(&planar_hitpt_vector)); @@ -177,7 +177,7 @@ impl<'scene> HitableTrait for Triangle<'scene> { #[must_use] fn pdf_value( &self, - origin: Vec3, + origin: Position, direction: Direction, wavelength: Wavelength, time: Float, @@ -204,7 +204,7 @@ impl<'scene> HitableTrait for Triangle<'scene> { /// Returns a random point on the triangle surface #[must_use] - fn random(&self, origin: Vec3, rng: &mut SmallRng) -> Vec3 { + fn random(&self, origin: Position, rng: &mut SmallRng) -> Position { let mut a = rng.gen::(); let mut b = rng.gen::(); if a + b > 1.0 { @@ -212,7 +212,7 @@ impl<'scene> HitableTrait for Triangle<'scene> { b = 1.0 - b; } - let point: Vec3 = self.q + (a * self.u) + (b * self.v); + let point: Position = self.q + (a * self.u) + (b * self.v); point - origin } @@ -238,7 +238,7 @@ mod tests { const TIME_0: Float = 0.0; const TIME_1: Float = 1.0; const RAY: Ray = Ray { - origin: Vec3::new(0.0, 0.0, -1.0), + origin: Position::new(0.0, 0.0, -1.0), direction: Unit::new_unchecked(Vec3::new(0.0, 0.0, 1.0)), time: TIME_0, wavelength: 600, @@ -318,7 +318,7 @@ mod tests { .expect("No hit record for triangle and ray"); assert!(hit_record.distance - 1.0 <= Float::EPSILON); - assert_eq!(hit_record.position, Vec3::new(0.0, 0.0, 0.0)); + assert_eq!(hit_record.position, Position::new(0.0, 0.0, 0.0)); assert_eq!( hit_record.normal, Unit::new_normalize(Vec3::new(0.0, 0.0, -1.0)) @@ -359,7 +359,7 @@ mod tests { .expect("No hit record for triangle and ray"); assert!(hit_record.distance - 1.0 <= Float::EPSILON); - assert_eq!(hit_record.position, Vec3::new(0.0, 0.0, 0.0)); + assert_eq!(hit_record.position, Position::new(0.0, 0.0, 0.0)); assert_eq!( hit_record.normal, Unit::new_normalize(Vec3::new(0.0, 0.0, -1.0)) @@ -400,7 +400,7 @@ mod tests { .expect("No hit record for triangle and ray"); assert!(hit_record.distance - 1.0 <= Float::EPSILON); - assert_eq!(hit_record.position, Vec3::new(0.0, 0.0, 0.0)); + assert_eq!(hit_record.position, Position::new(0.0, 0.0, 0.0)); assert_eq!( hit_record.normal, Unit::new_normalize(Vec3::new(0.0, 0.0, -1.0)) diff --git a/clovers/src/pdf.rs b/clovers/src/pdf.rs index c3abe22d..7e3cc2de 100644 --- a/clovers/src/pdf.rs +++ b/clovers/src/pdf.rs @@ -7,7 +7,7 @@ use crate::{ onb::ONB, random::{random_cosine_direction, random_unit_vector}, wavelength::Wavelength, - Box, Direction, Float, Vec3, PI, + Box, Direction, Float, Position, PI, }; use enum_dispatch::enum_dispatch; use rand::rngs::SmallRng; @@ -35,7 +35,7 @@ pub(crate) trait PDFTrait { ) -> Float; #[must_use] - fn generate(&self, rng: &mut SmallRng) -> Vec3; + fn generate(&self, rng: &mut SmallRng) -> Position; } #[derive(Debug, Clone)] @@ -70,20 +70,20 @@ impl PDFTrait for CosinePDF { } #[must_use] - fn generate(&self, rng: &mut SmallRng) -> Vec3 { + fn generate(&self, rng: &mut SmallRng) -> Position { *self.uvw.local(random_cosine_direction(rng)) } } #[derive(Debug, Clone)] pub struct HitablePDF<'scene> { - origin: Vec3, + origin: Position, hitable: &'scene Hitable<'scene>, } impl<'scene> HitablePDF<'scene> { #[must_use] - pub fn new(hitable: &'scene Hitable, origin: Vec3) -> Self { + pub fn new(hitable: &'scene Hitable, origin: Position) -> Self { HitablePDF { origin, hitable } } } @@ -102,7 +102,7 @@ impl<'scene> PDFTrait for HitablePDF<'scene> { } #[must_use] - fn generate(&self, rng: &mut SmallRng) -> Vec3 { + fn generate(&self, rng: &mut SmallRng) -> Position { self.hitable.random(self.origin, rng) } } @@ -138,7 +138,7 @@ impl<'scene> PDFTrait for MixturePDF<'scene> { } #[must_use] - fn generate(&self, rng: &mut SmallRng) -> Vec3 { + fn generate(&self, rng: &mut SmallRng) -> Position { if rng.gen::() { self.pdf1.generate(rng) } else { @@ -170,7 +170,8 @@ impl PDFTrait for SpherePDF { } #[must_use] - fn generate(&self, rng: &mut SmallRng) -> Vec3 { + fn generate(&self, rng: &mut SmallRng) -> Position { + // TODO: verify correctness! radius? *random_unit_vector(rng) } } @@ -199,7 +200,7 @@ impl PDFTrait for ZeroPDF { } #[must_use] - fn generate(&self, rng: &mut SmallRng) -> Vec3 { + fn generate(&self, rng: &mut SmallRng) -> Position { *random_unit_vector(rng) } } diff --git a/clovers/src/ray.rs b/clovers/src/ray.rs index 6fa9689f..98825f15 100644 --- a/clovers/src/ray.rs +++ b/clovers/src/ray.rs @@ -1,12 +1,12 @@ //! The very core of the ray tracing rendering itself: the [Ray] -use crate::{wavelength::Wavelength, Direction, Float, Vec3}; +use crate::{wavelength::Wavelength, Direction, Float, Position}; /// A Ray has an origin and a direction, as well as an instant in time it exists in. Motion blur is achieved by creating multiple rays with slightly different times. #[derive(Clone, Debug, PartialEq)] pub struct Ray { /// The origin of the ray. - pub origin: Vec3, + pub origin: Position, /// The direction of the ray. pub direction: Direction, /// The time instant at which the ray exists. @@ -18,7 +18,7 @@ pub struct Ray { impl Ray { /// Evaluates the position (coordinate) at which the ray is at the given parameter, considering the origin and direction. Considering a default unit speed of 1 per unit time, this function can be given either a time or a distance. #[must_use] - pub fn evaluate(&self, parameter: Float) -> Vec3 { + pub fn evaluate(&self, parameter: Float) -> Position { self.origin + parameter * *self.direction } } diff --git a/clovers/src/textures.rs b/clovers/src/textures.rs index ea73e147..7f1a76c9 100644 --- a/clovers/src/textures.rs +++ b/clovers/src/textures.rs @@ -10,7 +10,7 @@ pub use solid_color::*; pub use spatial_checker::*; pub use surface_checker::*; -use crate::{Float, Vec3}; +use crate::{Float, Position}; #[enum_dispatch(TextureTrait)] #[derive(Clone, Debug)] @@ -30,7 +30,7 @@ pub enum Texture { pub(crate) trait TextureTrait { /// Evaluates the color of the texture at the given surface coordinates or spatial coordinate. #[must_use] - fn color(&self, u: Float, v: Float, position: Vec3) -> Xyz; + fn color(&self, u: Float, v: Float, position: Position) -> Xyz; } impl Default for Texture { diff --git a/clovers/src/textures/solid_color.rs b/clovers/src/textures/solid_color.rs index 391944fa..de627ada 100644 --- a/clovers/src/textures/solid_color.rs +++ b/clovers/src/textures/solid_color.rs @@ -4,7 +4,7 @@ use palette::{convert::IntoColorUnclamped, white_point::E, Xyz}; use super::TextureTrait; use crate::colorinit::ColorInit; -use crate::{Float, Vec3}; +use crate::{Float, Position}; /// Initialization structure for a solid color texture. #[derive(Clone, Debug)] @@ -34,7 +34,7 @@ pub struct SolidColor { impl TextureTrait for SolidColor { /// Evaluates the color ignoring the given surface coordinates and spatial position - always returns the solid color. #[must_use] - fn color(&self, _u: Float, _v: Float, _position: Vec3) -> Xyz { + fn color(&self, _u: Float, _v: Float, _position: Position) -> Xyz { self.color.into_color_unclamped() } } diff --git a/clovers/src/textures/spatial_checker.rs b/clovers/src/textures/spatial_checker.rs index b8f7590b..224324a4 100644 --- a/clovers/src/textures/spatial_checker.rs +++ b/clovers/src/textures/spatial_checker.rs @@ -10,7 +10,7 @@ use super::TextureTrait; use crate::colorinit::ColorInit; #[cfg(feature = "serde-derive")] use crate::colorinit::TypedColorInit; -use crate::{Float, Vec3, PI}; +use crate::{Float, Position, PI}; /// A standard checkered texture based on spatial 3D texturing. #[derive(Clone, Debug)] @@ -82,7 +82,7 @@ impl SpatialChecker { impl TextureTrait for SpatialChecker { /// Evaluates the color at the given spatial position coordinate. Note that the `SpatialChecker` is spatial - surface coordinates are ignored. #[must_use] - fn color(&self, _u: Float, _v: Float, position: Vec3) -> Xyz { + fn color(&self, _u: Float, _v: Float, position: Position) -> Xyz { // TODO: convert ahead-of-time. NOTE: take into account serde-i-fication; not enough to do in `new` alone let density = self.density * PI; let sines = 1.0 // cosmetic 1 for readability of following lines :) diff --git a/clovers/src/textures/surface_checker.rs b/clovers/src/textures/surface_checker.rs index 8dceb168..604748fe 100644 --- a/clovers/src/textures/surface_checker.rs +++ b/clovers/src/textures/surface_checker.rs @@ -8,7 +8,7 @@ use super::TextureTrait; use crate::colorinit::ColorInit; #[cfg(feature = "serde-derive")] use crate::colorinit::TypedColorInit; -use crate::{Float, Vec3, PI}; +use crate::{Float, Position, PI}; #[derive(Clone, Debug)] #[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))] @@ -80,7 +80,7 @@ impl SurfaceChecker { impl TextureTrait for SurfaceChecker { /// Evaluates the color at the given surface position coordinates. Note that `SurfaceChecker` is surface-based, and thus ignores the spatial position coordinate. #[must_use] - fn color(&self, u: Float, v: Float, _position: Vec3) -> Xyz { + fn color(&self, u: Float, v: Float, _position: Position) -> Xyz { // TODO: convert ahead-of-time. NOTE: take into account serde-i-fication; not enough to do in `new` alone let density = self.density * PI; let sines = 1.0 // cosmetic 1 for readability of following lines :)