use crate::geom::{Matrix, Point, Rect, Vector};
use crate::scene::Light;
use crate::scene::Object;
use crate::spectrum::{wavelength_to_colour, FIRST_WAVELENGTH, LAST_WAVELENGTH};
use rand::prelude::*;
use rand_distr::num_traits::real::Real;
use std::f64::consts::PI;
pub struct RayResult {
pub origin: Point,
pub termination: Point,
pub wavelength: f64,
}
impl RayResult {
#[cfg(test)]
pub fn new<A, B>(start: A, end: B, wavelen: f64) -> Self
where
A: Into<Point>,
B: Into<Point>,
{
Self {
origin: start.into(),
termination: end.into(),
wavelength: wavelen,
}
}
pub fn color<P>(&self) -> (P, P, P)
where
P: Copy + Real,
{
wavelength_to_colour(self.wavelength)
}
}
pub struct Ray<R: Rng + SeedableRng> {
origin: Point,
direction: Vector,
wavelength: f64,
bounces: u32,
ray_rng: R,
}
impl<R> Ray<R>
where
R: Rng + SeedableRng,
{
pub fn new(light: &Light<R>, rng: &mut R) -> Self {
let p = light.location.get(rng);
let cart_x = p.x;
let cart_y = p.y;
let polar_angle = light.polar_angle.sample(rng) * (PI / 180.0);
let polar_dist = light.polar_distance.sample(rng);
let origin = Point {
x: cart_x + f64::cos(polar_angle) * polar_dist,
y: cart_y + f64::sin(polar_angle) * polar_dist,
};
let ray_angle = light.ray_angle.sample(rng) * (PI / 180.0);
let direction = Vector {
x: f64::cos(ray_angle),
y: f64::sin(ray_angle),
};
let mut wavelength = 0.0;
while wavelength > LAST_WAVELENGTH - 1.0 || wavelength < FIRST_WAVELENGTH {
wavelength = light.wavelength.sample(rng);
}
Ray {
origin,
direction,
wavelength,
bounces: 1000,
ray_rng: R::seed_from_u64(rng.gen()),
}
}
pub fn collision_list(
mut self,
obj_list: &Vec<Object<R>>,
viewport: Rect,
) -> (Option<RayResult>, Option<Self>) {
let ((hit, normal, alpha), _dist, obj) = obj_list
.iter()
.filter_map(|o| {
o.get_hit(&self.origin, &self.direction, &mut self.ray_rng)
.map(|r| (r, self.origin.distance(&r.0), o))
})
.filter(|(_, distance, _)| *distance > 3.0)
.fold(
(((0.0, 0.0).into(), (0.0, 0.0).into(), 0.0), f64::MAX, None),
|b, x| if b.1 > x.1 { (x.0, x.1, Some(x.2)) } else { b },
);
match obj {
None => {
match self.furthest_aabb(viewport) {
Some(vp_hit) => (
Some(RayResult {
origin: self.origin,
termination: vp_hit,
wavelength: self.wavelength,
}),
None,
),
None => (None, None),
}
}
Some(obj) => {
let material_result = obj
.process_material(
&self.direction.normalized(),
&normal.normalized(),
self.wavelength,
alpha,
&mut self.ray_rng,
)
.map(|v| Ray {
origin: hit,
direction: v,
wavelength: self.wavelength,
bounces: self.bounces - 1,
ray_rng: R::seed_from_u64(self.ray_rng.gen()),
});
(
Some(RayResult {
origin: self.origin,
termination: hit,
wavelength: self.wavelength,
}),
material_result,
)
}
}
}
fn intersect_edge(&self, s1: Point, sd: Vector) -> Option<f64> {
let mat_a = Matrix {
a1: sd.x,
b1: -self.direction.x,
a2: sd.y,
b2: -self.direction.y,
};
let omega = self.origin - s1;
let result = match mat_a.inverse() {
Some(m) => m * omega,
None => {
return None; }
};
if (result.x >= 0.0) && (result.x <= 1.0) && (result.y > 0.0) {
Some(result.y)
} else {
None
}
}
pub fn furthest_aabb(&self, aabb: Rect) -> Option<Point> {
let mut max_dist: Option<f64> = None;
let horizontal = Vector {
x: aabb.top_right().x - aabb.top_left().x,
y: 0.0,
};
let vertical = Vector {
x: 0.0,
y: aabb.bottom_left().y - aabb.top_left().y,
};
match self.intersect_edge(aabb.top_left(), horizontal) {
None => (),
Some(d) => {
max_dist = match max_dist {
None => Some(d),
Some(md) => {
if d > md {
Some(d)
} else {
max_dist
}
}
};
}
}
match self.intersect_edge(aabb.bottom_left(), horizontal) {
None => (),
Some(d) => {
max_dist = match max_dist {
None => Some(d),
Some(md) => {
if d > md {
Some(d)
} else {
max_dist
}
}
};
}
}
match self.intersect_edge(aabb.top_left(), vertical) {
None => (),
Some(d) => {
max_dist = match max_dist {
None => Some(d),
Some(md) => {
if d > md {
Some(d)
} else {
max_dist
}
}
};
}
}
match self.intersect_edge(aabb.top_right(), vertical) {
None => (),
Some(d) => {
max_dist = match max_dist {
None => Some(d),
Some(md) => {
if d > md {
Some(d)
} else {
max_dist
}
}
};
}
}
match max_dist {
None => {
return None;
}
Some(d) => {
return Some(Point {
x: self.origin.x + d * self.direction.x,
y: self.origin.y + d * self.direction.y,
});
}
}
}
}
#[cfg(test)]
mod test {
type RandGen = rand_pcg::Pcg64Mcg;
use super::Ray;
use crate::geom::{Point, Rect};
use crate::sampler::Sampler;
use crate::scene::Light;
use rand::prelude::*;
fn new_test_light<R>(l: (f64, f64), a: f64) -> Light<R>
where
R: Rng,
{
Light::new(l, 1.0, 0.0, 0.0, a, Sampler::new_blackbody(5800.0))
}
#[test]
fn new_works() {
let mut rng = RandGen::from_entropy();
let l = Light {
power: Sampler::new_const(1.0),
location: (100.0, 100.0).into(),
polar_angle: Sampler::new_const(360.0),
polar_distance: Sampler::new_const(1.0),
ray_angle: Sampler::new_const(0.0),
wavelength: Sampler::new_const(460.0),
};
let r = Ray::new(&l, &mut rng);
assert_eq!(r.origin.x.round(), 101.0);
assert_eq!(r.origin.y.round(), 100.0);
assert_eq!(r.direction.x.round(), 1.0);
assert_eq!(r.direction.y.round(), 0.0);
assert_eq!(r.wavelength.round(), 460.0);
assert_eq!(r.bounces, 1000);
}
#[test]
fn furthest_aabb_hits_horziontal() {
let mut rng = RandGen::from_entropy();
let x_plus_light = new_test_light((0.0, 0.0), 0.0);
let ray = Ray::new(&x_plus_light, &mut rng);
let p1 = Point { x: 1.0, y: -10.0 };
let p2 = Point { x: 11.0, y: 10.0 };
let aabb = Rect::from_points(&p1, &p2);
let result = ray.furthest_aabb(aabb);
let result = result.expect("Result should have been Some()");
assert_eq!(result.x, 11.0);
assert_eq!(result.y, 0.0);
}
#[test]
fn furthest_aabb_hits_vertical() {
let mut rng = RandGen::from_entropy();
let x_plus_light = new_test_light((0.0, 0.0), 90.0);
let ray = Ray::new(&x_plus_light, &mut rng);
let p1 = Point { x: -10.0, y: 1.0 };
let p2 = Point { x: 10.0, y: 11.0 };
let aabb = Rect::from_points(&p1, &p2);
let result = ray.furthest_aabb(aabb);
let result = result.expect("That shouldn't be None!");
assert_eq!(result.x.round(), 0.0);
assert_eq!(result.y.round(), 11.0);
}
#[test]
fn furthest_aabb_hits_almost_vertical() {
let mut rng = RandGen::from_entropy();
let x_plus_light = new_test_light((0.0, 0.0), 45.0);
let ray = Ray::new(&x_plus_light, &mut rng);
let p1 = Point { x: -10.0, y: 1.0 };
let p2 = Point { x: 20.0, y: 11.0 };
let aabb = Rect::from_points(&p1, &p2);
let result = ray.furthest_aabb(aabb);
assert!(result.is_some());
let result = result.expect("Something was meant to be there!");
assert_eq!(result.x.round(), 11.0);
assert_eq!(result.y.round(), 11.0);
}
#[test]
fn furthest_aabb_special_case() {
let mut rng = RandGen::from_entropy();
let x_plus_light = new_test_light((100.0, 700.0), -45.0);
let ray = Ray::new(&x_plus_light, &mut rng);
let p1 = Point { x: 0.0, y: 0.0 };
let p2 = Point {
x: 200.0,
y: 1000.0,
};
let aabb = Rect::from_points(&p1, &p2);
let result = ray.furthest_aabb(aabb);
let result = result.expect("None is not what we wanted");
assert_eq!(result.x.round(), 200.0);
assert_eq!(result.y.round(), 600.0);
}
}