Extract the types into separate files

Not for any reason other than clarity. The number of operations that are
declared for each type is making it difficult to find operations and
difficult to keep the order consistent.
This commit is contained in:
Savanni D'Gerinel 2024-06-09 14:18:46 -04:00
parent 39c947b461
commit 2a38ca38e1
7 changed files with 424 additions and 409 deletions

View File

@ -33,5 +33,11 @@ fn main() {
p = tick(&e, &p); p = tick(&e, &p);
} }
println!("distance travelled: [{}] {} {} {}", (p.position - start.position).magnitude(), p.position.x, p.position.y, p.position.z); println!(
"distance travelled: [{}] {} {} {}",
(p.position - start.position).magnitude(),
p.position.x,
p.position.y,
p.position.z
);
} }

View File

@ -1,2 +1 @@
pub mod types; pub mod types;

View File

@ -1,407 +0,0 @@
const EPSILON: f64 = 0.00001;
fn eq_f64(l: f64, r: f64) -> bool {
(l - r).abs() < EPSILON
}
#[derive(Debug, Clone, Copy)]
pub struct Tuple {
pub x: f64,
pub y: f64,
pub z: f64,
pub w: f64, // Used for very low-level math. w = 1.0 indicates a point, w = 0.0 indicates a vector.
// Theoretically the type system should make this redundant, so operations on points
// and vectors can always assert the correct value.
}
impl Tuple {
fn dot(&self, r: &Tuple) -> f64 {
self.x * r.x + self.y * r.y + self.z * r.z + self.w * r.w
}
}
impl PartialEq for Tuple {
fn eq(&self, r: &Tuple) -> bool {
eq_f64(self.x, r.x) && eq_f64(self.y, r.y) && eq_f64(self.z, r.z) && eq_f64(self.w, r.w)
}
}
impl std::ops::Add for Tuple {
type Output = Tuple;
fn add(self, r: Tuple) -> Self::Output {
return Self::Output {
x: self.x + r.x,
y: self.y + r.y,
z: self.z + r.z,
w: self.w + r.w,
};
}
}
impl std::ops::Sub for Tuple {
type Output = Tuple;
fn sub(self, r: Tuple) -> Self::Output {
return Self::Output {
x: self.x - r.x,
y: self.y - r.y,
z: self.z - r.z,
w: self.w - r.w,
};
}
}
impl std::ops::Neg for Tuple {
type Output = Tuple;
fn neg(self) -> Self::Output {
return Self::Output {
x: -self.x,
y: -self.y,
z: -self.z,
w: -self.w,
};
}
}
impl std::ops::Mul<f64> for Tuple {
type Output = Tuple;
fn mul(self, scalar: f64) -> Self::Output {
return Self::Output {
x: self.x * scalar,
y: self.y * scalar,
z: self.z * scalar,
w: self.w * scalar,
};
}
}
impl std::ops::Div<f64> for Tuple {
type Output = Tuple;
fn div(self, scalar: f64) -> Self::Output {
return Self::Output {
x: self.x / scalar,
y: self.y / scalar,
z: self.z / scalar,
w: self.w / scalar,
};
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Point(Tuple);
impl Point {
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self(Tuple { x, y, z, w: 1.0 })
}
}
impl std::ops::Deref for Point {
type Target = Tuple;
fn deref(&self) -> &Tuple {
&self.0
}
}
impl From<Tuple> for Point {
fn from(tuple: Tuple) -> Self {
assert_eq!(tuple.w, 1.0);
Self(tuple)
}
}
impl Default for Point {
fn default() -> Self {
Self::new(0., 0., 0.)
}
}
impl std::ops::Add<Vector> for Point {
type Output = Point;
fn add(self, r: Vector) -> Self::Output {
Point::from(self.0 + r.0)
}
}
impl std::ops::Sub for Point {
type Output = Vector;
fn sub(self, r: Point) -> Self::Output {
Vector::from(self.0 - r.0)
}
}
impl std::ops::Sub<Vector> for Point {
type Output = Point;
fn sub(self, r: Vector) -> Self::Output {
Point::from(self.0 - r.0)
}
}
impl std::ops::Neg for Point {
type Output = Point;
fn neg(self) -> Self::Output {
let mut t = -self.0;
t.w = 1.;
Point::from(t)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Vector(Tuple);
impl Vector {
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self(Tuple { x, y, z, w: 0.0 })
}
pub fn magnitude(&self) -> f64 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
pub fn normalize(&self) -> Self {
let mag = self.magnitude();
Self::new(self.x / mag, self.y / mag, self.z / mag)
}
pub fn dot(&self, r: &Vector) -> f64 {
self.0.dot(r)
}
pub fn cross(&self, r: &Vector) -> Self {
let x = self.y * r.z - self.z * r.y;
let y = self.z * r.x - self.x * r.z;
let z = self.x * r.y - self.y * r.x;
Self::new(x, y, z)
}
}
impl std::ops::Deref for Vector {
type Target = Tuple;
fn deref(&self) -> &Tuple {
&self.0
}
}
impl Default for Vector {
fn default() -> Self {
Self::new(0., 0., 0.)
}
}
impl From<Tuple> for Vector {
fn from(tuple: Tuple) -> Self {
assert_eq!(tuple.w, 0.0);
Self(tuple)
}
}
impl std::ops::Add for Vector {
type Output = Vector;
fn add(self, r: Self) -> Self {
Vector::from(self.0 + r.0)
}
}
impl std::ops::Sub for Vector {
type Output = Vector;
fn sub(self, r: Self) -> Self {
Vector::from(self.0 - r.0)
}
}
impl std::ops::Mul<f64> for Vector {
type Output = Vector;
fn mul(self, r: f64) -> Self {
Vector::from(self.0 * r)
}
}
impl std::ops::Neg for Vector {
type Output = Vector;
fn neg(self) -> Self::Output {
let mut t = -self.0;
t.w = 0.;
Vector::from(t)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eq_f64_compares_values() {
assert!(eq_f64(1.0, 1.0));
assert!(eq_f64(0.9994, 0.9994));
assert!(eq_f64(0.9999994, 0.9999995));
assert!(!eq_f64(0.9995, 0.9994));
}
#[test]
fn add_two_tuples() {
let a = Tuple {
x: 3.,
y: -2.,
z: 5.,
w: 1.,
};
let b = Tuple {
x: -2.,
y: 3.,
z: 1.,
w: 0.,
};
assert_eq!(
a + b,
Tuple {
x: 1.,
y: 1.,
z: 6.,
w: 1.
}
);
}
#[test]
fn subtracts_two_tuples() {
let a = Tuple {
x: 3.,
y: 2.,
z: 1.,
w: 1.,
};
let b = Tuple {
x: 5.,
y: 6.,
z: 7.,
w: 1.,
};
assert_eq!(
a - b,
Tuple {
x: -2.,
y: -4.,
z: -6.,
w: 0.
}
);
}
#[test]
fn adds_point_and_vector() {
let a = Point::new(3., -2., 5.);
let b = Vector::new(-2., 3., 1.);
assert_eq!(a + b, Point::new(1., 1., 6.,));
}
#[test]
fn subtracts_two_points() {
let a = Point::new(3., 2., 1.);
let b = Point::new(5., 6., 7.);
assert_eq!(a - b, Vector::new(-2., -4., -6.,));
}
#[test]
fn subtracts_vector_from_point() {
let a = Point::new(3., 2., 1.);
let b = Vector::new(5., 6., 7.);
assert_eq!(a - b, Point::new(-2., -4., -6.));
}
#[test]
fn subtracts_two_vectors() {
let a = Vector::new(3., 2., 1.);
let b = Vector::new(5., 6., 7.);
assert_eq!(a - b, Vector::new(-2., -4., -6.));
}
#[test]
fn it_negates_primitives() {
assert_eq!(
-Tuple {
x: 1.,
y: 2.,
z: 3.,
w: 4.
},
Tuple {
x: -1.,
y: -2.,
z: -3.,
w: -4.
},
);
}
#[test]
fn multiply_tuple_by_scalar() {
assert_eq!(
Tuple {
x: 1.,
y: -2.,
z: 3.,
w: -4.
} * 3.5,
Tuple {
x: 3.5,
y: -7.,
z: 10.5,
w: -14.
}
);
}
#[test]
fn divide_tuple_by_scalar() {
assert_eq!(
Tuple {
x: 1.,
y: -2.,
z: 3.,
w: -4.
} / 2.,
Tuple {
x: 0.5,
y: -1.,
z: 1.5,
w: -2.
}
);
}
#[test]
fn magnitude_of_vector() {
assert_eq!(Vector::new(1., 0., 0.).magnitude(), 1.);
assert_eq!(Vector::new(0., 1., 0.).magnitude(), 1.);
assert_eq!(Vector::new(0., 0., 1.).magnitude(), 1.);
assert_eq!(Vector::new(1., 2., 3.).magnitude(), 14_f64.sqrt());
assert_eq!(Vector::new(-1., -2., -3.).magnitude(), 14_f64.sqrt());
}
#[test]
fn normalize_vector() {
assert_eq!(Vector::new(4., 0., 0.).normalize(), Vector::new(1., 0., 0.));
assert_eq!(
Vector::new(1., 2., 3.).normalize(),
Vector::new(0.26726, 0.53452, 0.80178)
);
assert_eq!(Vector::new(1., 2., 3.).normalize().magnitude(), 1.);
}
#[test]
fn dot_product() {
assert_eq!(Vector::new(1., 2., 3.).dot(&Vector::new(2., 3., 4.)), 20.);
}
#[test]
fn cross_product() {
assert_eq!(
Vector::new(1., 2., 3.).cross(&Vector::new(2., 3., 4.)),
Vector::new(-1., 2., -1.)
);
assert_eq!(
Vector::new(2., 3., 4.).cross(&Vector::new(1., 2., 3.)),
Vector::new(1., -2., 1.)
);
}
}

194
ray-tracer/src/types/mod.rs Normal file
View File

@ -0,0 +1,194 @@
mod point;
mod tuple;
mod vector;
pub use point::Point;
pub use tuple::Tuple;
pub use vector::Vector;
const EPSILON: f64 = 0.00001;
fn eq_f64(l: f64, r: f64) -> bool {
(l - r).abs() < EPSILON
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eq_f64_compares_values() {
assert!(eq_f64(1.0, 1.0));
assert!(eq_f64(0.9994, 0.9994));
assert!(eq_f64(0.9999994, 0.9999995));
assert!(!eq_f64(0.9995, 0.9994));
}
#[test]
fn add_two_tuples() {
let a = Tuple {
x: 3.,
y: -2.,
z: 5.,
w: 1.,
};
let b = Tuple {
x: -2.,
y: 3.,
z: 1.,
w: 0.,
};
assert_eq!(
a + b,
Tuple {
x: 1.,
y: 1.,
z: 6.,
w: 1.
}
);
}
#[test]
fn subtracts_two_tuples() {
let a = Tuple {
x: 3.,
y: 2.,
z: 1.,
w: 1.,
};
let b = Tuple {
x: 5.,
y: 6.,
z: 7.,
w: 1.,
};
assert_eq!(
a - b,
Tuple {
x: -2.,
y: -4.,
z: -6.,
w: 0.
}
);
}
#[test]
fn adds_point_and_vector() {
let a = Point::new(3., -2., 5.);
let b = Vector::new(-2., 3., 1.);
assert_eq!(a + b, Point::new(1., 1., 6.,));
}
#[test]
fn subtracts_two_points() {
let a = Point::new(3., 2., 1.);
let b = Point::new(5., 6., 7.);
assert_eq!(a - b, Vector::new(-2., -4., -6.,));
}
#[test]
fn subtracts_vector_from_point() {
let a = Point::new(3., 2., 1.);
let b = Vector::new(5., 6., 7.);
assert_eq!(a - b, Point::new(-2., -4., -6.));
}
#[test]
fn subtracts_two_vectors() {
let a = Vector::new(3., 2., 1.);
let b = Vector::new(5., 6., 7.);
assert_eq!(a - b, Vector::new(-2., -4., -6.));
}
#[test]
fn it_negates_primitives() {
assert_eq!(
-Tuple {
x: 1.,
y: 2.,
z: 3.,
w: 4.
},
Tuple {
x: -1.,
y: -2.,
z: -3.,
w: -4.
},
);
}
#[test]
fn multiply_tuple_by_scalar() {
assert_eq!(
Tuple {
x: 1.,
y: -2.,
z: 3.,
w: -4.
} * 3.5,
Tuple {
x: 3.5,
y: -7.,
z: 10.5,
w: -14.
}
);
}
#[test]
fn divide_tuple_by_scalar() {
assert_eq!(
Tuple {
x: 1.,
y: -2.,
z: 3.,
w: -4.
} / 2.,
Tuple {
x: 0.5,
y: -1.,
z: 1.5,
w: -2.
}
);
}
#[test]
fn magnitude_of_vector() {
assert_eq!(Vector::new(1., 0., 0.).magnitude(), 1.);
assert_eq!(Vector::new(0., 1., 0.).magnitude(), 1.);
assert_eq!(Vector::new(0., 0., 1.).magnitude(), 1.);
assert_eq!(Vector::new(1., 2., 3.).magnitude(), 14_f64.sqrt());
assert_eq!(Vector::new(-1., -2., -3.).magnitude(), 14_f64.sqrt());
}
#[test]
fn normalize_vector() {
assert_eq!(Vector::new(4., 0., 0.).normalize(), Vector::new(1., 0., 0.));
assert_eq!(
Vector::new(1., 2., 3.).normalize(),
Vector::new(0.26726, 0.53452, 0.80178)
);
assert_eq!(Vector::new(1., 2., 3.).normalize().magnitude(), 1.);
}
#[test]
fn dot_product() {
assert_eq!(Vector::new(1., 2., 3.).dot(&Vector::new(2., 3., 4.)), 20.);
}
#[test]
fn cross_product() {
assert_eq!(
Vector::new(1., 2., 3.).cross(&Vector::new(2., 3., 4.)),
Vector::new(-1., 2., -1.)
);
assert_eq!(
Vector::new(2., 3., 4.).cross(&Vector::new(1., 2., 3.)),
Vector::new(1., -2., 1.)
);
}
}

View File

@ -0,0 +1,60 @@
use crate::types::{Tuple, Vector};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Point(Tuple);
impl Point {
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self(Tuple { x, y, z, w: 1.0 })
}
}
impl Default for Point {
fn default() -> Self {
Self::new(0., 0., 0.)
}
}
impl From<Tuple> for Point {
fn from(tuple: Tuple) -> Self {
assert_eq!(tuple.w, 1.0);
Self(tuple)
}
}
impl std::ops::Deref for Point {
type Target = Tuple;
fn deref(&self) -> &Tuple {
&self.0
}
}
impl std::ops::Add<Vector> for Point {
type Output = Point;
fn add(self, r: Vector) -> Self::Output {
Point::from(self.0 + *r)
}
}
impl std::ops::Sub for Point {
type Output = Vector;
fn sub(self, r: Point) -> Self::Output {
Vector::from(self.0 - r.0)
}
}
impl std::ops::Sub<Vector> for Point {
type Output = Point;
fn sub(self, r: Vector) -> Self::Output {
Point::from(self.0 - *r)
}
}
impl std::ops::Neg for Point {
type Output = Point;
fn neg(self) -> Self::Output {
let mut t = -self.0;
t.w = 1.;
Point::from(t)
}
}

View File

@ -0,0 +1,83 @@
use crate::types::eq_f64;
#[derive(Debug, Clone, Copy)]
pub struct Tuple {
pub x: f64,
pub y: f64,
pub z: f64,
pub w: f64, // Used for very low-level math. w = 1.0 indicates a point, w = 0.0 indicates a vector.
// Theoretically the type system should make this redundant, so operations on points
// and vectors can always assert the correct value.
}
impl Tuple {
pub fn dot(&self, r: &Tuple) -> f64 {
self.x * r.x + self.y * r.y + self.z * r.z + self.w * r.w
}
}
impl PartialEq for Tuple {
fn eq(&self, r: &Tuple) -> bool {
eq_f64(self.x, r.x) && eq_f64(self.y, r.y) && eq_f64(self.z, r.z) && eq_f64(self.w, r.w)
}
}
impl std::ops::Add for Tuple {
type Output = Tuple;
fn add(self, r: Tuple) -> Self::Output {
return Self::Output {
x: self.x + r.x,
y: self.y + r.y,
z: self.z + r.z,
w: self.w + r.w,
};
}
}
impl std::ops::Sub for Tuple {
type Output = Tuple;
fn sub(self, r: Tuple) -> Self::Output {
return Self::Output {
x: self.x - r.x,
y: self.y - r.y,
z: self.z - r.z,
w: self.w - r.w,
};
}
}
impl std::ops::Neg for Tuple {
type Output = Tuple;
fn neg(self) -> Self::Output {
return Self::Output {
x: -self.x,
y: -self.y,
z: -self.z,
w: -self.w,
};
}
}
impl std::ops::Mul<f64> for Tuple {
type Output = Tuple;
fn mul(self, scalar: f64) -> Self::Output {
return Self::Output {
x: self.x * scalar,
y: self.y * scalar,
z: self.z * scalar,
w: self.w * scalar,
};
}
}
impl std::ops::Div<f64> for Tuple {
type Output = Tuple;
fn div(self, scalar: f64) -> Self::Output {
return Self::Output {
x: self.x / scalar,
y: self.y / scalar,
z: self.z / scalar,
w: self.w / scalar,
};
}
}

View File

@ -0,0 +1,80 @@
use crate::types::Tuple;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Vector(Tuple);
impl Vector {
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self(Tuple { x, y, z, w: 0.0 })
}
pub fn magnitude(&self) -> f64 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
pub fn normalize(&self) -> Self {
let mag = self.magnitude();
Self::new(self.x / mag, self.y / mag, self.z / mag)
}
pub fn dot(&self, r: &Vector) -> f64 {
self.0.dot(r)
}
pub fn cross(&self, r: &Vector) -> Self {
let x = self.y * r.z - self.z * r.y;
let y = self.z * r.x - self.x * r.z;
let z = self.x * r.y - self.y * r.x;
Self::new(x, y, z)
}
}
impl Default for Vector {
fn default() -> Self {
Self::new(0., 0., 0.)
}
}
impl From<Tuple> for Vector {
fn from(tuple: Tuple) -> Self {
assert_eq!(tuple.w, 0.0);
Self(tuple)
}
}
impl std::ops::Deref for Vector {
type Target = Tuple;
fn deref(&self) -> &Tuple {
&self.0
}
}
impl std::ops::Add for Vector {
type Output = Vector;
fn add(self, r: Self) -> Self {
Vector::from(self.0 + r.0)
}
}
impl std::ops::Sub for Vector {
type Output = Vector;
fn sub(self, r: Self) -> Self {
Vector::from(self.0 - r.0)
}
}
impl std::ops::Neg for Vector {
type Output = Vector;
fn neg(self) -> Self::Output {
let mut t = -self.0;
t.w = 0.;
Vector::from(t)
}
}
impl std::ops::Mul<f64> for Vector {
type Output = Vector;
fn mul(self, r: f64) -> Self {
Vector::from(self.0 * r)
}
}