#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct ScanLineEffectConfig {
pub line_spacing_px: f32,
pub darkness: f32,
pub wave_amplitude: f32,
pub wave_frequency: f32,
pub flicker_rate: f32,
pub opacity: f32,
pub enabled: bool,
}
impl Default for ScanLineEffectConfig {
fn default() -> Self {
Self {
line_spacing_px: 3.0,
darkness: 0.25,
wave_amplitude: 0.001,
wave_frequency: 0.1,
flicker_rate: 0.02,
opacity: 0.7,
enabled: true,
}
}
}
#[allow(dead_code)]
pub fn new_scan_line_effect_config() -> ScanLineEffectConfig {
ScanLineEffectConfig::default()
}
#[allow(dead_code)]
pub fn sl_set_darkness(cfg: &mut ScanLineEffectConfig, v: f32) {
cfg.darkness = v.clamp(0.0, 1.0);
}
#[allow(dead_code)]
pub fn sl_set_opacity(cfg: &mut ScanLineEffectConfig, v: f32) {
cfg.opacity = v.clamp(0.0, 1.0);
}
#[allow(dead_code)]
pub fn sl_set_line_spacing(cfg: &mut ScanLineEffectConfig, px: f32) {
cfg.line_spacing_px = px.clamp(1.0, 32.0);
}
#[allow(dead_code)]
pub fn sl_darkening_at(y_px: f32, time_s: f32, cfg: &ScanLineEffectConfig) -> f32 {
if !cfg.enabled {
return 0.0;
}
let flicker = (time_s * cfg.flicker_rate).fract();
let effective_y = y_px + flicker * cfg.line_spacing_px;
let phase = (effective_y / cfg.line_spacing_px).fract();
let on_line = if !(0.3..=0.7).contains(&phase) {
1.0
} else {
0.0
};
on_line * cfg.darkness * cfg.opacity
}
#[allow(dead_code)]
pub fn sl_line_count(height_px: f32, cfg: &ScanLineEffectConfig) -> u32 {
(height_px / cfg.line_spacing_px.max(1e-3)) as u32
}
#[allow(dead_code)]
pub fn sl_midpoint_darkening(height_px: f32, cfg: &ScanLineEffectConfig) -> f32 {
sl_darkening_at(height_px * 0.5, 0.0, cfg)
}
#[allow(dead_code)]
pub fn sl_to_json(cfg: &ScanLineEffectConfig) -> String {
format!(
"{{\"line_spacing_px\":{:.1},\"darkness\":{:.3},\"opacity\":{:.3},\"enabled\":{}}}",
cfg.line_spacing_px, cfg.darkness, cfg.opacity, cfg.enabled
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn disabled_zero_darkening() {
let mut cfg = new_scan_line_effect_config();
cfg.enabled = false;
assert!(sl_darkening_at(10.0, 0.0, &cfg) < 1e-8);
}
#[test]
fn darkening_in_range() {
let cfg = new_scan_line_effect_config();
let d = sl_darkening_at(0.0, 0.0, &cfg);
assert!((0.0..=1.0).contains(&d));
}
#[test]
fn line_count_positive() {
let cfg = new_scan_line_effect_config();
assert!(sl_line_count(480.0, &cfg) > 0);
}
#[test]
fn line_count_proportional() {
let cfg = new_scan_line_effect_config();
let a = sl_line_count(240.0, &cfg);
let b = sl_line_count(480.0, &cfg);
assert!(b >= a);
}
#[test]
fn darkness_clamps() {
let mut cfg = new_scan_line_effect_config();
sl_set_darkness(&mut cfg, 5.0);
assert!((cfg.darkness - 1.0).abs() < 1e-6);
}
#[test]
fn opacity_clamps() {
let mut cfg = new_scan_line_effect_config();
sl_set_opacity(&mut cfg, -1.0);
assert!(cfg.opacity < 1e-6);
}
#[test]
fn line_spacing_clamps_low() {
let mut cfg = new_scan_line_effect_config();
sl_set_line_spacing(&mut cfg, 0.0);
assert!((cfg.line_spacing_px - 1.0).abs() < 1e-6);
}
#[test]
fn line_spacing_clamps_high() {
let mut cfg = new_scan_line_effect_config();
sl_set_line_spacing(&mut cfg, 100.0);
assert!((cfg.line_spacing_px - 32.0).abs() < 1e-6);
}
#[test]
fn midpoint_darkening_in_range() {
let cfg = new_scan_line_effect_config();
let d = sl_midpoint_darkening(480.0, &cfg);
assert!((0.0..=1.0).contains(&d));
}
#[test]
fn json_has_keys() {
let j = sl_to_json(&new_scan_line_effect_config());
assert!(j.contains("line_spacing_px") && j.contains("enabled"));
}
}