#[derive(Debug, Clone)]
pub struct ShadowConfig {
pub resolution: u32,
pub bias: f64,
pub softness: f64,
pub darkness: f64,
pub enabled: bool,
pub ortho_size: f64,
}
impl Default for ShadowConfig {
fn default() -> Self {
Self {
resolution: 1024,
bias: 0.005,
softness: 1.0,
darkness: 0.5,
enabled: false,
ortho_size: 10.0,
}
}
}
impl ShadowConfig {
pub fn new() -> Self {
Self { enabled: true, ..Default::default() }
}
pub fn with_resolution(mut self, res: u32) -> Self {
self.resolution = res;
self
}
pub fn with_softness(mut self, softness: f64) -> Self {
self.softness = softness;
self
}
pub fn light_vp_matrix(&self, light_dir: [f64; 3], center: [f64; 3]) -> [[f64; 4]; 4] {
let s = self.ortho_size;
let ld = normalize3(light_dir);
let light_pos = [
center[0] - ld[0] * s * 2.0,
center[1] - ld[1] * s * 2.0,
center[2] - ld[2] * s * 2.0,
];
let view = look_at(light_pos, center, [0.0, 1.0, 0.0]);
let proj = ortho(-s, s, -s, s, 0.1, s * 4.0);
mat4_mul(proj, view)
}
}
fn normalize3(v: [f64; 3]) -> [f64; 3] {
let len = (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]).sqrt();
if len < 1e-15 { return [0.0, -1.0, 0.0]; }
[v[0]/len, v[1]/len, v[2]/len]
}
fn look_at(eye: [f64; 3], target: [f64; 3], up: [f64; 3]) -> [[f64; 4]; 4] {
let f = normalize3([target[0]-eye[0], target[1]-eye[1], target[2]-eye[2]]);
let s = normalize3(cross3(f, up));
let u = cross3(s, f);
[
[s[0], u[0], -f[0], 0.0],
[s[1], u[1], -f[1], 0.0],
[s[2], u[2], -f[2], 0.0],
[-dot3(s, eye), -dot3(u, eye), dot3(f, eye), 1.0],
]
}
fn ortho(l: f64, r: f64, b: f64, t: f64, n: f64, f: f64) -> [[f64; 4]; 4] {
[
[2.0/(r-l), 0.0, 0.0, 0.0],
[0.0, 2.0/(t-b), 0.0, 0.0],
[0.0, 0.0, -2.0/(f-n), 0.0],
[-(r+l)/(r-l), -(t+b)/(t-b), -(f+n)/(f-n), 1.0],
]
}
fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0]]
}
fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
}
fn mat4_mul(a: [[f64; 4]; 4], b: [[f64; 4]; 4]) -> [[f64; 4]; 4] {
let mut r = [[0.0; 4]; 4];
for i in 0..4 {
for j in 0..4 {
for k in 0..4 {
r[i][j] += a[k][j] * b[i][k];
}
}
}
r
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config() {
let sc = ShadowConfig::default();
assert!(!sc.enabled);
assert_eq!(sc.resolution, 1024);
}
#[test]
fn enabled_config() {
let sc = ShadowConfig::new().with_resolution(2048).with_softness(2.0);
assert!(sc.enabled);
assert_eq!(sc.resolution, 2048);
assert_eq!(sc.softness, 2.0);
}
#[test]
fn light_vp_matrix() {
let sc = ShadowConfig::new();
let vp = sc.light_vp_matrix([0.0, -1.0, 0.0], [0.0, 0.0, 0.0]);
assert!(vp[3][3].abs() > 0.0);
}
#[test]
fn normalize() {
let n = normalize3([3.0, 4.0, 0.0]);
let len = (n[0]*n[0] + n[1]*n[1] + n[2]*n[2]).sqrt();
assert!((len - 1.0).abs() < 1e-10);
}
}