Set up lighting calculations
This commit is contained in:
parent
2fbb468830
commit
59dfaf1696
|
@ -7,7 +7,7 @@ const SIZE: usize = 1000;
|
||||||
fn main() {
|
fn main() {
|
||||||
let canvas = Arc::new(RwLock::new(Canvas::new(SIZE, SIZE)));
|
let canvas = Arc::new(RwLock::new(Canvas::new(SIZE, SIZE)));
|
||||||
let mut sphere = Sphere::default();
|
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 camera = Point::new(0., 1., -5.);
|
||||||
let wall_z = 10.;
|
let wall_z = 10.;
|
||||||
|
|
|
@ -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,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,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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
mod canvas;
|
mod canvas;
|
||||||
mod color;
|
mod color;
|
||||||
mod intersections;
|
mod intersections;
|
||||||
|
mod light;
|
||||||
|
mod material;
|
||||||
mod matrix;
|
mod matrix;
|
||||||
mod point;
|
mod point;
|
||||||
mod ray;
|
mod ray;
|
||||||
|
@ -11,6 +13,8 @@ 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 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;
|
||||||
|
|
|
@ -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 {
|
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 {
|
||||||
|
|
|
@ -172,7 +172,7 @@ mod tests {
|
||||||
fn intersect_scaled_sphere_with_ray() {
|
fn intersect_scaled_sphere_with_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 mut s = Sphere::default();
|
let mut s = Sphere::default();
|
||||||
s.set_transformation(scaling(2., 2., 2.));
|
*s.transformation_mut() = scaling(2., 2., 2.);
|
||||||
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, 3.);
|
assert_eq!(xs[0].t, 3.);
|
||||||
|
@ -183,7 +183,7 @@ mod tests {
|
||||||
fn intersect_translated_sphere_with_ray() {
|
fn intersect_translated_sphere_with_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 mut s = Sphere::default();
|
let mut s = Sphere::default();
|
||||||
s.set_transformation(translation(5., 0., 0.));
|
*s.transformation_mut() = translation(5., 0., 0.);
|
||||||
let xs = r.intersect(&s);
|
let xs = r.intersect(&s);
|
||||||
assert_eq!(xs.len(), 0);
|
assert_eq!(xs.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::types::{Matrix, Point, Vector};
|
use super::{Matrix, Point, Vector, Material };
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Sphere {
|
pub struct Sphere {
|
||||||
origin: Point,
|
origin: Point,
|
||||||
transformation: Matrix,
|
transformation: Matrix,
|
||||||
|
material: Material,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sphere {
|
impl Sphere {
|
||||||
|
@ -11,8 +12,16 @@ impl Sphere {
|
||||||
&self.transformation
|
&self.transformation
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_transformation(&mut self, m: Matrix) {
|
pub fn transformation_mut(&mut self) -> &mut Matrix{
|
||||||
self.transformation = m
|
&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 {
|
pub fn normal_at(&self, world_point: &Point) -> Vector {
|
||||||
|
@ -30,6 +39,7 @@ impl Default for Sphere {
|
||||||
Self {
|
Self {
|
||||||
origin: Point::new(0., 0., 0.),
|
origin: Point::new(0., 0., 0.),
|
||||||
transformation: Matrix::identity(),
|
transformation: Matrix::identity(),
|
||||||
|
material: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +59,7 @@ mod test {
|
||||||
fn change_a_spheres_transformation() {
|
fn change_a_spheres_transformation() {
|
||||||
let mut s = Sphere::default();
|
let mut s = Sphere::default();
|
||||||
let t = translation(2., 3., 4.);
|
let t = translation(2., 3., 4.);
|
||||||
s.set_transformation(t.clone());
|
*s.transformation_mut() = t.clone();
|
||||||
assert_eq!(*s.transformation(), t);
|
assert_eq!(*s.transformation(), t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +101,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn compute_normal_of_translated_sphere() {
|
fn compute_normal_of_translated_sphere() {
|
||||||
let mut s = Sphere::default();
|
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));
|
let n = s.normal_at(&Point::new(0., 1.70711, -0.70711));
|
||||||
assert_eq!(n, Vector::new(0., 0.70711, -0.70711));
|
assert_eq!(n, Vector::new(0., 0.70711, -0.70711));
|
||||||
}
|
}
|
||||||
|
@ -99,7 +109,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn compute_normal_of_transformed_sphere() {
|
fn compute_normal_of_transformed_sphere() {
|
||||||
let mut s = Sphere::default();
|
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.));
|
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));
|
assert_eq!(n, Vector::new(0., 0.97014, -0.24254));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue