161 lines
3.6 KiB
Rust
161 lines
3.6 KiB
Rust
use std::f32::consts::PI;
|
||
|
||
use glam::{Mat4, Vec2, Vec3, vec3};
|
||
use rand_distr::{Distribution, UnitSphere};
|
||
|
||
use crate::{camera::OrbitalCamera, ray::Ray};
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub struct Source {
|
||
/// Horizontal position (angle), in radians from +X towards +Y.
|
||
pub position_yaw: f32,
|
||
|
||
/// Vertical position (angle), in radians from XY plane towards +Z.
|
||
pub position_pitch: f32,
|
||
|
||
/// Distance from the origin.
|
||
pub distance: f32,
|
||
|
||
/// Disc diameter.
|
||
pub radius: f32,
|
||
|
||
pub spread: f32,
|
||
}
|
||
|
||
impl Source {
|
||
fn orbital(&self) -> OrbitalCamera {
|
||
let &Self {
|
||
position_yaw,
|
||
position_pitch,
|
||
distance,
|
||
..
|
||
} = self;
|
||
OrbitalCamera {
|
||
position_yaw,
|
||
position_pitch,
|
||
distance,
|
||
}
|
||
}
|
||
|
||
pub fn position(&self) -> Vec3 {
|
||
self.orbital().position()
|
||
}
|
||
|
||
pub fn transform(&self) -> Mat4 {
|
||
self.orbital().transform().inverse()
|
||
}
|
||
|
||
pub fn contour(&self, n: usize) -> impl Iterator<Item = Vec3> {
|
||
let step = 2. * PI / n as f32;
|
||
let m = self.transform();
|
||
(0..n).map(move |k| {
|
||
let angle = (k as f32) * step;
|
||
let (x, y) = angle.sin_cos();
|
||
m.transform_point3(self.radius * vec3(x, y, 0.))
|
||
})
|
||
}
|
||
|
||
pub fn make_ray(&self, rng: &mut impl rand::Rng) -> Ray {
|
||
let base: Vec2 = rand_distr::UnitDisc.sample(rng).into();
|
||
let off: f32 = rand_distr::StandardUniform.sample(rng);
|
||
let side: Vec2 = rand_distr::UnitCircle.sample(rng).into();
|
||
|
||
let m = self.transform();
|
||
let base = Vec3::from((self.radius * base, 0.));
|
||
let fwd = 1. - self.spread * off;
|
||
let side_scale = (1. - fwd.powi(2)).sqrt();
|
||
let dir = Vec3::from((side_scale * side, fwd));
|
||
Ray {
|
||
base: m.transform_point3(base),
|
||
dir: m.transform_vector3(dir),
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub struct Sphere {
|
||
pub position: Vec3,
|
||
pub radius: f32,
|
||
}
|
||
|
||
struct Hit1 {
|
||
pos: Vec3,
|
||
dist: f32,
|
||
normal: Vec3,
|
||
}
|
||
|
||
impl Sphere {
|
||
fn trace_ray(&self, ray: Ray) -> Option<Hit1> {
|
||
// let t: f32;
|
||
// let hit = ray.base + t * ray.dir;
|
||
// (hit - self.position).length() == self.radius;
|
||
let Ray { base, dir } = ray;
|
||
let rel = base - self.position;
|
||
// (rel + t⋅dir)² == r²
|
||
// rel² − r² + 2⋅t⋅rel⋅dir + t²⋅dir² = 0
|
||
let a = dir.length_squared();
|
||
let b2 = rel.dot(dir);
|
||
let c = rel.length_squared() - self.radius.powi(2);
|
||
let d4 = b2.powi(2) - a * c;
|
||
if d4 < 0. {
|
||
return None;
|
||
}
|
||
let dist = (-b2 - d4.sqrt()) / a;
|
||
let pos = ray.advance(dist).base;
|
||
let normal = (pos - self.position).normalize();
|
||
Some(Hit1 { pos, dist, normal })
|
||
}
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
pub struct Scene {
|
||
pub objects: Vec<Sphere>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Copy)]
|
||
pub struct Hit {
|
||
pub incident: Ray,
|
||
pub normal: Vec3,
|
||
}
|
||
|
||
impl Scene {
|
||
pub fn trace_ray(&self, ray: Ray) -> Option<Hit> {
|
||
let hit = self
|
||
.objects
|
||
.iter()
|
||
.filter_map(|obj| obj.trace_ray(ray))
|
||
.min_by(|a, b| f32::total_cmp(&a.dist, &b.dist))?;
|
||
Some(Hit {
|
||
incident: Ray {
|
||
base: hit.pos,
|
||
dir: ray.dir,
|
||
},
|
||
normal: hit.normal,
|
||
})
|
||
}
|
||
}
|
||
|
||
pub trait Reflector {
|
||
fn brdf(&self, normal: Vec3, incident: Vec3, reflected: Vec3) -> f32 /* 1/sr */;
|
||
fn reflect(&self, rgen: &mut impl rand::Rng, normal: Vec3, incident: Vec3) -> Vec3;
|
||
}
|
||
|
||
pub struct Lambertian;
|
||
|
||
impl Reflector for Lambertian {
|
||
fn brdf(&self, _normal: Vec3, _incident: Vec3, _reflected: Vec3) -> f32 {
|
||
1. / PI
|
||
}
|
||
|
||
fn reflect(&self, rgen: &mut impl rand::Rng, normal: Vec3, _incident: Vec3) -> Vec3 {
|
||
let sphere: Vec3 = UnitSphere.sample(rgen).into();
|
||
let sphere_n = normal.dot(sphere); // uniform on [-1, 1]!
|
||
let sphere_t = sphere - sphere_n * normal;
|
||
|
||
let out_n_len2 = sphere_n.abs();
|
||
let out_t = (1. + out_n_len2).recip().sqrt() * sphere_t;
|
||
let out_n = out_n_len2.sqrt() * normal;
|
||
out_t + out_n
|
||
}
|
||
}
|