Set up lighting calculations

This commit is contained in:
Savanni D'Gerinel 2024-06-23 18:00:04 -04:00
parent 2fbb468830
commit 59dfaf1696
8 changed files with 167 additions and 10 deletions

View File

@ -7,7 +7,7 @@ const SIZE: usize = 1000;
fn main() {
let canvas = Arc::new(RwLock::new(Canvas::new(SIZE, SIZE)));
let mut sphere = Sphere::default();
sphere.set_transformation(shearing(1., 0., 0., 0., 0., 0.));
*sphere.transformation_mut() = shearing(1., 0., 0., 0., 0., 0.);
let camera = Point::new(0., 1., -5.);
let wall_z = 10.;

View File

@ -22,7 +22,6 @@ impl Color {
pub fn blue(&self) -> f64 {
self.0.2
}
}
impl Default for Color {

View File

@ -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 }
}
}

View File

@ -0,0 +1,118 @@
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: &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);
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);
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 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));
}
}

View File

@ -1,6 +1,8 @@
mod canvas;
mod color;
mod intersections;
mod light;
mod material;
mod matrix;
mod point;
mod ray;
@ -11,6 +13,8 @@ mod vector;
pub use canvas::Canvas;
pub use color::Color;
pub use intersections::{Intersections, Intersection};
pub use light::PointLight;
pub use material::Material;
pub use matrix::Matrix;
pub use point::Point;
pub use ray::Ray;

View File

@ -58,6 +58,20 @@ impl std::ops::Sub<&Point> for &Point {
}
}
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
}
}
impl std::ops::Sub<Point> for Point {
type Output = Vector;
fn sub(self, r: Point) -> Self::Output {

View File

@ -172,7 +172,7 @@ mod tests {
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.set_transformation(scaling(2., 2., 2.));
*s.transformation_mut() = scaling(2., 2., 2.);
let xs = r.intersect(&s);
assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, 3.);
@ -183,7 +183,7 @@ mod tests {
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.set_transformation(translation(5., 0., 0.));
*s.transformation_mut() = translation(5., 0., 0.);
let xs = r.intersect(&s);
assert_eq!(xs.len(), 0);
}

View File

@ -1,9 +1,10 @@
use crate::types::{Matrix, Point, Vector};
use super::{Matrix, Point, Vector, Material };
#[derive(Debug, PartialEq)]
pub struct Sphere {
origin: Point,
transformation: Matrix,
material: Material,
}
impl Sphere {
@ -11,8 +12,16 @@ impl Sphere {
&self.transformation
}
pub fn set_transformation(&mut self, m: Matrix) {
self.transformation = m
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 {
@ -30,6 +39,7 @@ impl Default for Sphere {
Self {
origin: Point::new(0., 0., 0.),
transformation: Matrix::identity(),
material: Default::default(),
}
}
}
@ -49,7 +59,7 @@ mod test {
fn change_a_spheres_transformation() {
let mut s = Sphere::default();
let t = translation(2., 3., 4.);
s.set_transformation(t.clone());
*s.transformation_mut() = t.clone();
assert_eq!(*s.transformation(), t);
}
@ -91,7 +101,7 @@ mod test {
#[test]
fn compute_normal_of_translated_sphere() {
let mut s = Sphere::default();
s.set_transformation(translation(0., 1., 0.));
*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));
}
@ -99,7 +109,7 @@ mod test {
#[test]
fn compute_normal_of_transformed_sphere() {
let mut s = Sphere::default();
s.set_transformation(scaling(1., 0.5, 1.) * rotation_z(std::f64::consts::PI / 5.));
*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));
}