#![allow(dead_code)]
use std::f32::consts::PI;
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AoTechnique {
Ssao,
Hbao,
Gtao,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct AoV2Config {
pub technique: AoTechnique,
pub radius: f32,
pub bias: f32,
pub intensity: f32,
pub sample_count: u32,
pub blur_passes: u32,
}
impl Default for AoV2Config {
fn default() -> Self {
Self {
technique: AoTechnique::Ssao,
radius: 0.5,
bias: 0.025,
intensity: 1.0,
sample_count: 16,
blur_passes: 2,
}
}
}
#[allow(dead_code)]
pub fn new_ao_v2_config() -> AoV2Config {
AoV2Config::default()
}
#[allow(dead_code)]
pub fn ao_v2_set_technique(cfg: &mut AoV2Config, t: AoTechnique) {
cfg.technique = t;
}
#[allow(dead_code)]
pub fn ao_v2_set_radius(cfg: &mut AoV2Config, r: f32) {
cfg.radius = r.max(0.01);
}
#[allow(dead_code)]
pub fn ao_v2_set_intensity(cfg: &mut AoV2Config, v: f32) {
cfg.intensity = v.clamp(0.0, 4.0);
}
#[allow(dead_code)]
pub fn ao_v2_set_samples(cfg: &mut AoV2Config, n: u32) {
cfg.sample_count = n.clamp(4, 64);
}
#[allow(dead_code)]
pub fn ao_v2_hemisphere_dir(i: u32, n: u32) -> [f32; 3] {
if n == 0 {
return [0.0, 1.0, 0.0];
}
let t = i as f32 / n as f32;
let phi = t * 2.0 * PI;
let cos_theta = (1.0 - t).sqrt();
let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
[sin_theta * phi.cos(), cos_theta, sin_theta * phi.sin()]
}
#[allow(dead_code)]
pub fn ao_v2_technique_name(t: AoTechnique) -> &'static str {
match t {
AoTechnique::Ssao => "ssao",
AoTechnique::Hbao => "hbao",
AoTechnique::Gtao => "gtao",
}
}
#[allow(dead_code)]
pub fn ao_v2_estimated_cost(cfg: &AoV2Config) -> f32 {
cfg.sample_count as f32 * (1.0 + cfg.blur_passes as f32 * 0.2)
}
#[allow(dead_code)]
pub fn ao_v2_to_json(cfg: &AoV2Config) -> String {
format!(
"{{\"technique\":\"{}\",\"radius\":{:.4},\"intensity\":{:.4},\"samples\":{}}}",
ao_v2_technique_name(cfg.technique),
cfg.radius,
cfg.intensity,
cfg.sample_count
)
}
#[allow(dead_code)]
pub fn ao_v2_blend_configs(a: &AoV2Config, b: &AoV2Config, t: f32) -> AoV2Config {
let t = t.clamp(0.0, 1.0);
AoV2Config {
technique: if t < 0.5 { a.technique } else { b.technique },
radius: a.radius + (b.radius - a.radius) * t,
bias: a.bias + (b.bias - a.bias) * t,
intensity: a.intensity + (b.intensity - a.intensity) * t,
sample_count: if t < 0.5 {
a.sample_count
} else {
b.sample_count
},
blur_passes: if t < 0.5 {
a.blur_passes
} else {
b.blur_passes
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_technique_ssao() {
assert_eq!(new_ao_v2_config().technique, AoTechnique::Ssao);
}
#[test]
fn radius_min_clamp() {
let mut cfg = new_ao_v2_config();
ao_v2_set_radius(&mut cfg, -1.0);
assert!(cfg.radius >= 0.01);
}
#[test]
fn intensity_clamp() {
let mut cfg = new_ao_v2_config();
ao_v2_set_intensity(&mut cfg, 10.0);
assert!(cfg.intensity <= 4.0);
}
#[test]
fn sample_count_clamp_min() {
let mut cfg = new_ao_v2_config();
ao_v2_set_samples(&mut cfg, 1);
assert!(cfg.sample_count >= 4);
}
#[test]
fn hemisphere_dir_len() {
let d = ao_v2_hemisphere_dir(0, 16);
let len = (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt();
assert!((len - 1.0).abs() < 1e-4);
}
#[test]
fn technique_names() {
assert_eq!(ao_v2_technique_name(AoTechnique::Gtao), "gtao");
}
#[test]
fn cost_positive() {
assert!(ao_v2_estimated_cost(&new_ao_v2_config()) > 0.0);
}
#[test]
fn json_has_technique() {
assert!(ao_v2_to_json(&new_ao_v2_config()).contains("technique"));
}
#[test]
fn blend_t0_is_a() {
let a = new_ao_v2_config();
let mut b = new_ao_v2_config();
ao_v2_set_intensity(&mut b, 2.0);
let r = ao_v2_blend_configs(&a, &b, 0.0);
assert!((r.intensity - a.intensity).abs() < 1e-5);
}
#[test]
fn zero_samples_hemisphere_safe() {
let d = ao_v2_hemisphere_dir(0, 0);
assert!((d[1] - 1.0).abs() < 1e-5);
}
}