use glam::Vec3;
#[derive(Clone, Copy, Debug)]
pub struct Ray {
pub origin: Vec3,
pub dir: Vec3,
}
#[derive(Clone, Copy, Debug)]
pub struct HitRecord {
pub t: f32,
pub normal: Vec3,
pub facet_idx: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FacetFinish {
#[default]
Polished,
Frosted,
}
pub const DEFAULT_FOV_DEG: f32 = 42.0;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OrbitPose {
pub yaw: f32,
pub pitch: f32,
pub distance: f32,
}
pub const DEFAULT_POSE: OrbitPose = OrbitPose {
yaw: 0.60,
pitch: 0.45,
distance: 2.4,
};
pub struct Camera {
pub origin: Vec3,
pub forward: Vec3,
pub right: Vec3,
pub up: Vec3,
pub fov_tan: f32,
}
impl Camera {
#[must_use]
pub fn new(yaw: f32, pitch: f32, distance: f32, fov_deg: f32) -> Self {
let cos_p = pitch.cos();
let sin_p = pitch.sin();
let cos_y = yaw.cos();
let sin_y = yaw.sin();
let origin = Vec3::new(
distance * cos_p * sin_y,
distance * sin_p,
distance * cos_p * cos_y,
);
let forward = (-origin).normalize();
let world_up = if cos_p.abs() < 1e-4 {
Vec3::new(0.0, 0.0, -1.0)
} else if cos_p < 0.0 {
Vec3::NEG_Y
} else {
Vec3::Y
};
let right = forward.cross(world_up).normalize();
let up = right.cross(forward).normalize();
let fov_tan = (fov_deg.to_radians() * 0.5).tan();
Self {
origin,
forward,
right,
up,
fov_tan,
}
}
#[must_use]
pub fn generate_ray(
&self,
screen_x: f32,
screen_y: f32,
width: f32,
height: f32,
jitter_x: f32,
jitter_y: f32,
) -> Ray {
let aspect = width / height;
let u = ((screen_x + jitter_x) / width - 0.5) * 2.0 * aspect * self.fov_tan;
let v = (0.5 - (screen_y + jitter_y) / height) * 2.0 * self.fov_tan;
let dir = (self.forward + self.right * u + self.up * v).normalize();
Ray {
origin: self.origin,
dir,
}
}
}
#[cfg(test)]
mod tests {
use super::Camera;
use std::f32::consts::FRAC_PI_2;
#[test]
fn right_vector_is_continuous_through_the_pole() {
let yaw = 0.7;
let before = Camera::new(yaw, FRAC_PI_2 - 0.01, 2.4, 42.0);
let after = Camera::new(yaw, FRAC_PI_2 + 0.01, 2.4, 42.0);
assert!(
(before.right - after.right).length() < 1e-3,
"right flipped through the pole: {:?} vs {:?}",
before.right,
after.right
);
assert!((after.forward + after.origin.normalize()).length() < 1e-6);
assert!((after.up.dot(after.forward)).abs() < 1e-6);
}
}