commit 137ddfbd287a3acb72e9db04d1fe2f828f9e6ec6 Author: pingupingou Date: Wed Oct 29 17:29:36 2025 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27aff38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +*.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ee1a3f7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "pbrt" +version = "0.1.0" +edition = "2024" + +[dependencies] +num-traits = "0.2.19" diff --git a/src/core/geometry.rs b/src/core/geometry.rs new file mode 100644 index 0000000..3fb89e1 --- /dev/null +++ b/src/core/geometry.rs @@ -0,0 +1,396 @@ +use std::ops::{Sub, SubAssign, Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Index, IndexMut}; +use crate::core::pbrt::Float; +use num_traits::{Num, Bounded}; + +pub trait Tuple : Index + IndexMut + Copy { + fn get(&self, i: usize) -> T; + fn set(&mut self, i: usize, val: T); +} + +#[derive(Copy, Clone, PartialEq)] +pub struct Vector2 { pub x: T, pub y: T } +#[derive(Copy, Clone, PartialEq)] +pub struct Point2 { pub x: T, pub y: T } +#[derive(Copy, Clone, PartialEq)] +pub struct Vector3 { pub x: T, pub y: T, pub z: T } +#[derive(Copy, Clone, PartialEq)] +pub struct Normal3 { pub x: T, pub y: T, pub z: T } +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Point3 { pub x: T, pub y: T, pub z : T } + +// Using macros to make this more concise +// This is pretty useful, gotta use it more often +macro_rules! impl_tuple_core { + ($Struct:ident, [$($field:ident), +]) => { + impl $Struct { + pub fn new($($field: T), +) -> Self { Self { $($field), + }} + + pub fn has_nan(&self) -> bool where T: num_traits::Float { + $(self.$field.is_nan())||+ + } + } + + impl $Struct { + pub fn dot(self, other: Self) -> T { + let mut sum = T::zero(); + $( sum = sum + self.$field * other.$field; )+ + sum + } + + pub fn abs_dot(self, other: Self) -> T { + self.dot(other).abs() + } + + pub fn length_squared(self) -> T { + self.dot(self) + } + } + + impl $Struct { + pub fn length(self) -> T { + self.length_squared().sqrt() + } + + pub fn normalize(self) -> Self { + self / self.length() + } + } + + impl> Neg for $Struct { + type Output = Self; + fn neg(self) -> Self { Self::new($(-self.$field),+) } + } + + impl + Copy> Mul for $Struct { + type Output = Self; + fn mul(self, s: T) -> Self { Self::new($(self.$field * s),+) }} + + impl MulAssign for $Struct { + fn mul_assign(&mut self, s: T) { $(self.$field *= s;)+ } + } + + impl + Copy> Div for $Struct { + type Output = Self; + fn div(self, s: T) -> Self { Self::new($(self.$field / s),+) } + } + impl DivAssign for $Struct { + fn div_assign(&mut self, s: T) { $(self.$field /= s;)+ } + } + + // Indexing + impl Index for $Struct { + type Output = T; + fn index(&self, i: usize) -> &T { + let mut idx = 0; + $( if i == idx { return &self.$field; } idx += 1; )+ + panic!("Index out of bounds"); + } + } + impl IndexMut for $Struct { + fn index_mut(&mut self, i: usize) -> &mut T { + let mut idx = 0; + $( if i == idx { return &mut self.$field; } idx += 1; )+ + panic!("Index out of bounds"); + } + } + }; +} + +macro_rules! impl_tuple_ops { + ($Struct:ident) => { + impl> Add for $Struct { + type Output = Self; + fn add(self, rhs: Self) -> Self { Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) } + } + impl AddAssign for $Struct { + fn add_assign(&mut self, rhs: Self) + { self.x += rhs.x; self.y += rhs.y; self.z += rhs.z; } + } + impl> Sub for $Struct { + type Output = Self; + fn sub(self, rhs: Self) -> Self { Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) } + } + impl SubAssign for $Struct { + fn sub_assign(&mut self, rhs: Self) { self.x -= rhs.x; self.y -= rhs.y; self.z -= rhs.z; } + } + }; +} + + +impl_tuple_core!(Vector3, [x, y, z]); +impl_tuple_core!(Normal3, [x, y, z]); +impl_tuple_core!(Point2, [x, y]); +impl_tuple_core!(Point3, [x, y, z]); + +impl_tuple_ops!(Vector3); +impl_tuple_ops!(Normal3); + +// Point - Point -> Vector +impl> Sub> for Point3 { + type Output = Vector3; + fn sub(self, rhs: Point3) -> Vector3 { + Vector3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) + } +} + +// Point + Vector -> Point +impl> Add> for Point3 { + type Output = Point3; + fn add(self, rhs: Vector3) -> Point3 { + Point3::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) + } +} +impl AddAssign> for Point3 { + fn add_assign(&mut self, rhs: Vector3) { + self.x += rhs.x; + self.y += rhs.y; + self.z += rhs.z; + } +} + +// Point - Vector -> Point +impl> Sub> for Point3 { + type Output = Point3; + fn sub(self, rhs: Vector3) -> Point3 { + Point3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) + } +} +impl SubAssign> for Point3 { + fn sub_assign(&mut self, rhs: Vector3) { + self.x -= rhs.x; + self.y -= rhs.y; + self.z -= rhs.z; + } +} + +impl Vector3 { + pub fn cross(self, other: Vector3) -> Vector3 { + Vector3 { + x: self.y * other.z - self.z * other.y, + y: self.z * other.x - self.x * other.z, + z: self.x * other.y - self.y * other.x, + } + } +} + +pub trait Norm { + type Scalar; + + fn norm(self) -> Self::Scalar; + fn norm_squared(self) -> Self::Scalar; +} + +impl Norm for Vector2 { + type Scalar = T; + fn norm(self) -> T { self.length_squared().sqrt() } + fn norm_squared(self) -> T { self.length_squared } +} + +pub fn distance(p1: Point2, p2: Point2) -> T +where + T: num_traits::Float, + Point2: Sub>, + Vector2: Copy + Mul, +{ + (p1 - p2).length() +} + +pub fn distance_squared(p1: Point2) { + +} + + +pub type Point3f = Point3; +pub type Point3i = Point3; +pub type Vector3f = Vector3; +pub type Vector3i = Vector3; +pub type Normal3f = Normal3; +pub type Normal3i = Normal3; + + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Bounds3 { + pub p_min: Point3, + pub p_max: Point3, +} + +impl Default for Bounds3 +where + T: Num + Bounded +{ + fn default() -> Self { + Self { + p_min: Point3::new(T::max_value(), T::max_value(), T::max_value()), + p_max: Point3::new(T::min_value(), T::min_value(), T::min_value()), + } + } +} + +impl Bounds3 +where + T: Num + PartialOrd + Copy, +{ + pub fn from_point(p: Point3) -> Self { + Self { p_min: p, p_max: p } + } + + pub fn from_points(p1: Point3, p2: Point3) -> Self { + Self { + p_min: Point3::new( + if p1.x < p2.x { p1.x } else { p2.x }, + if p1.y < p2.y { p1.y } else { p2.y }, + if p1.z < p2.z { p1.z } else { p2.z }, + ), + p_max: Point3::new( + if p1.x > p2.x { p1.x } else { p2.x }, + if p1.y > p2.y { p1.y } else { p2.y }, + if p1.z > p2.z { p1.z } else { p2.z }, + ), + } + } + + pub fn diagonal(&self) -> Vector3 { + self.p_max - self.p_min + } + + pub fn surface_area(&self) -> T { + let d = self.diagonal(); + let two = T::one() + T::one(); + two * (d.x * d.y + d.x * d.z + d.y * d.z) + } + + pub fn volume(&self) -> T { + let d = self.diagonal(); + d.x * d.y * d.z + } + + pub fn is_empty(&self) -> bool { + self.p_min.x >= self.p_max.x || self.p_min.y >= self.p_max.y || self.p_min.z >= self.p_max.z + } + + pub fn max_dimension(&self) -> usize { + let d = self.diagonal(); + if d.x > d.y && d.x > d.z { + 0 + } else if d.y > d.z { + 1 + } else { + 2 + } + } + + pub fn offset(p: Point3) {} +} + +fn min(a: T, b: T) -> T { if a < b { a } else { b }} +fn max(a: T, b: T) -> T { if a > b { a } else { b }} + +pub fn union_bounds_point(b: Bounds3, p: Point3) -> Bounds3 { + Bounds3 { + p_min: Point3::new(min(b.p_min.x, p.x), min(b.p_min.y, p.y), min(b.p_min.z, p.z)), + p_max: Point3::new(max(b.p_max.x, p.x), max(b.p_max.y, p.y), max(b.p_max.z, p.z)), + } +} + +pub fn union_bounds(b1: Bounds3, b2: Bounds3) -> Bounds3 { + Bounds3 { + p_min: Point3::new(min(b1.p_min.x, b2.p_min.x), min(b1.p_min.y, b2.p_min.y), min(b1.p_min.z, b2.p_min.z)), + p_max: Point3::new(max(b1.p_max.x, b2.p_max.x), max(b1.p_max.y, b2.p_max.y), max(b1.p_max.z, b2.p_max.z)), + } +} + +pub fn face_forward_v(v: Vector3f, n: Normal3f) -> Vector3f { + if v.dot(Vector3::new(n.x, n.y, n.z)) < 0.0 { -v } else { v } +} + +pub fn face_forward_n(n1: Normal3f, n2: Normal3f) -> Normal3f { + if n1.dot(n2) < 0.0 { -n1 } else { n1 } +} + +pub fn coordinate_system(v1: Vector3f) -> (Vector3f, Vector3f) { + let v2 = if v1.x.abs() > v1.y.abs() { + Vector3::new(-v1.z, 0.0, v1.x) / (v1.x * v1.x + v1.z * v1.z).sqrt() + } else { + Vector3::new(0.0, v1.z, -v1.y) / (v1.y * v1.y + v1.z * v1.z).sqrt() + }; + let v3 = v1.cross(v2); + (v2, v3) +} + +#[derive(Copy, Clone, Default, PartialEq)] +pub struct Frame { + pub x: Vector3f, + pub y: Vector3f, + pub z: Vector3f, +} + +impl Frame { + pub fn from_z(z: Vector3f) -> Self { + let (x, y) = coordinate_system(z.normalize()); + Self { x, y, z: z.normalize() } + } + + pub fn to_local(&self, v: Vector3f) -> Vector3f { + Vector3f::new(v.dot(self.x), v.dot(self.y), v.dot(self.z)) + } + + pub fn from_local(&self, v: Vector3f) -> Vector3f { + self.x * v.x + self.y * v.y + self.z * v.z + } + +} + +#[derive(Copy, Clone, PartialEq)] +pub struct Quaternion { + pub v: Vector3f, + pub w: Float, +} + +impl Default for Quaternion { + fn default() -> Self { + Self { v: Vector3f::default(), w: 1.0 } + } +} + +impl Add for Quaternion { + type Output = Self; + fn add(self, rhs: Quaternion) -> Self { + Self { v: self.v + rhs.v, w: self.w + rhs.w } + } +} + +impl AddAssign for Quaternion { + fn add_assign(&mut self, rhs: Self) { self.v += rhs.v; self.w += rhs.w; } +} + +impl Sub for Quaternion { + type Output = Self; + fn sub(self, rhs: Self) -> Self { Self { v: self.v - rhs.v, w: self.w - rhs.w }} +} + +impl SubAssign for Quaternion { + fn sub_assign(&mut self, rhs: Self) { self.v -= rhs.v; self.w -= rhs.w; } +} + +impl Mul for Quaternion { + type Output = Self; + fn mul(self, rhs: Float) -> Self { Self { v: self.v * rhs, w: self.w * rhs }} +} + +impl MulAssign for Quaternion { + fn mul_assign(&mut self, rhs: Float) { self.v *= rhs; self.w *= rhs; } +} + +impl Div for Quaternion { + type Output = Self; + fn div(self, rhs: Float) -> Self { Self { v: self.v / rhs, w: self.w / rhs }} +} + +impl DivAssign for Quaternion { + fn div_assign(&mut self, rhs: Float) { self.v /= rhs; self.w /= rhs; } +} + +impl Neg for Quaternion { + type Output = Self; + fn neg(self) -> Self { Self { v: -self.v, w: -self.w }} +} diff --git a/src/core/integrator.rs b/src/core/integrator.rs new file mode 100644 index 0000000..60623ab --- /dev/null +++ b/src/core/integrator.rs @@ -0,0 +1,8 @@ +use std::sync::Arc; + +use crate::core::primitive::Primitive; + +pub struct Integrator { + aggregate: Arc, + lights: Vec>, +} diff --git a/src/core/light.rs b/src/core/light.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/core/material.rs b/src/core/material.rs new file mode 100644 index 0000000..3fa532d --- /dev/null +++ b/src/core/material.rs @@ -0,0 +1,44 @@ +pub struct CoatedDiffuseMaterial; +pub struct CoatedConductorMaterial; +pub struct ConductorMaterial; +pub struct DielectricMaterial; +pub struct DiffuseMaterial; +pub struct DiffuseTransmissionMaterial; +pub struct HairMaterial; +pub struct MeasuredMaterial; +pub struct SubsurfaceMaterial; +pub struct ThinDielectricMaterial; +pub struct MixMaterial; + +pub enum Material { + CoatedDiffuse(CoatedDiffuseMaterial), + CoatedConductor(CoatedConductorMaterial), + Conductor(ConductorMaterial), + Dielectric(DielectricMaterial), + Diffuse(DiffuseMaterial), + DiffuseTransmission(DiffuseTransmissionMaterial), + Hair(HairMaterial), + Measured(MeasuredMaterial), + Subsurface(SubsurfaceMaterial), + ThinDielectric(ThinDielectricMaterial), + Mix(MixMaterial), +} + +impl Material { + pub fn get_bsdf( + &self, tex_eval: T, ctx: MaterialEvalContext, lambda: &mut SampledWavelengths, buf: &mut ScratchBuffer) -> BSDF { + match self { + Material::CoatedDiffuse(m) => m.get_bsdf(tex_eval, ctx, lambda, buf), + Material::CoatedConductor(m) => m.get_bsdf(tex_eval, ctx, lambda, buf), + Material::Conductor(m) => m.get_bsdf(tex_eval, ctx, lambda, buf), + Material::Dielectric(m) => m.get_bsdf(tex_eval, ctx, lambda, buf), + Material::Diffuse(m) => m.get_bsdf(tex_eval, ctx, lambda, buf), + Material::DiffuseTransmission(m) => m.get_bsdf(tex_eval, ctx, lambda, buf), + Material::Hair(m) => m.get_bsdf(tex_eval, ctx, lambda, buf), + Material::Measured(m) => m.get_bsdf(tex_eval, ctx, lambda, buf), + Material::Subsurface(m) => m.get_bsdf(tex_eval, ctx, lambda, buf), + Material::ThinDielectric(m) => m.get_bsdf(tex_eval, ctx, lambda, buf), + Material::Mix(m) => m.get_bsdf(tex_eval, ctx, lambda, buf), + } + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..845320d --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1,8 @@ +pub mod geometry; +pub mod integrator; +pub mod light; +pub mod material; +pub mod pbrt; +pub mod primitive; +pub mod shape; +pub mod texture; diff --git a/src/core/pbrt.rs b/src/core/pbrt.rs new file mode 100644 index 0000000..d0813f7 --- /dev/null +++ b/src/core/pbrt.rs @@ -0,0 +1,25 @@ + +pub type Float = f32; + +pub const MACHINE_EPSILON: Float = std::f32::EPSILON * 0.5; +pub const SHADOW_EPSILON: Float = 0.0001; + +#[inline] +pub fn lerp(x: T, a: f64, b: f64) -> f64 { + (T::onw() - x) * a + x * b +} + +pub fn linear_pdf(x: f64, a: f64, b: f64) -> f64 { + if x < 0.0 || x > 1.0 { + return 0.0; + } + 2.0 * lerp(x, a, b) / (a + b) +} + +pub fn sample_linear(u: f64, a: f64, b: f64) -> f64 { + if u == 0.0 && a == 0.0 { + return 0.0; + } + + u * (a + b) +} diff --git a/src/core/primitive.rs b/src/core/primitive.rs new file mode 100644 index 0000000..444ea51 --- /dev/null +++ b/src/core/primitive.rs @@ -0,0 +1,45 @@ +// use crate::core::medium::MediumInterface; +// use crate::core::light::Light; +use crate::utils::types::Bounds3f; +// +// +pub struct GeometricPrimitive; +pub struct TransformedPrimitive; +pub struct AnimatedPrimitive; +pub struct BVHAggregatePrimitive; +pub struct KdTreeAggregate; + +pub enum Primitive { + Geometric(GeometricPrimitive), + Transformed(TransformedPrimitive), + Animated(AnimatedPrimitive), + BVH(BVHAggregatePrimitive), + KdTree(KdTreeAggregate) +} + +impl Primitive { + pub fn bounds(&self) -> Bounds3f { + match self { + Primitive::Geometric(primitive) => primitive.bounds(), + Primitive::Transformed(primitive) => primitive.bounds(), + Primitive::Animated(primitive) => primitive.bounds(), + Primitive::BVH(primitive) => primitive.bounds(), + Primitive::KdTree(primitive) => primitive.bounds(), + } + } + + pub fn +} + +// struct GeometricPrimitive { +// shape: Shape, +// material: Material, +// area_light: Light, +// medium_interface: MediumInterface, +// alpha: Texture, +// } +// +// +// impl GeometricPrimitive { +// fn new(shape: Shape, material: Material, medium_interface: MediumInterface, alpha: Texture) +// } diff --git a/src/core/shape.rs b/src/core/shape.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/core/texture.rs b/src/core/texture.rs new file mode 100644 index 0000000..f4df3d9 --- /dev/null +++ b/src/core/texture.rs @@ -0,0 +1,107 @@ +pub struct TextureEvalContext; + +pub struct FloatImageTexture; +pub struct GPUFloatImageTexture; +pub struct FloatMixTexture; +pub struct FloatDirectionMixTexture; +pub struct FloatScaledTexture; +pub struct FloatConstantTexture; +pub struct FloatBilerpTexture; +pub struct FloatCheckerboardTexture; +pub struct FloatDotsTexture; +pub struct FBmTexture; +pub struct FloatPtexTexture; +pub struct GPUFloatPtex; +pub struct WindyTexture; +pub struct WrinkledTexture; + +pub enum FloatTexture { + FloatImage(FloatImageTexture), + GPUFloatImage(GPUFloatImageTexture), + FloatMix(FloatMixTexture), + FloatDirectionMix(FloatDirectionMixTexture), + FloatScaled(FloatScaledTexture), + FloatConstant(FloatConstantTexture), + FloatBilerp(FloatBilerpTexture), + FloatCheckerboard(FloatCheckerboardTexture), + FloatDots(FloatDotsTexture), + FBm(FBmTexture), + FloatPtex(FloatPtexTexture), + GPUFloatPtex(GPUFloatPtex), + Windy(WindyTexture), + Wrinkled(WrinkledTexture), +} + +impl FloatTexture { + pub fn evaluate(&self, ctx: TextureEvalContext) -> f32 { + match self { + FloatTexture::FloatImage(texture) => texture.evaluate(ctx), + FloatTexture::GPUFloatImage(texture) => texture.evaluate(ctx), + FloatTexture::FloatMix(texture) => texture.evaluate(ctx), + FloatTexture::FloatDirectionMix(texture) => texture.evaluate(ctx), + FloatTexture::FloatScaled(texture) => texture.evaluate(ctx), + FloatTexture::FloatConstant(texture) => texture.evaluate(ctx), + FloatTexture::FloatBilerp(texture) => texture.evaluate(ctx), + FloatTexture::FloatCheckerboard(texture) => texture.evaluate(ctx), + FloatTexture::FloatDots(texture) => texture.evaluate(ctx), + FloatTexture::FBm(texture) => texture.evaluate(ctx), + FloatTexture::FloatPtex(texture) => texture.evaluate(ctx), + FloatTexture::GPUFloatPtex(texture) => texture.evaluate(ctx), + FloatTexture::Windy(texture) => texture.evaluate(ctx), + FloatTexture::Wrinkled(texture) => texture.evaluate(ctx), + } + } +} + +pub struct RGBConstantTexture; +pub struct RGBReflectanceConstantTexture; +pub struct SpectrumConstantTexture; +pub struct SpectrumBilerpTexture; +pub struct SpectrumCheckerboardTexture; +pub struct SpectrumImageTexture; +pub struct GPUSpectrumImageTexture; +pub struct MarbleTexture; +pub struct SpectrumMixTexture; +pub struct SpectrumDirectionMixTexture; +pub struct SpectrumDotsTexture; +pub struct SpectrumPtexTexture; +pub struct GPUSpectrumPtexTexture; +pub struct SpectrumScaledTexture; + +pub enum SpectrumTexture { + RGBConstant(RGBConstantTexture), + RGBReflectanceConstant(RGBReflectanceConstantTexture), + SpectrumConstant(SpectrumConstantTexture), + SpectrumBilerp(SpectrumBilerpTexture), + SpectrumCheckerboard(SpectrumCheckerboardTexture), + SpectrumImage(SpectrumImageTexture), + GPUSpectrumImage(GPUSpectrumImageTexture), + Marble(MarbleTexture), + SpectrumMix(SpectrumMixTexture), + SpectrumDirectionMix(SpectrumDirectionMixTexture), + SpectrumDots(SpectrumDotsTexture), + SpectrumPtex(SpectrumPtexTexture), + GPUSpectrumPtex(GPUSpectrumPtexTexture), + SpectrumScaled(SpectrumScaledTexture), +} + +impl SpectrumTexture { + pub fn evaluate(&self, ctx: TextureEvalContext) -> f32 { + match self { + SpectrumTexture::FloatImage(texture) => texture.evaluate(ctx), + SpectrumTexture::GPUFloatImage(texture) => texture.evaluate(ctx), + SpectrumTexture::FloatMix(texture) => texture.evaluate(ctx), + SpectrumTexture::FloatDirectionMix(texture) => texture.evaluate(ctx), + SpectrumTexture::FloatScaled(texture) => texture.evaluate(ctx), + SpectrumTexture::FloatConstant(texture) => texture.evaluate(ctx), + SpectrumTexture::FloatBilerp(texture) => texture.evaluate(ctx), + SpectrumTexture::FloatCheckerboard(texture) => texture.evaluate(ctx), + SpectrumTexture::FloatDots(texture) => texture.evaluate(ctx), + SpectrumTexture::FBm(texture) => texture.evaluate(ctx), + SpectrumTexture::FloatPtex(texture) => texture.evaluate(ctx), + SpectrumTexture::GPUFloatPtex(texture) => texture.evaluate(ctx), + SpectrumTexture::Windy(texture) => texture.evaluate(ctx), + SpectrumTexture::Wrinkled(texture) => texture.evaluate(ctx), + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0b43446 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +mod core; +mod utils; diff --git a/src/mod.rs b/src/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..cd40856 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod types; diff --git a/src/utils/types.rs b/src/utils/types.rs new file mode 100644 index 0000000..e2dbb90 --- /dev/null +++ b/src/utils/types.rs @@ -0,0 +1,245 @@ +use std::ops::{Sub, Add, Div, Mul, Neg, Index, IndexMut}; +use num_traits::{Num, Float, Bounded}; + +pub trait Tuple : Index + IndexMut + Copy { + fn get(&self, i: usize) -> T; + fn set(&mut self, i: usize, val: T); + fn has_nan(&self) -> bool where T: Float { + for i in 0..N { + if self.get(i).is_nan() { + return true + } + } + false + } +} + +#[derive(Copy, Clone, PartialEq)] +pub struct Vector2 { pub x: T, pub y: T } +#[derive(Copy, Clone, PartialEq)] +pub struct Point2 { pub x: T, pub y: T } +#[derive(Copy, Clone, PartialEq)] +pub struct Vector3 { pub x: T, pub y: T, pub z: T } +#[derive(Copy, Clone, PartialEq)] +pub struct Normal3 { pub x: T, pub y: T, pub z: T } +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Point3 { pub x: T, pub y: T, pub z : T } + +// Using macros to make this more concise +macro_rules! impl_tuple3 { + ($Struct:ident, [$($field:ident), +]) => { + impl $Struct { + pub fn new($($field: T), +) -> Self { Self { $($field), + }} + } + + impl $Struct { + pub fn dot(&self, other: Self) -> T { + self.x * other.x + self.y * other.y + self.z * other.z + } + + pub fn length_squared(self) -> T { + self.dot(self) + } + } + + impl $Struct { + pub fn length(self) -> T { + self.length_squared().sqrt() + } + + pub fn normalize(self) -> Self { + self / self.length() + } + } + + // Operators + impl> Add for $Struct { + type Output = Self; + fn add(self, rhs: Self) -> Self { Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z )} + } + + impl + Copy > Mul for $Struct { + type Output = Self; + fn mul(self, rhs: T) -> Self { Self::new(self.x * rhs, self.y * rhs, self.z * rhs )} + } + + impl + Copy > Div for $Struct { + type Output = Self; + fn div(self, rhs: T) -> Self { Self::new(self.x / rhs, self.y / rhs, self.z / rhs )} + } + + impl> Neg for $Struct { + type Output = Self; + fn neg(self) -> Self { Self::new(-self.x, -self.y, -self.z)} + } + + impl Index for $Struct { + type Output = T; + fn index(&self, i: usize) -> &T { + match i { 0 => &self.x, 1 => &self.y, 2 => &self.z, _ => panic!("Index out of bonds") } + } + } + + impl IndexMut for $Struct { + fn index_mut(&mut self, i: usize) -> &mut T { + match i { 0 => &mut self.x, 1 => &mut self.y, 2 => &mut self.z, _ => panic!("Index out of bonds") } + } + } + }; +} + +macro_rules! impl_component_ops { + ($Struct:ident) => { + impl> Sub for $Struct { + type Output = Self; + fn sub(self, rhs: Self) -> Self { Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) } + } + } +} + +impl_tuple3!(Vector3, [x, y, z]); +impl_tuple3!(Point3, [x, y, z]); +impl_tuple3!(Normal3, [x, y, z]); +impl_component_ops!(Vector3); +impl_component_ops!(Normal3); + +// Point3 - Point3 -> Vector3 +impl> Sub> for Point3 { + type Output = Vector3; + fn sub(self, rhs: Point3) -> Vector3 { + Vector3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) + } +} + +// Point3 + Vector3 -> Point3 +impl> Add> for Point3 { + type Output = Point3; + fn add(self, rhs: Vector3) -> Point3 { + Point3::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) + } +} + +// Point3 - Vector3 -> Point3 +impl> Sub> for Point3 { + type Output = Point3; + fn sub(self, rhs: Vector3) -> Point3 { + Point3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) + } +} + + +// Cross Product for Vector3 +impl Vector3 { + pub fn cross(self, other: Vector3) -> Vector3 { + Vector3 { + x: self.y * other.z - self.z * other.y, + y: self.z * other.x - self.x * other.z, + z: self.x * other.y - self.y * other.x, + } + } +} + + +pub type Point3f = Point3; +pub type Point3i = Point3; +pub type Vector3f = Vector3; +pub type Vector3i = Vector3; +pub type Normal3f = Normal3; +pub type Normal3i = Normal3; + + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Bounds3 { + pub p_min: Point3, + pub p_max: Point3, +} + +impl Bounds3 +where + T: Num + Bounded + PartialOrd + Copy, +{ + pub fn new() -> Self { + Self { + p_min: Point3::new(T::max_value(), T::max_value(), T::max_value()), + p_max: Point3::new(T::min_value(), T::min_value(), T::min_value()), + } + } + + pub fn from_point(p: Point3) -> Self { + Self { p_min: p, p_max: p } + } + + pub fn from_points(p1: Point3, p2: Point3) -> Self { + Self { + p_min: Point3::new( + if p1.x < p2.x { p1.x } else { p2.x }, + if p1.y < p2.y { p1.y } else { p2.y }, + if p1.z < p2.z { p1.z } else { p2.z }, + ), + p_max: Point3::new( + if p1.x > p2.x { p1.x } else { p2.x }, + if p1.y > p2.y { p1.y } else { p2.y }, + if p1.z > p2.z { p1.z } else { p2.z }, + ), + } + } + + pub fn diagonal(&self) -> Vector3 { + self.p_max - self.p_min + } + + pub fn surface_area(&self) -> T { + let d = self.diagonal(); + let two = T::one() + T::one(); + two * (d.x * d.y + d.x * d.z + d.y * d.z) + } + + pub fn volume(&self) -> T { + let d = self.diagonal(); + d.x * d.y * d.z + } + + pub fn is_empty(&self) -> bool { + self.p_min.x >= self.p_max.x || self.p_min.y >= self.p_max.y || self.p_min.z >= self.p_max.z + } + + pub fn max_dimension(&self) -> usize { + let d = self.diagonal(); + if d.x > d.y && d.x > d.z { + return 0; + } else if d.y > d.z { + return 1; + } + 2 + } +} + +pub fn union_bounds(b1: Bounds3, b2: Bounds3) -> Bounds3 { + Bounds3 { + p_min: Point3::new( + if b1.p_min.x < b2.p_min.x { b1.p_min.x } else { b2.p_min.x }, + if b1.p_min.y < b2.p_min.y { b1.p_min.y } else { b2.p_min.y }, + if b1.p_min.z < b2.p_min.z { b1.p_min.z } else { b2.p_min.z }, + ), + p_max: Point3::new( + if b1.p_max.x > b2.p_max.x { b1.p_max.x } else { b2.p_max.x }, + if b1.p_max.y > b2.p_max.y { b1.p_max.y } else { b2.p_max.y }, + if b1.p_max.z > b2.p_max.z { b1.p_max.z } else { b2.p_max.z }, + ), + } +} + +pub fn union_bounds_point(b: Bounds3, p: Point3) -> Bounds3 { + Bounds3 { + p_min: Point3::new( + if b.p_min.x < p.x { b.p_min.x } else { p.x }, + if b.p_min.y < p.y { b.p_min.y } else { p.y }, + if b.p_min.z < p.z { b.p_min.z } else { p.z }, + ), + p_max: Point3::new( + if b.p_max.x > p.x { b.p_max.x } else { p.x }, + if b.p_max.y > p.y { b.p_max.y } else { p.y }, + if b.p_max.z > p.z { b.p_max.z } else { p.z }, + ), + } +}