diff --git a/src/main.rs b/src/main.rs index 7501f36..160fd99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ extern crate rand; use std::cmp::max; +use std::cmp::min; use std::f32::consts::PI; use std::io::Cursor; use std::ops; @@ -38,6 +39,28 @@ struct Vector3f { x: f32, y: f32, z: f32 } #[derive(Clone, Copy, Debug, Default)] struct Vector4f { x: f32, y: f32, z: f32, w: f32 } +#[derive(Clone, Copy, Debug, Default)] +struct Lighting +{ + diffuse: Vector3f, + specular: Vector3f, +} + +#[derive(Clone, Copy, Debug, Default)] +struct PointLight +{ + position : Vector3f, + diffuseColor : Vector3f, + diffusePower : f32, + specularColor : Vector3f, + specularPower : f32, +} + +fn saturate(v: f32) -> f32 { + f32::min(f32::max(v, 0.0), 1.0) +} + + fn dot(vec_a: Vector3f, vec_b: Vector3f) -> f32 { vec_a.x * vec_b.x + vec_a.y * vec_b.y + vec_a.z * vec_b.z } @@ -50,6 +73,14 @@ fn mult(vec: Vector3f, scalar: f32) -> Vector3f { } } +fn div(vec: Vector3f, scalar: f32) -> Vector3f { + Vector3f { + x: vec.x / scalar, + y: vec.y / scalar, + z: vec.z / scalar, + } +} + fn sub(vec_a: Vector3f, vec_b: Vector3f) -> Vector3f { Vector3f { x: vec_a.x - vec_b.x, @@ -66,11 +97,23 @@ fn add(vec_a: Vector3f, vec_b: Vector3f) -> Vector3f { } } +fn pow(vec: Vector3f, scalar: f32) -> Vector3f { + Vector3f { + x: f32::powf(vec.x, scalar), + y: f32::powf(vec.y, scalar), + z: f32::powf(vec.z, scalar), + } +} + fn mix(a: Vector3f, b: Vector3f, mixValue: f32) -> Vector3f { add(mult(a, (1.0 - mixValue)), mult(b, mixValue)) } +fn len(vec: Vector3f) -> f32 { + (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z).sqrt() as f32 +} + fn normalize(ray: Vector3f) -> Vector3f { let multiplier = (ray.x * ray.x + ray.y * ray.y + ray.z * ray.z).sqrt(); Vector3f { @@ -115,20 +158,56 @@ fn solve_quadratic(a: f32, b: f32, c: f32, mut x0: f32, mut x1: f32) -> Option Vector2f +// Return the texture coordinates of the surface hit +fn get_surface_data(phit: Vector3f, sphere_center: Vector3f) -> (Vector2f, Vector3f) { let nhit = sub(phit, sphere_center); let nhit = normalize(nhit); -// In this particular case, the normal is simular to a point on a unit sphere -// centred around the origin. We can thus use the normal coordinates to compute -// the spherical coordinates of Phit. -// atan2 returns a value in the range [-pi, pi] and we need to remap it to range [0, 1] -// acosf returns a value in the range [0, pi] and we also need to remap it to the range [0, 1] - Vector2f { + // In this particular case, the normal is simular to a point on a unit sphere + // centred around the origin. We can thus use the normal coordinates to compute + // the spherical coordinates of Phit. + // atan2 returns a value in the range [-pi, pi] and we need to remap it to range [0, 1] + // acosf returns a value in the range [0, pi] and we also need to remap it to the range [0, 1] + (Vector2f { x: (1.0 + (nhit.x).atan2(nhit.z) / PI) * 0.5, y: (nhit.y).acos() / PI, + }, nhit) +} + + +fn get_point_light(light: PointLight, pos3D: Vector3f, viewDir: Vector3f, normal: Vector3f) -> Lighting +{ + let mut out = Lighting::default(); + if (light.diffusePower > 0.0) + { + let lightDir = sub(light.position, pos3D); //3D position in space of the surface + let distance = len(lightDir); + let lightDir = div(lightDir, distance); // = normalize(lightDir); + let distance = distance * distance; //This line may be optimised using Inverse square root + + //Intensity of the diffuse light. Saturate to keep within the 0-1 range. + let NdotL = dot(normal, lightDir); + let intensity = saturate(NdotL); + + // Calculate the diffuse light factoring in light color, power and the attenuation + out.diffuse = div(mult(mult(light.diffuseColor, intensity), light.diffusePower), distance); + + //Calculate the half vector between the light vector and the view vector. + // This is typically slower than calculating the actual reflection vector + // due to the normalize function's reciprocal square root + let H = normalize(add(lightDir, viewDir)); + + //Intensity of the specular light + let NdotH = dot(normal, H); + let intensity = f32::powf(saturate(NdotH), 1.0); + + //Sum up the specular light factoring + out.specular = div(mult(mult(light.specularColor, intensity), light.specularPower), distance); } + out } + + fn main() { let mut rng = rand::thread_rng(); @@ -194,10 +273,11 @@ fn main() { match intersect(orig, dir, sphere_center, radius) { None => {} Some(t) => { + // The point on the circle which we intersected let phit = add(orig, mult(dir, t)); - let nhit = Vector3f::default(); - let tex = get_surface_data(phit, nhit, sphere_center); + // The tex coord & normal at phit + let (tex, nhit) = get_surface_data(phit, sphere_center); let scale = 4.0; @@ -212,6 +292,7 @@ fn main() { z: -dir.z, }; + let hit_color = mult(mix(sphere_color, mult(sphere_color, 0.8), f32::max(0.0, dot(nhit, ndir))), pattern); *pixel = image::Rgb([hit_color.x as u8, hit_color.y as u8, hit_color.z as u8]); }