127 lines
3.9 KiB
Rust
127 lines
3.9 KiB
Rust
use super::types::Color;
|
|
use super::types::Intersect;
|
|
use super::types::Light;
|
|
use super::types::Material;
|
|
use super::types::Point3;
|
|
use super::types::Ray;
|
|
use super::types::Vector3;
|
|
use super::vec::mirror;
|
|
use super::vec::reflect;
|
|
extern crate nalgebra as na;
|
|
|
|
/// A scene is a collection of objects and lights, and provides a method to trace a ray through the scene.
|
|
pub struct Scene {
|
|
/// The ambient light of the scene
|
|
ambient: Color,
|
|
/// The objects in the scene
|
|
objects: Vec<Box<dyn Intersect>>,
|
|
/// The lights in the scene
|
|
lights: Vec<Light>,
|
|
}
|
|
|
|
impl Scene {
|
|
/// Create a new empty scene
|
|
pub fn new() -> Scene {
|
|
Scene {
|
|
ambient: na::Vector3::new(0.0, 0.0, 0.0),
|
|
objects: Vec::new(),
|
|
lights: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Set the ambient light of the scene
|
|
pub fn set_ambient(&mut self, ambient: Color) {
|
|
self.ambient = ambient;
|
|
}
|
|
|
|
/// Add an object to the scene
|
|
pub fn add_object(&mut self, obj: Box<dyn Intersect>) {
|
|
self.objects.push(obj);
|
|
}
|
|
|
|
/// Add a light to the scene
|
|
pub fn add_light(&mut self, light: Light) {
|
|
self.lights.push(light);
|
|
}
|
|
|
|
/// Trace a ray through the scene and return the color of the ray.
|
|
/// - `ray` is the ray to be traced
|
|
/// - `depth` is the maximum recursion depth aka the number of reflections
|
|
pub fn trace(&self, ray: &Ray, depth: u32) -> Color {
|
|
if depth == 0 {
|
|
return na::Vector3::new(0.0, 0.0, 0.0);
|
|
}
|
|
|
|
match self
|
|
.objects
|
|
.iter()
|
|
.filter_map(|obj| obj.intersect(ray))
|
|
.min_by(|(_, _, t1, _), (_, _, t2, _)| t1.partial_cmp(t2).unwrap())
|
|
{
|
|
Some((isect_pt, isect_norm, _, material)) => {
|
|
// Lighting of material at the intersection point
|
|
let color = self.lighting(-&ray.direction, material, isect_pt, isect_norm);
|
|
|
|
// Calculate reflections, if the material has mirror properties
|
|
if material.mirror > 0.0 {
|
|
let new_ray = Ray {
|
|
origin: isect_pt,
|
|
direction: reflect(ray.direction, isect_norm),
|
|
};
|
|
return (1.0 - material.mirror) * color
|
|
+ material.mirror * self.trace(&new_ray, depth - 1);
|
|
} else {
|
|
return color;
|
|
}
|
|
}
|
|
_ => {
|
|
return na::Vector3::new(0.0, 0.0, 0.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Calculate Phong lighting from a `view` on a `material` at an intersection point `isect_pt` with a normal `isect_norm`.
|
|
fn lighting(
|
|
&self,
|
|
view: Vector3,
|
|
material: &Material,
|
|
isect_pt: Point3,
|
|
isect_norm: Vector3,
|
|
) -> Color {
|
|
// Start with ambient lighting
|
|
let mut color = material.ambient_color.component_mul(&self.ambient);
|
|
|
|
for light in &self.lights {
|
|
// Cast Shadow-Ray
|
|
let shadow_ray = Ray {
|
|
origin: isect_pt,
|
|
direction: (light.position - isect_pt).normalize(),
|
|
};
|
|
if self
|
|
.objects
|
|
.iter()
|
|
.any(|obj| obj.intersect(&shadow_ray).is_some())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Diffuse
|
|
let l = (light.position - isect_pt).normalize();
|
|
let cos_theta = l.dot(&isect_norm);
|
|
if cos_theta > 0.0 {
|
|
color += material.diffuse_color.component_mul(&light.color) * cos_theta;
|
|
|
|
// Specular
|
|
let r = mirror(l, isect_norm);
|
|
let cos_alpha = r.dot(&view);
|
|
if cos_alpha > 0.0 {
|
|
color += material.specular_color.component_mul(&light.color)
|
|
* cos_alpha.powf(material.shininess);
|
|
}
|
|
}
|
|
}
|
|
|
|
color
|
|
}
|
|
}
|