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 { 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 { // 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, } #[derive(Debug, Clone, Copy)] pub struct Hit { pub incident: Ray, pub normal: Vec3, } impl Scene { pub fn trace_ray(&self, ray: Ray) -> Option { 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 } }