#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DirectionalLight {
pub direction: [f32; 3],
pub color: [f32; 3],
pub intensity: f32,
pub casts_shadow: bool,
}
impl Default for DirectionalLight {
fn default() -> Self {
Self {
direction: [0.0, 0.0, 1.0],
color: [1.0; 3],
intensity: 1.0,
casts_shadow: false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PointLight {
pub position: [f32; 3],
pub color: [f32; 3],
pub intensity: f32,
pub radius: f32,
pub casts_shadow: bool,
}
impl Default for PointLight {
fn default() -> Self {
Self {
position: [0.0; 3],
color: [1.0; 3],
intensity: 1.0,
radius: 32.0,
casts_shadow: false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SpotLight {
pub position: [f32; 3],
pub direction: [f32; 3],
pub color: [f32; 3],
pub intensity: f32,
pub radius: f32,
pub inner_angle_deg: f32,
pub outer_angle_deg: f32,
pub casts_shadow: bool,
}
impl Default for SpotLight {
fn default() -> Self {
Self {
position: [0.0; 3],
direction: [0.0, 0.0, 1.0],
color: [1.0; 3],
intensity: 1.0,
radius: 32.0,
inner_angle_deg: 20.0,
outer_angle_deg: 30.0,
casts_shadow: false,
}
}
}
impl SpotLight {
fn clamped_angles(&self) -> (f32, f32) {
let outer = self.outer_angle_deg.clamp(0.0, 180.0);
let inner = self.inner_angle_deg.clamp(0.0, outer);
(inner, outer)
}
pub(crate) fn axis(&self) -> [f32; 3] {
let d = self.direction;
let len = (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt();
if len > 1e-6 {
[d[0] / len, d[1] / len, d[2] / len]
} else {
[0.0, 0.0, 1.0]
}
}
pub(crate) fn cos_inner(&self) -> f32 {
self.clamped_angles().0.to_radians().cos()
}
pub(crate) fn cos_outer(&self) -> f32 {
self.clamped_angles().1.to_radians().cos()
}
}
#[derive(Clone, Copy, Debug)]
pub struct LightRig<'a> {
pub sun: Option<DirectionalLight>,
pub points: &'a [PointLight],
pub spots: &'a [SpotLight],
pub ambient: [f32; 3],
pub shadow_strength: f32,
pub shadow_bias_voxels: f32,
pub shadow_max_dist: f32,
pub bands: u32,
pub shadow_tint: [f32; 3],
}
impl Default for LightRig<'_> {
fn default() -> Self {
Self {
sun: None,
points: &[],
spots: &[],
ambient: [1.0; 3],
shadow_strength: 0.7,
shadow_bias_voxels: 1.5,
shadow_max_dist: 512.0,
bands: 0,
shadow_tint: [0.12, 0.14, 0.2],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spot_angles_convert_and_clamp() {
let s = SpotLight {
inner_angle_deg: 20.0,
outer_angle_deg: 30.0,
..SpotLight::default()
};
assert!(s.cos_inner() > s.cos_outer());
assert!((s.cos_inner() - 20.0f32.to_radians().cos()).abs() < 1e-6);
let hard = SpotLight {
inner_angle_deg: 40.0,
outer_angle_deg: 25.0,
..SpotLight::default()
};
assert!((hard.cos_inner() - hard.cos_outer()).abs() < 1e-6);
let wide = SpotLight {
outer_angle_deg: 180.0,
..SpotLight::default()
};
assert!(wide.cos_outer() <= -0.999);
}
#[test]
fn spot_axis_normalizes() {
let s = SpotLight {
direction: [0.0, 0.0, 5.0],
..SpotLight::default()
};
assert_eq!(s.axis(), [0.0, 0.0, 1.0]);
let z = SpotLight {
direction: [0.0; 3],
..SpotLight::default()
};
assert_eq!(z.axis(), [0.0, 0.0, 1.0]);
}
}