use glam::Vec3;
pub const RING_LIGHT_COUNT: usize = 16;
#[derive(Clone, Copy, Debug)]
pub struct StudioRig {
pub key_dir: Vec3,
pub fill_dir: Vec3,
pub ring_dirs: [Vec3; RING_LIGHT_COUNT],
pub sin_light_pitch: f32,
}
impl StudioRig {
#[must_use]
pub fn new(light_yaw: f32, light_pitch: f32) -> Self {
let cos_pitch = light_pitch.cos();
let sin_pitch = light_pitch.sin();
let cos_yaw = light_yaw.cos();
let sin_yaw = light_yaw.sin();
let key_dir = Vec3::new(cos_pitch * sin_yaw, sin_pitch, cos_pitch * cos_yaw).normalize();
let fill_yaw = std::f32::consts::PI.mul_add(0.78, light_yaw);
let fill_pitch = (light_pitch * 0.65).clamp(0.15, 1.2);
let fill_dir = Vec3::new(
fill_pitch.cos() * fill_yaw.sin(),
fill_pitch.sin(),
fill_pitch.cos() * fill_yaw.cos(),
)
.normalize();
let ring_dirs = std::array::from_fn(|i| {
let angle = (i as f32).mul_add(
std::f32::consts::PI * 2.0 / RING_LIGHT_COUNT as f32,
light_yaw,
);
Vec3::new(angle.sin() * 0.75, sin_pitch * 0.8, angle.cos() * 0.75).normalize()
});
Self {
key_dir,
fill_dir,
ring_dirs,
sin_light_pitch: sin_pitch,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn azimuth(dir: Vec3) -> f32 {
dir.x.atan2(dir.z)
}
fn angle_difference(a: f32, b: f32) -> f32 {
let tau = std::f32::consts::TAU;
(a - b + std::f32::consts::PI).rem_euclid(tau) - std::f32::consts::PI
}
#[test]
fn ring_azimuths_follow_the_key_yaw_and_rotate_in_its_sense() {
let light_pitch = 0.95f32;
for light_yaw in [0.0f32, 0.3, 0.85, 1.5] {
let rig = StudioRig::new(light_yaw, light_pitch);
assert_eq!(rig.ring_dirs.len(), RING_LIGHT_COUNT);
assert!(
angle_difference(azimuth(rig.key_dir), light_yaw).abs() < 1e-5,
"key azimuth must equal the yaw {light_yaw}"
);
assert!(
angle_difference(azimuth(rig.ring_dirs[0]), light_yaw).abs() < 1e-5,
"ring slot 0 must share the key azimuth at yaw {light_yaw}"
);
let quarter = light_yaw + std::f32::consts::FRAC_PI_2;
assert!(
angle_difference(azimuth(rig.ring_dirs[4]), quarter).abs() < 1e-5,
"ring slot 4 must sit a quarter turn past the key at yaw {light_yaw}"
);
assert!((rig.sin_light_pitch - light_pitch.sin()).abs() < 1e-6);
}
}
#[test]
fn sample_studio_environment_peaks_exactly_along_this_rigs_key_direction() {
let light_yaw = 0.3f32;
let light_pitch = 0.6f32;
let rig = StudioRig::new(light_yaw, light_pitch);
let on_axis = crate::optics::raytracer::sample_studio_environment(
rig.key_dir,
560.0,
crate::optics::raytracer::LightingPreset::RingLights,
1.0,
light_yaw,
light_pitch,
);
let off_axis = crate::optics::raytracer::sample_studio_environment(
Vec3::new(-rig.key_dir.z, rig.key_dir.y, rig.key_dir.x),
560.0,
crate::optics::raytracer::LightingPreset::RingLights,
1.0,
light_yaw,
light_pitch,
);
assert!(
on_axis > off_axis,
"radiance exactly along the shared rig's key_dir ({on_axis}) should exceed a direction rotated away from it ({off_axis})"
);
}
}