qt-tracing/src/trace.rs
2025-11-20 13:07:02 +03:00

161 lines
3.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
}