#![allow(dead_code)]
use std::f32::consts::FRAC_PI_4;
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlphaDiscardMode {
Hard,
Dithered,
None,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct AlphaDiscardConfig {
pub threshold: f32,
pub soft_range: f32,
pub mode: AlphaDiscardMode,
pub dither_scale: f32,
}
impl Default for AlphaDiscardConfig {
fn default() -> Self {
AlphaDiscardConfig {
threshold: 0.5,
soft_range: 0.05,
mode: AlphaDiscardMode::Hard,
dither_scale: FRAC_PI_4,
}
}
}
pub fn default_alpha_discard_config() -> AlphaDiscardConfig {
AlphaDiscardConfig::default()
}
pub fn new_alpha_discard(threshold: f32, mode: AlphaDiscardMode) -> AlphaDiscardConfig {
AlphaDiscardConfig {
threshold: threshold.clamp(0.0, 1.0),
mode,
..AlphaDiscardConfig::default()
}
}
pub fn ad_set_threshold(cfg: &mut AlphaDiscardConfig, v: f32) {
cfg.threshold = v.clamp(0.0, 1.0);
}
pub fn ad_set_soft_range(cfg: &mut AlphaDiscardConfig, v: f32) {
cfg.soft_range = v.clamp(0.0, 0.5);
}
pub fn ad_set_mode(cfg: &mut AlphaDiscardConfig, mode: AlphaDiscardMode) {
cfg.mode = mode;
}
pub fn ad_mode_name(cfg: &AlphaDiscardConfig) -> &'static str {
match cfg.mode {
AlphaDiscardMode::Hard => "hard",
AlphaDiscardMode::Dithered => "dithered",
AlphaDiscardMode::None => "none",
}
}
pub fn ad_should_keep(cfg: &AlphaDiscardConfig, alpha: f32) -> bool {
match cfg.mode {
AlphaDiscardMode::None => true,
AlphaDiscardMode::Hard => alpha >= cfg.threshold,
AlphaDiscardMode::Dithered => {
let lo = cfg.threshold - cfg.soft_range;
let hi = cfg.threshold + cfg.soft_range;
if alpha >= hi {
return true;
}
if alpha < lo {
return false;
}
let t = (alpha - lo) / (hi - lo);
t * t * (3.0 - 2.0 * t) >= 0.5
}
}
}
pub fn ad_coverage_estimate(cfg: &AlphaDiscardConfig) -> f32 {
let n = 10usize;
let mut kept = 0usize;
#[allow(clippy::needless_range_loop)]
for i in 0..n {
let alpha = i as f32 / (n - 1) as f32;
if ad_should_keep(cfg, alpha) {
kept += 1;
}
}
kept as f32 / n as f32
}
pub fn ad_to_json(cfg: &AlphaDiscardConfig) -> String {
format!(
r#"{{"threshold":{:.4},"soft_range":{:.4},"mode":"{}"}}"#,
cfg.threshold,
cfg.soft_range,
ad_mode_name(cfg)
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_threshold_half() {
let cfg = default_alpha_discard_config();
assert!((cfg.threshold - 0.5).abs() < 1e-5);
}
#[test]
fn hard_keep_above_threshold() {
let cfg = new_alpha_discard(0.5, AlphaDiscardMode::Hard);
assert!(ad_should_keep(&cfg, 0.6));
}
#[test]
fn hard_discard_below_threshold() {
let cfg = new_alpha_discard(0.5, AlphaDiscardMode::Hard);
assert!(!ad_should_keep(&cfg, 0.4));
}
#[test]
fn none_mode_always_keeps() {
let cfg = new_alpha_discard(0.5, AlphaDiscardMode::None);
assert!(ad_should_keep(&cfg, 0.0));
}
#[test]
fn set_threshold_clamps() {
let mut cfg = default_alpha_discard_config();
ad_set_threshold(&mut cfg, 5.0);
assert!((cfg.threshold - 1.0).abs() < 1e-5);
}
#[test]
fn mode_name_correct() {
let cfg = new_alpha_discard(0.5, AlphaDiscardMode::Dithered);
assert_eq!(ad_mode_name(&cfg), "dithered");
}
#[test]
fn coverage_in_range() {
let cfg = default_alpha_discard_config();
assert!((0.0..=1.0).contains(&ad_coverage_estimate(&cfg)));
}
#[test]
fn json_has_threshold() {
assert!(ad_to_json(&default_alpha_discard_config()).contains("threshold"));
}
#[test]
fn dithered_keeps_full_alpha() {
let cfg = new_alpha_discard(0.5, AlphaDiscardMode::Dithered);
assert!(ad_should_keep(&cfg, 1.0));
}
}