Compare commits
11 Commits
8e4f6b06e6
...
f347e2e47d
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | f347e2e47d | |
Savanni D'Gerinel | 324d37f858 | |
Savanni D'Gerinel | 59dfaf1696 | |
Savanni D'Gerinel | 2fbb468830 | |
Savanni D'Gerinel | fa4ec059f7 | |
Savanni D'Gerinel | 4f47d65ba5 | |
Savanni D'Gerinel | f15fa9dd48 | |
Savanni D'Gerinel | af75bc20c8 | |
Savanni D'Gerinel | b07925a2c3 | |
Savanni D'Gerinel | 40bfe6d74f | |
Savanni D'Gerinel | af7d8680a0 |
|
@ -3334,12 +3334,15 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ray-tracer"
|
name = "ray-tracer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"rayon",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon"
|
name = "rayon"
|
||||||
version = "1.8.0"
|
version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
|
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
"rayon-core",
|
"rayon-core",
|
||||||
|
@ -3347,9 +3350,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon-core"
|
name = "rayon-core"
|
||||||
version = "1.12.0"
|
version = "1.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
|
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-deque",
|
"crossbeam-deque",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
|
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
rayon = "1.10.0"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "projectile"
|
name = "projectile"
|
||||||
|
|
|
@ -46,7 +46,7 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ppm = PPM::from(canvas);
|
let ppm = PPM::from(&canvas);
|
||||||
|
|
||||||
let mut file = File::create("projectile.ppm").unwrap();
|
let mut file = File::create("projectile.ppm").unwrap();
|
||||||
let _ = file.write(ppm.as_bytes());
|
let _ = file.write(ppm.as_bytes());
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
use ray_tracer::{types::*, PPM};
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::Write,
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
};
|
||||||
|
|
||||||
|
const SIZE: usize = 100;
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
camera: &Point,
|
||||||
|
light: &PointLight,
|
||||||
|
sphere: &Sphere,
|
||||||
|
wall_z: f64,
|
||||||
|
wall_size: f64,
|
||||||
|
filename: &str,
|
||||||
|
) {
|
||||||
|
let canvas = Arc::new(RwLock::new(Canvas::new(SIZE, SIZE)));
|
||||||
|
|
||||||
|
let pixel_size = wall_size / SIZE as f64;
|
||||||
|
let half = wall_size / 2.;
|
||||||
|
|
||||||
|
let ray = Ray::new(camera.clone(), (Point::new(0., 0., wall_z) - camera).normalize());
|
||||||
|
let xs = ray.intersect(sphere);
|
||||||
|
match xs.hit() {
|
||||||
|
Some(hit) => {
|
||||||
|
let point = ray.position(hit.t);
|
||||||
|
let normal = hit.object.normal_at(&point);
|
||||||
|
let eye = -ray.direction;
|
||||||
|
let color = hit.object.material().lighting(&light, &point, &eye, &normal);
|
||||||
|
println!("{:?}", color);
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
(0..SIZE).into_par_iter().for_each(|x| {
|
||||||
|
(0..SIZE).into_par_iter().for_each(|y| {
|
||||||
|
let world_x = -half + pixel_size * x as f64;
|
||||||
|
let world_y = half - pixel_size * y as f64;
|
||||||
|
let position = Point::new(world_x, world_y, wall_z);
|
||||||
|
|
||||||
|
let ray = Ray::new(camera.clone(), (position - camera).normalize());
|
||||||
|
let xs = ray.intersect(sphere);
|
||||||
|
match xs.hit() {
|
||||||
|
Some(hit) => {
|
||||||
|
let point = ray.position(hit.t);
|
||||||
|
let normal = hit.object.normal_at(&point);
|
||||||
|
let eye = -ray.direction;
|
||||||
|
let color = hit
|
||||||
|
.object
|
||||||
|
.material()
|
||||||
|
.lighting(&light, &point, &eye, &normal);
|
||||||
|
*canvas.write().unwrap().pixel_mut(x, y) = color;
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let ppm = PPM::from(&*canvas.read().unwrap());
|
||||||
|
let mut file = File::create(filename).unwrap();
|
||||||
|
let _ = file.write(ppm.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut sphere = Sphere::default();
|
||||||
|
|
||||||
|
let mut material = Material::default();
|
||||||
|
material.color = Color::new(1., 0.2, 1.);
|
||||||
|
*sphere.material_mut() = material;
|
||||||
|
|
||||||
|
let camera = Point::new(0., 0., -5.);
|
||||||
|
let wall_z = 10.;
|
||||||
|
let wall_size = 7.;
|
||||||
|
|
||||||
|
{
|
||||||
|
let light = PointLight::new(Point::new(0., 0., -10.), Color::new(1., 1., 1.));
|
||||||
|
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_1.ppm");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let light = PointLight::new(Point::new(-1., 0., -10.), Color::new(1., 1., 1.));
|
||||||
|
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_2.ppm");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let light = PointLight::new(Point::new(1., 0., -10.), Color::new(1., 1., 1.));
|
||||||
|
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_3.ppm");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let light = PointLight::new(Point::new(0., -1., -10.), Color::new(1., 1., 1.));
|
||||||
|
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_4.ppm");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let light = PointLight::new(Point::new(0., 1., -10.), Color::new(1., 1., 1.));
|
||||||
|
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_5.ppm");
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,8 +29,8 @@ fn join_to_line_limit(data: impl IntoIterator<Item = String>) -> Vec<String> {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PPM(String);
|
pub struct PPM(String);
|
||||||
|
|
||||||
impl From<Canvas> for PPM {
|
impl From<&Canvas> for PPM {
|
||||||
fn from(c: Canvas) -> Self {
|
fn from(c: &Canvas) -> Self {
|
||||||
// let v = vec![0.; c.width() * c.height() * 3];
|
// let v = vec![0.; c.width() * c.height() * 3];
|
||||||
let header = format!("P3\n{} {}\n255\n", c.width(), c.height());
|
let header = format!("P3\n{} {}\n255\n", c.width(), c.height());
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn construct_ppm_header() {
|
fn construct_ppm_header() {
|
||||||
let c = Canvas::new(5, 3);
|
let c = Canvas::new(5, 3);
|
||||||
let ppm = PPM::from(c);
|
let ppm = PPM::from(&c);
|
||||||
|
|
||||||
assert!(ppm.starts_with(
|
assert!(ppm.starts_with(
|
||||||
"P3\n\
|
"P3\n\
|
||||||
|
@ -91,7 +91,7 @@ mod tests {
|
||||||
0 0 0 0 0 0 0 128 0 0 0 0 0 0 0\n\
|
0 0 0 0 0 0 0 128 0 0 0 0 0 0 0\n\
|
||||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 255\n";
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 255\n";
|
||||||
|
|
||||||
let ppm = PPM::from(c);
|
let ppm = PPM::from(&c);
|
||||||
assert_eq!(*ppm, expected);
|
assert_eq!(*ppm, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ mod tests {
|
||||||
255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204\n\
|
255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204\n\
|
||||||
153 255 204 153 255 204 153 255 204 153 255 204 153\n";
|
153 255 204 153 255 204 153 255 204 153 255 204 153\n";
|
||||||
|
|
||||||
let ppm = PPM::from(c);
|
let ppm = PPM::from(&c);
|
||||||
assert_eq!(*ppm, expected);
|
assert_eq!(*ppm, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ mod tests {
|
||||||
fn multiply_by_translation_matrix() {
|
fn multiply_by_translation_matrix() {
|
||||||
let tr = translation(5., -3., 2.);
|
let tr = translation(5., -3., 2.);
|
||||||
let p = Point::new(-3., 4., 5.);
|
let p = Point::new(-3., 4., 5.);
|
||||||
assert_eq!(tr.clone() * p, Point::new(2., 1., 7.));
|
assert_eq!(&tr * p, Point::new(2., 1., 7.));
|
||||||
|
|
||||||
let inv = tr.inverse();
|
let inv = tr.inverse();
|
||||||
assert_eq!(inv * p, Point::new(-8., 7., 3.));
|
assert_eq!(inv * p, Point::new(-8., 7., 3.));
|
||||||
|
@ -81,7 +81,7 @@ mod tests {
|
||||||
fn translation_does_not_affect_vectors() {
|
fn translation_does_not_affect_vectors() {
|
||||||
let tr = translation(5., -3., 2.);
|
let tr = translation(5., -3., 2.);
|
||||||
let v = Vector::new(-3., 4., 5.);
|
let v = Vector::new(-3., 4., 5.);
|
||||||
assert_eq!(tr.clone() * v, v);
|
assert_eq!(tr * v, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -90,7 +90,7 @@ mod tests {
|
||||||
let p = Point::new(-4., 6., 8.);
|
let p = Point::new(-4., 6., 8.);
|
||||||
let v = Vector::new(-4., 6., 8.);
|
let v = Vector::new(-4., 6., 8.);
|
||||||
|
|
||||||
assert_eq!(tr.clone() * p, Point::new(-8., 18., 32.));
|
assert_eq!(&tr * p, Point::new(-8., 18., 32.));
|
||||||
assert_eq!(tr * v, Vector::new(-8., 18., 32.));
|
assert_eq!(tr * v, Vector::new(-8., 18., 32.));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ impl Color {
|
||||||
pub fn blue(&self) -> f64 {
|
pub fn blue(&self) -> f64 {
|
||||||
self.0.2
|
self.0.2
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Color {
|
impl Default for Color {
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use super::Sphere;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Intersection<'a> {
|
||||||
|
pub t: f64,
|
||||||
|
pub object: &'a Sphere,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Intersections<'a>(Vec<Intersection<'a>>);
|
||||||
|
|
||||||
|
impl<'a> Intersections<'a> {
|
||||||
|
pub fn is_empty(&'a self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&'a self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hit(&'a self) -> Option<&Intersection<'a>> {
|
||||||
|
self.0.iter().find(|i| i.t >= 0.)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::ops::Index<usize> for Intersections<'a> {
|
||||||
|
type Output = Intersection<'a>;
|
||||||
|
fn index(&self, idx: usize) -> &Intersection<'a> {
|
||||||
|
&self.0[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<Vec<Intersection<'a>>> for Intersections<'a> {
|
||||||
|
fn from(mut v: Vec<Intersection<'a>>) -> Self {
|
||||||
|
v.sort_by(|l, r| l.t.partial_cmp(&r.t).unwrap_or(Ordering::Equal));
|
||||||
|
Self(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
use super::{Color, Point};
|
||||||
|
|
||||||
|
pub struct PointLight {
|
||||||
|
pub position: Point,
|
||||||
|
pub intensity: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointLight {
|
||||||
|
pub fn new(position: Point, intensity: Color) -> Self {
|
||||||
|
Self { position, intensity }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
use super::{Color, Point, PointLight, Vector};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Material {
|
||||||
|
pub color: Color,
|
||||||
|
pub ambient: f64,
|
||||||
|
pub diffuse: f64,
|
||||||
|
pub specular: f64,
|
||||||
|
pub shininess: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Material {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
color: Color::new(1., 1., 1.),
|
||||||
|
ambient: 0.1,
|
||||||
|
diffuse: 0.9,
|
||||||
|
specular: 0.9,
|
||||||
|
shininess: 200.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Material {
|
||||||
|
pub fn lighting(&self, light: &PointLight, position: &Point, eye: &Vector, normal_v: &Vector) -> Color {
|
||||||
|
let effective_color = self.color * light.intensity;
|
||||||
|
|
||||||
|
let light_v = (light.position - position).normalize();
|
||||||
|
let ambient = effective_color * self.ambient;
|
||||||
|
|
||||||
|
let light_dot_normal = light_v.dot(&normal_v);
|
||||||
|
let (diffuse, specular) = if light_dot_normal < 0. {
|
||||||
|
(Color::new(0., 0., 0.), Color::new(0., 0., 0.))
|
||||||
|
} else {
|
||||||
|
let diffuse = effective_color * self.diffuse * light_dot_normal;
|
||||||
|
|
||||||
|
let reflect_v = (-light_v).reflect(&normal_v);
|
||||||
|
let reflect_dot_eye = reflect_v.dot(&eye);
|
||||||
|
|
||||||
|
let specular = if reflect_dot_eye <= 0. {
|
||||||
|
Color::new(0., 0., 0.)
|
||||||
|
} else {
|
||||||
|
let factor = reflect_dot_eye.powf(self.shininess);
|
||||||
|
light.intensity * self.specular * factor
|
||||||
|
};
|
||||||
|
|
||||||
|
(diffuse, specular)
|
||||||
|
};
|
||||||
|
|
||||||
|
ambient + diffuse + specular
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::types::{Color, Point, PointLight, Vector};
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lighting_with_eye_between_light_and_surface() {
|
||||||
|
let m = Material::default();
|
||||||
|
let position = Point::default();
|
||||||
|
|
||||||
|
let eyev = Vector::new(0., 0., -1.);
|
||||||
|
let normalv = Vector::new(0., 0., -1.);
|
||||||
|
let light = PointLight::new(Point::new(0., 0., -10.), Color::new(1., 1., 1.));
|
||||||
|
|
||||||
|
assert_eq!(m.lighting(&light, &position, &eyev, &normalv), Color::new(1.9, 1.9, 1.9));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lighting_with_eye_between_light_and_surface_eye_offset_45() {
|
||||||
|
let m = Material::default();
|
||||||
|
let position = Point::default();
|
||||||
|
|
||||||
|
let eyev = Vector::new(0., 2_f64.sqrt() / 2., -2_f64.sqrt() / 2.);
|
||||||
|
let normalv = Vector::new(0., 0., -1.);
|
||||||
|
let light = PointLight::new(Point::new(0., 0., -10.), Color::new(1., 1., 1.));
|
||||||
|
|
||||||
|
assert_eq!(m.lighting(&light, &position, &eyev, &normalv), Color::new(1., 1., 1.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lighting_with_eye_between_light_and_surface_light_offset_45() {
|
||||||
|
let m = Material::default();
|
||||||
|
let position = Point::default();
|
||||||
|
|
||||||
|
let eyev = Vector::new(0., 0., -1.);
|
||||||
|
let normalv = Vector::new(0., 0., -1.);
|
||||||
|
let light = PointLight::new(Point::new(0., 10., -10.), Color::new(1., 1., 1.));
|
||||||
|
|
||||||
|
assert_eq!(m.lighting(&light, &position, &eyev, &normalv), Color::new(0.7364, 0.7364, 0.7364));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lighting_eye_in_path_of_reflection() {
|
||||||
|
let m = Material::default();
|
||||||
|
let position = Point::default();
|
||||||
|
|
||||||
|
// let eyev = Vector::new(0., -2_f64.sqrt() / 2., -2_f64.sqrt() / 2.);
|
||||||
|
let eyev = Vector::new(0., -10., -10.).normalize();
|
||||||
|
let normalv = Vector::new(0., 0., -1.);
|
||||||
|
let light = PointLight::new(Point::new(0., 10., -10.), Color::new(1., 1., 1.));
|
||||||
|
|
||||||
|
assert_eq!(m.lighting(&light, &position, &eyev, &normalv), Color::new(1.6364, 1.6364, 1.6364));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lighting_with_light_behind_surface() {
|
||||||
|
let m = Material::default();
|
||||||
|
let position = Point::default();
|
||||||
|
|
||||||
|
let eyev = Vector::new(0., 0., -1.);
|
||||||
|
let normalv = Vector::new(0., 0., -1.);
|
||||||
|
let light = PointLight::new(Point::new(0., 0., 10.), Color::new(1., 1., 1.));
|
||||||
|
|
||||||
|
assert_eq!(m.lighting(&light, &position, &eyev, &normalv), Color::new(0.1, 0.1, 0.1));
|
||||||
|
}
|
||||||
|
}
|
|
@ -169,9 +169,9 @@ impl PartialEq for Matrix {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Mul for Matrix {
|
impl std::ops::Mul<&Matrix> for &Matrix {
|
||||||
type Output = Matrix;
|
type Output = Matrix;
|
||||||
fn mul(self, rside: Matrix) -> Matrix {
|
fn mul(self, rside: &Matrix) -> Matrix {
|
||||||
assert_eq!(self.size, 4);
|
assert_eq!(self.size, 4);
|
||||||
assert_eq!(rside.size, 4);
|
assert_eq!(rside.size, 4);
|
||||||
|
|
||||||
|
@ -189,7 +189,28 @@ impl std::ops::Mul for Matrix {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Mul<Tuple> for Matrix {
|
impl std::ops::Mul<Matrix> for &Matrix {
|
||||||
|
type Output = Matrix;
|
||||||
|
fn mul(self, rside: Matrix) -> Matrix {
|
||||||
|
self * &rside
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<&Matrix> for Matrix {
|
||||||
|
type Output = Matrix;
|
||||||
|
fn mul(self, rside: &Matrix) -> Matrix {
|
||||||
|
&self * rside
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul for Matrix {
|
||||||
|
type Output = Matrix;
|
||||||
|
fn mul(self, rside: Matrix) -> Matrix {
|
||||||
|
&self * &rside
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<Tuple> for &Matrix {
|
||||||
type Output = Tuple;
|
type Output = Tuple;
|
||||||
fn mul(self, rside: Tuple) -> Tuple {
|
fn mul(self, rside: Tuple) -> Tuple {
|
||||||
assert_eq!(self.size, 4);
|
assert_eq!(self.size, 4);
|
||||||
|
@ -207,10 +228,39 @@ impl std::ops::Mul<Tuple> for Matrix {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<Tuple> for Matrix {
|
||||||
|
type Output = Tuple;
|
||||||
|
fn mul(self, rside: Tuple) -> Tuple {
|
||||||
|
&self * rside
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<&Point> for &Matrix {
|
||||||
|
type Output = Point;
|
||||||
|
fn mul(self, rside: &Point) -> Point {
|
||||||
|
let t: Tuple = **rside;
|
||||||
|
Point::from(self * t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<Point> for &Matrix {
|
||||||
|
type Output = Point;
|
||||||
|
fn mul(self, rside: Point) -> Point {
|
||||||
|
self * &rside
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<&Point> for Matrix {
|
||||||
|
type Output = Point;
|
||||||
|
fn mul(self, rside: &Point) -> Point {
|
||||||
|
&self * rside
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::ops::Mul<Point> for Matrix {
|
impl std::ops::Mul<Point> for Matrix {
|
||||||
type Output = Point;
|
type Output = Point;
|
||||||
fn mul(self, rside: Point) -> Point {
|
fn mul(self, rside: Point) -> Point {
|
||||||
Point::from(self * *rside)
|
&self * rside
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,7 +375,7 @@ mod tests {
|
||||||
[4., 8., 16., 32.],
|
[4., 8., 16., 32.],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert_eq!(a.clone() * Matrix::identity(), a);
|
assert_eq!(&a * Matrix::identity(), a);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -488,7 +538,7 @@ mod tests {
|
||||||
[7., 0., 5., 4.],
|
[7., 0., 5., 4.],
|
||||||
[6., -2., 0., 5.],
|
[6., -2., 0., 5.],
|
||||||
]);
|
]);
|
||||||
let c = a.clone() * b.clone();
|
let c = &a * &b;
|
||||||
assert_eq!(c * b.inverse(), a);
|
assert_eq!(c * b.inverse(), a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
mod canvas;
|
mod canvas;
|
||||||
mod color;
|
mod color;
|
||||||
|
mod intersections;
|
||||||
|
mod light;
|
||||||
|
mod material;
|
||||||
mod matrix;
|
mod matrix;
|
||||||
mod point;
|
mod point;
|
||||||
mod ray;
|
mod ray;
|
||||||
|
@ -9,6 +12,9 @@ mod vector;
|
||||||
|
|
||||||
pub use canvas::Canvas;
|
pub use canvas::Canvas;
|
||||||
pub use color::Color;
|
pub use color::Color;
|
||||||
|
pub use intersections::{Intersections, Intersection};
|
||||||
|
pub use light::PointLight;
|
||||||
|
pub use material::Material;
|
||||||
pub use matrix::Matrix;
|
pub use matrix::Matrix;
|
||||||
pub use point::Point;
|
pub use point::Point;
|
||||||
pub use ray::Ray;
|
pub use ray::Ray;
|
||||||
|
@ -154,4 +160,26 @@ mod tests {
|
||||||
*c.pixel_mut(2, 3) = red;
|
*c.pixel_mut(2, 3) = red;
|
||||||
assert_eq!(*c.pixel(2, 3), red);
|
assert_eq!(*c.pixel(2, 3), red);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reflect_vector_approaching_at_45() {
|
||||||
|
let v = Vector::new(1., -1., 0.);
|
||||||
|
let n = Vector::new(0., 1., 0.);
|
||||||
|
assert_eq!(v.reflect(&n), Vector::new(1., 1., 0.));
|
||||||
|
|
||||||
|
let v = Vector::new(-1., -1., 0.);
|
||||||
|
let n = Vector::new(1., 0., 0.);
|
||||||
|
assert_eq!(v.reflect(&n), Vector::new(1., -1., 0.));
|
||||||
|
|
||||||
|
let v = Vector::new(1., 0., -1.);
|
||||||
|
let n = Vector::new(0., 0., 1.);
|
||||||
|
assert_eq!(v.reflect(&n), Vector::new(1., 0., 1.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reflect_off_slanted_surface() {
|
||||||
|
let v = Vector::new(0., -1., 0.);
|
||||||
|
let n = Vector::new(2_f64.sqrt() / 2., 2_f64.sqrt() / 2., 0.);
|
||||||
|
assert_eq!(v.reflect(&n), Vector::new(1., 0., 0.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,10 +51,31 @@ impl std::ops::Add<Vector> for Point {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Sub for Point {
|
impl std::ops::Sub<&Point> for &Point {
|
||||||
|
type Output = Vector;
|
||||||
|
fn sub(self, r: &Point) -> Self::Output {
|
||||||
|
Vector::from(self.0 - r.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub<Point> for &Point {
|
||||||
type Output = Vector;
|
type Output = Vector;
|
||||||
fn sub(self, r: Point) -> Self::Output {
|
fn sub(self, r: Point) -> Self::Output {
|
||||||
Vector::from(self.0 - r.0)
|
self - &r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub<&Point> for Point {
|
||||||
|
type Output = Vector;
|
||||||
|
fn sub(self, r: &Point) -> Self::Output {
|
||||||
|
&self - r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub<Point> for Point {
|
||||||
|
type Output = Vector;
|
||||||
|
fn sub(self, r: Point) -> Self::Output {
|
||||||
|
&self - &r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,42 +1,9 @@
|
||||||
use std::cmp::Ordering;
|
use super::{Intersection, Intersections, Matrix, Point, Sphere, Vector};
|
||||||
|
|
||||||
use crate::types::{Point, Sphere, Vector};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct Intersection<'a> {
|
|
||||||
t: f64,
|
|
||||||
object: &'a Sphere,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Intersections<'a>(Vec<Intersection<'a>>);
|
|
||||||
|
|
||||||
impl <'a> Intersections<'a> {
|
|
||||||
pub fn len(&'a self) -> usize {
|
|
||||||
self.0.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hit(&'a self) -> Option<&Intersection<'a>> {
|
|
||||||
self.0.iter().find(|i| i.t >= 0.)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <'a> std::ops::Index<usize> for Intersections<'a> {
|
|
||||||
type Output = Intersection<'a>;
|
|
||||||
fn index(&self, idx: usize) -> &Intersection<'a> {
|
|
||||||
&self.0[idx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <'a> From<Vec<Intersection<'a>>> for Intersections<'a> {
|
|
||||||
fn from(mut v: Vec<Intersection<'a>>) -> Self {
|
|
||||||
v.sort_by(|l, r| l.t.partial_cmp(&r.t).unwrap_or(Ordering::Equal));
|
|
||||||
Self(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Ray {
|
pub struct Ray {
|
||||||
origin: Point,
|
origin: Point,
|
||||||
direction: Vector,
|
pub direction: Vector,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ray {
|
impl Ray {
|
||||||
|
@ -49,9 +16,10 @@ impl Ray {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn intersect<'a>(&self, s: &'a Sphere) -> Intersections<'a> {
|
pub fn intersect<'a>(&self, s: &'a Sphere) -> Intersections<'a> {
|
||||||
let sphere_to_ray = self.origin - Point::new(0., 0., 0.);
|
let r2 = self.transform(s.transformation().inverse());
|
||||||
let a = self.direction.dot(&self.direction);
|
let sphere_to_ray = r2.origin - Point::new(0., 0., 0.);
|
||||||
let b = 2. * self.direction.dot(&sphere_to_ray);
|
let a = r2.direction.dot(&r2.direction);
|
||||||
|
let b = 2. * r2.direction.dot(&sphere_to_ray);
|
||||||
let c = sphere_to_ray.dot(&sphere_to_ray) - 1.;
|
let c = sphere_to_ray.dot(&sphere_to_ray) - 1.;
|
||||||
|
|
||||||
let discriminant = b * b - 4. * a * c;
|
let discriminant = b * b - 4. * a * c;
|
||||||
|
@ -63,15 +31,24 @@ impl Ray {
|
||||||
let t2 = (-b + discriminant.sqrt()) / (2. * a);
|
let t2 = (-b + discriminant.sqrt()) / (2. * a);
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
Intersection { t: t1, object: &s },
|
Intersection { t: t1, object: s },
|
||||||
Intersection { t: t2, object: &s },
|
Intersection { t: t2, object: s },
|
||||||
].into()
|
]
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform(&self, m: Matrix) -> Self {
|
||||||
|
Self {
|
||||||
|
origin: &m * self.origin,
|
||||||
|
direction: m * self.direction,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::transforms::{scaling, translation};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn computing_point_from_distance() {
|
fn computing_point_from_distance() {
|
||||||
|
@ -85,7 +62,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn ray_intersects_sphere_at_two_points() {
|
fn ray_intersects_sphere_at_two_points() {
|
||||||
let r = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.));
|
let r = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.));
|
||||||
let s = Sphere::new();
|
let s = Sphere::default();
|
||||||
let xs = r.intersect(&s);
|
let xs = r.intersect(&s);
|
||||||
assert_eq!(xs.len(), 2);
|
assert_eq!(xs.len(), 2);
|
||||||
assert_eq!(xs[0].t, 4.);
|
assert_eq!(xs[0].t, 4.);
|
||||||
|
@ -97,7 +74,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn ray_tangents_sphere_at_one_point() {
|
fn ray_tangents_sphere_at_one_point() {
|
||||||
let r = Ray::new(Point::new(0., 1., -5.), Vector::new(0., 0., 1.));
|
let r = Ray::new(Point::new(0., 1., -5.), Vector::new(0., 0., 1.));
|
||||||
let s = Sphere::new();
|
let s = Sphere::default();
|
||||||
let xs = r.intersect(&s);
|
let xs = r.intersect(&s);
|
||||||
assert_eq!(xs.len(), 2);
|
assert_eq!(xs.len(), 2);
|
||||||
assert_eq!(xs[0].t, 5.);
|
assert_eq!(xs[0].t, 5.);
|
||||||
|
@ -107,7 +84,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn ray_misses_the_sphere() {
|
fn ray_misses_the_sphere() {
|
||||||
let r = Ray::new(Point::new(0., 2., -5.), Vector::new(0., 0., 1.));
|
let r = Ray::new(Point::new(0., 2., -5.), Vector::new(0., 0., 1.));
|
||||||
let s = Sphere::new();
|
let s = Sphere::default();
|
||||||
let xs = r.intersect(&s);
|
let xs = r.intersect(&s);
|
||||||
assert_eq!(xs.len(), 0);
|
assert_eq!(xs.len(), 0);
|
||||||
}
|
}
|
||||||
|
@ -115,7 +92,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn ray_originates_inside_the_sphere() {
|
fn ray_originates_inside_the_sphere() {
|
||||||
let r = Ray::new(Point::new(0., 0., 0.), Vector::new(0., 0., 1.));
|
let r = Ray::new(Point::new(0., 0., 0.), Vector::new(0., 0., 1.));
|
||||||
let s = Sphere::new();
|
let s = Sphere::default();
|
||||||
let xs = r.intersect(&s);
|
let xs = r.intersect(&s);
|
||||||
assert_eq!(xs.len(), 2);
|
assert_eq!(xs.len(), 2);
|
||||||
assert_eq!(xs[0].t, -1.);
|
assert_eq!(xs[0].t, -1.);
|
||||||
|
@ -125,7 +102,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn sphere_is_behind_the_ray() {
|
fn sphere_is_behind_the_ray() {
|
||||||
let r = Ray::new(Point::new(0., 0., 5.), Vector::new(0., 0., 1.));
|
let r = Ray::new(Point::new(0., 0., 5.), Vector::new(0., 0., 1.));
|
||||||
let s = Sphere::new();
|
let s = Sphere::default();
|
||||||
let xs = r.intersect(&s);
|
let xs = r.intersect(&s);
|
||||||
assert_eq!(xs.len(), 2);
|
assert_eq!(xs.len(), 2);
|
||||||
assert_eq!(xs[0].t, -6.);
|
assert_eq!(xs[0].t, -6.);
|
||||||
|
@ -134,19 +111,19 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hit_all_intersections_are_positive() {
|
fn hit_all_intersections_are_positive() {
|
||||||
let s = Sphere::new();
|
let s = Sphere::default();
|
||||||
let i1 = Intersection{ t: 1., object: &s };
|
let i1 = Intersection { t: 1., object: &s };
|
||||||
let i2 = Intersection{ t: 2., object: &s };
|
let i2 = Intersection { t: 2., object: &s };
|
||||||
let xs = Intersections::from(vec![i1.clone(), i2]);
|
let xs = Intersections::from(vec![i1.clone(), i2.clone()]);
|
||||||
|
|
||||||
assert_eq!(xs.hit(), Some(&i1));
|
assert_eq!(xs.hit(), Some(&i1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hit_some_intersections_are_negative() {
|
fn hit_some_intersections_are_negative() {
|
||||||
let s = Sphere::new();
|
let s = Sphere::default();
|
||||||
let i1 = Intersection{ t: -1., object: &s };
|
let i1 = Intersection { t: -1., object: &s };
|
||||||
let i2 = Intersection{ t: 1., object: &s };
|
let i2 = Intersection { t: 1., object: &s };
|
||||||
let xs = Intersections::from(vec![i1, i2.clone()]);
|
let xs = Intersections::from(vec![i1, i2.clone()]);
|
||||||
|
|
||||||
assert_eq!(xs.hit(), Some(&i2));
|
assert_eq!(xs.hit(), Some(&i2));
|
||||||
|
@ -154,9 +131,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hit_all_intersections_are_negative() {
|
fn hit_all_intersections_are_negative() {
|
||||||
let s = Sphere::new();
|
let s = Sphere::default();
|
||||||
let i1 = Intersection{ t: -2., object: &s };
|
let i1 = Intersection { t: -2., object: &s };
|
||||||
let i2 = Intersection{ t: -1., object: &s };
|
let i2 = Intersection { t: -1., object: &s };
|
||||||
let xs = Intersections::from(vec![i1, i2]);
|
let xs = Intersections::from(vec![i1, i2]);
|
||||||
|
|
||||||
assert_eq!(xs.hit(), None);
|
assert_eq!(xs.hit(), None);
|
||||||
|
@ -164,13 +141,51 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hit_is_always_lowest_nonnegative() {
|
fn hit_is_always_lowest_nonnegative() {
|
||||||
let s = Sphere::new();
|
let s = Sphere::default();
|
||||||
let i1 = Intersection{ t: 5., object: &s };
|
let i1 = Intersection { t: 5., object: &s };
|
||||||
let i2 = Intersection{ t: 7., object: &s };
|
let i2 = Intersection { t: 7., object: &s };
|
||||||
let i3 = Intersection{ t: -3., object: &s };
|
let i3 = Intersection { t: -3., object: &s };
|
||||||
let i4 = Intersection{ t: 2., object: &s };
|
let i4 = Intersection { t: 2., object: &s };
|
||||||
let xs = Intersections::from(vec![i1, i2, i3, i4.clone()]);
|
let xs = Intersections::from(vec![i1, i2, i3, i4.clone()]);
|
||||||
|
|
||||||
assert_eq!(xs.hit(), Some(&i4));
|
assert_eq!(xs.hit(), Some(&i4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn translate_a_ray() {
|
||||||
|
let r = Ray::new(Point::new(1., 2., 3.), Vector::new(0., 1., 0.));
|
||||||
|
let m = translation(3., 4., 5.);
|
||||||
|
let r2 = r.transform(m);
|
||||||
|
assert_eq!(r2.origin, Point::new(4., 6., 8.));
|
||||||
|
assert_eq!(r2.direction, Vector::new(0., 1., 0.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scale_a_ray() {
|
||||||
|
let r = Ray::new(Point::new(1., 2., 3.), Vector::new(0., 1., 0.));
|
||||||
|
let m = scaling(2., 3., 4.);
|
||||||
|
let r2 = r.transform(m);
|
||||||
|
assert_eq!(r2.origin, Point::new(2., 6., 12.));
|
||||||
|
assert_eq!(r2.direction, Vector::new(0., 3., 0.,));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn intersect_scaled_sphere_with_ray() {
|
||||||
|
let r = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.));
|
||||||
|
let mut s = Sphere::default();
|
||||||
|
*s.transformation_mut() = scaling(2., 2., 2.);
|
||||||
|
let xs = r.intersect(&s);
|
||||||
|
assert_eq!(xs.len(), 2);
|
||||||
|
assert_eq!(xs[0].t, 3.);
|
||||||
|
assert_eq!(xs[1].t, 7.);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn intersect_translated_sphere_with_ray() {
|
||||||
|
let r = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.));
|
||||||
|
let mut s = Sphere::default();
|
||||||
|
*s.transformation_mut() = translation(5., 0., 0.);
|
||||||
|
let xs = r.intersect(&s);
|
||||||
|
assert_eq!(xs.len(), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,116 @@
|
||||||
use crate::types::Point;
|
use super::{Matrix, Point, Vector, Material };
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Sphere {
|
pub struct Sphere {
|
||||||
origin: Point,
|
origin: Point,
|
||||||
|
transformation: Matrix,
|
||||||
|
material: Material,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sphere {
|
impl Sphere {
|
||||||
pub fn new() -> Self {
|
pub fn transformation(&self) -> &Matrix {
|
||||||
Self{ origin: Point::new(0., 0., 0.) }
|
&self.transformation
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transformation_mut(&mut self) -> &mut Matrix{
|
||||||
|
&mut self.transformation
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn material(&self) -> &Material {
|
||||||
|
&self.material
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn material_mut(&mut self) -> &mut Material {
|
||||||
|
&mut self.material
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normal_at(&self, world_point: &Point) -> Vector {
|
||||||
|
let inverted_transform = self.transformation.inverse();
|
||||||
|
let object_point = &inverted_transform * world_point;
|
||||||
|
let object_normal = (object_point - Point::new(0., 0., 0.)).normalize();
|
||||||
|
let mut world_normal = inverted_transform.transpose() * *object_normal;
|
||||||
|
world_normal.3 = 0.;
|
||||||
|
Vector::from(world_normal).normalize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Sphere {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
origin: Point::new(0., 0., 0.),
|
||||||
|
transformation: Matrix::identity(),
|
||||||
|
material: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::transforms::{rotation_z, scaling, translation};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sphere_has_default_transformation() {
|
||||||
|
let s = Sphere::default();
|
||||||
|
assert_eq!(s.transformation(), &Matrix::identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn change_a_spheres_transformation() {
|
||||||
|
let mut s = Sphere::default();
|
||||||
|
let t = translation(2., 3., 4.);
|
||||||
|
*s.transformation_mut() = t.clone();
|
||||||
|
assert_eq!(*s.transformation(), t);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normal_of_sphere_on_x() {
|
||||||
|
let s = Sphere::default();
|
||||||
|
let n = s.normal_at(&Point::new(1., 0., 0.));
|
||||||
|
assert_eq!(n, Vector::new(1., 0., 0.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normal_of_sphere_on_y() {
|
||||||
|
let s = Sphere::default();
|
||||||
|
let n = s.normal_at(&Point::new(0., 1., 0.));
|
||||||
|
assert_eq!(n, Vector::new(0., 1., 0.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normal_of_sphere_on_z() {
|
||||||
|
let s = Sphere::default();
|
||||||
|
let n = s.normal_at(&Point::new(0., 0., 1.));
|
||||||
|
assert_eq!(n, Vector::new(0., 0., 1.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normal_of_sphere_on_nonaxial() {
|
||||||
|
let s = Sphere::default();
|
||||||
|
let n = s.normal_at(&Point::new(3_f64.sqrt() / 3., 3_f64.sqrt() / 3., 3_f64.sqrt() / 3.));
|
||||||
|
assert_eq!(n, Vector::new(3_f64.sqrt() / 3., 3_f64.sqrt() / 3., 3_f64.sqrt() / 3.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normal_is_normalized() {
|
||||||
|
let s = Sphere::default();
|
||||||
|
let n = s.normal_at(&Point::new(3_f64.sqrt() / 3., 3_f64.sqrt() / 3., 3_f64.sqrt() / 3.));
|
||||||
|
assert_eq!(n.normalize(), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compute_normal_of_translated_sphere() {
|
||||||
|
let mut s = Sphere::default();
|
||||||
|
*s.transformation_mut() = translation(0., 1., 0.);
|
||||||
|
let n = s.normal_at(&Point::new(0., 1.70711, -0.70711));
|
||||||
|
assert_eq!(n, Vector::new(0., 0.70711, -0.70711));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compute_normal_of_transformed_sphere() {
|
||||||
|
let mut s = Sphere::default();
|
||||||
|
*s.transformation_mut() = scaling(1., 0.5, 1.) * rotation_z(std::f64::consts::PI / 5.);
|
||||||
|
let n = s.normal_at(&Point::new(0., 2_f64.sqrt() / 2., -2_f64.sqrt() / 2.));
|
||||||
|
assert_eq!(n, Vector::new(0., 0.97014, -0.24254));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,28 +27,49 @@ impl PartialEq for Tuple {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Add for Tuple {
|
impl std::ops::Add<&Tuple> for &Tuple {
|
||||||
type Output = Tuple;
|
type Output = Tuple;
|
||||||
fn add(self, r: Tuple) -> Self::Output {
|
fn add(self, r: &Tuple) -> Self::Output {
|
||||||
Tuple(self.0 + r.0, self.1 + r.1, self.2 + r.2, self.3 + r.3)
|
Tuple(self.0 + r.0, self.1 + r.1, self.2 + r.2, self.3 + r.3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Sub for Tuple {
|
impl std::ops::Add<Tuple> for Tuple {
|
||||||
type Output = Tuple;
|
type Output = Tuple;
|
||||||
fn sub(self, r: Tuple) -> Self::Output {
|
fn add(self, r: Tuple) -> Self::Output {
|
||||||
|
&self + &r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub<&Tuple> for &Tuple {
|
||||||
|
type Output = Tuple;
|
||||||
|
fn sub(self, r: &Tuple) -> Self::Output {
|
||||||
Tuple(self.0 - r.0, self.1 - r.1, self.2 - r.2, self.3 - r.3)
|
Tuple(self.0 - r.0, self.1 - r.1, self.2 - r.2, self.3 - r.3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Neg for Tuple {
|
impl std::ops::Sub<Tuple> for Tuple {
|
||||||
|
type Output = Tuple;
|
||||||
|
fn sub(self, r: Tuple) -> Self::Output {
|
||||||
|
&self - &r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Neg for &Tuple {
|
||||||
type Output = Tuple;
|
type Output = Tuple;
|
||||||
fn neg(self) -> Self::Output {
|
fn neg(self) -> Self::Output {
|
||||||
Tuple(-self.0, -self.1, -self.2, -self.3)
|
Tuple(-self.0, -self.1, -self.2, -self.3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Mul<f64> for Tuple {
|
impl std::ops::Neg for Tuple {
|
||||||
|
type Output = Tuple;
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
-&self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<f64> for &Tuple {
|
||||||
type Output = Tuple;
|
type Output = Tuple;
|
||||||
fn mul(self, scalar: f64) -> Self::Output {
|
fn mul(self, scalar: f64) -> Self::Output {
|
||||||
Tuple(
|
Tuple(
|
||||||
|
@ -60,7 +81,15 @@ impl std::ops::Mul<f64> for Tuple {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Div<f64> for Tuple {
|
impl std::ops::Mul<f64> for Tuple {
|
||||||
|
type Output = Tuple;
|
||||||
|
#[allow(clippy::op_ref)]
|
||||||
|
fn mul(self, scalar: f64) -> Self::Output {
|
||||||
|
&self * scalar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Div<f64> for &Tuple {
|
||||||
type Output = Tuple;
|
type Output = Tuple;
|
||||||
fn div(self, scalar: f64) -> Self::Output {
|
fn div(self, scalar: f64) -> Self::Output {
|
||||||
Tuple(
|
Tuple(
|
||||||
|
@ -71,3 +100,11 @@ impl std::ops::Div<f64> for Tuple {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Div<f64> for Tuple {
|
||||||
|
type Output = Tuple;
|
||||||
|
#[allow(clippy::op_ref)]
|
||||||
|
fn div(self, scalar: f64) -> Self::Output {
|
||||||
|
&self / scalar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -42,6 +42,10 @@ impl Vector {
|
||||||
let z = self.x() * r.y() - self.y() * r.x();
|
let z = self.x() * r.y() - self.y() * r.x();
|
||||||
Self::new(x, y, z)
|
Self::new(x, y, z)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reflect(&self, n: &Vector) -> Self {
|
||||||
|
self - (n * 2. * self.dot(n))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Vector {
|
impl Default for Vector {
|
||||||
|
@ -66,15 +70,36 @@ impl std::ops::Deref for Vector {
|
||||||
|
|
||||||
impl std::ops::Add for Vector {
|
impl std::ops::Add for Vector {
|
||||||
type Output = Vector;
|
type Output = Vector;
|
||||||
fn add(self, r: Self) -> Self {
|
fn add(self, r: Self) -> Self::Output {
|
||||||
Vector::from(self.0 + r.0)
|
Vector::from(self.0 + r.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub<&Vector> for &Vector {
|
||||||
|
type Output = Vector;
|
||||||
|
fn sub(self, r: &Vector) -> Self::Output {
|
||||||
|
Vector::from(self.0 - r.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub<Vector> for &Vector {
|
||||||
|
type Output = Vector;
|
||||||
|
fn sub(self, r: Vector) -> Self::Output {
|
||||||
|
self - &r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub<&Vector> for Vector {
|
||||||
|
type Output = Vector;
|
||||||
|
fn sub(self, r: &Vector) -> Self::Output {
|
||||||
|
&self - r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::ops::Sub for Vector {
|
impl std::ops::Sub for Vector {
|
||||||
type Output = Vector;
|
type Output = Vector;
|
||||||
fn sub(self, r: Self) -> Self {
|
fn sub(self, r: Vector) -> Self::Output {
|
||||||
Vector::from(self.0 - r.0)
|
&self - &r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +112,13 @@ impl std::ops::Neg for Vector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<f64> for &Vector {
|
||||||
|
type Output = Vector;
|
||||||
|
fn mul(self, r: f64) -> Self::Output {
|
||||||
|
Vector::from(self.0 * r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::ops::Mul<f64> for Vector {
|
impl std::ops::Mul<f64> for Vector {
|
||||||
type Output = Vector;
|
type Output = Vector;
|
||||||
fn mul(self, r: f64) -> Self {
|
fn mul(self, r: f64) -> Self {
|
||||||
|
|
Loading…
Reference in New Issue