Skip to main content

oxihuman_viewer/
shadow_debug.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan) / SPDX-License-Identifier: Apache-2.0
2#![allow(dead_code)]
3
4//! Shadow map cascade debug visualization settings.
5
6/// Shadow cascade debug configuration.
7#[allow(dead_code)]
8#[derive(Debug, Clone)]
9pub struct ShadowDebugConfig {
10    /// Number of shadow cascades.
11    pub cascade_count: u32,
12    /// Currently visualized cascade index.
13    pub active_cascade: u32,
14    /// Show cascade split boundaries.
15    pub show_splits: bool,
16    /// Cascade colors for visualization.
17    pub cascade_colors: [[f32; 4]; 4],
18    /// Depth bias visualization scale.
19    pub bias_scale: f32,
20    /// Show shadow map texels overlay.
21    pub show_texels: bool,
22    /// Opacity of debug overlay 0..=1.
23    pub overlay_opacity: f32,
24}
25
26impl Default for ShadowDebugConfig {
27    fn default() -> Self {
28        Self {
29            cascade_count: 4,
30            active_cascade: 0,
31            show_splits: true,
32            cascade_colors: [
33                [1.0, 0.3, 0.3, 0.4],
34                [0.3, 1.0, 0.3, 0.4],
35                [0.3, 0.3, 1.0, 0.4],
36                [1.0, 1.0, 0.3, 0.4],
37            ],
38            bias_scale: 1.0,
39            show_texels: false,
40            overlay_opacity: 0.4,
41        }
42    }
43}
44
45/// Create default config.
46#[allow(dead_code)]
47pub fn new_shadow_debug_config() -> ShadowDebugConfig {
48    ShadowDebugConfig::default()
49}
50
51/// Set active cascade.
52#[allow(dead_code)]
53pub fn sd_set_active_cascade(cfg: &mut ShadowDebugConfig, idx: u32) {
54    cfg.active_cascade = idx.min(cfg.cascade_count.saturating_sub(1));
55}
56
57/// Set overlay opacity.
58#[allow(dead_code)]
59pub fn sd_set_opacity(cfg: &mut ShadowDebugConfig, opacity: f32) {
60    cfg.overlay_opacity = opacity.clamp(0.0, 1.0);
61}
62
63/// Toggle split boundary display.
64#[allow(dead_code)]
65pub fn sd_toggle_splits(cfg: &mut ShadowDebugConfig) {
66    cfg.show_splits = !cfg.show_splits;
67}
68
69/// Get cascade color for the active cascade.
70#[allow(dead_code)]
71pub fn sd_active_cascade_color(cfg: &ShadowDebugConfig) -> [f32; 4] {
72    let idx = (cfg.active_cascade as usize).min(3);
73    cfg.cascade_colors[idx]
74}
75
76/// Compute cascade split distance (simple logarithmic split).
77#[allow(dead_code)]
78pub fn sd_cascade_split(near: f32, far: f32, cascade: u32, total: u32) -> f32 {
79    if total == 0 {
80        return far;
81    }
82    let ratio = far / near.max(0.001);
83    let lambda = 0.7f32;
84    let t = cascade as f32 / total as f32;
85    let log_split = near * ratio.powf(t);
86    let lin_split = near + (far - near) * t;
87    lambda * log_split + (1.0 - lambda) * lin_split
88}
89
90/// Serialize to JSON.
91#[allow(dead_code)]
92pub fn shadow_debug_to_json(cfg: &ShadowDebugConfig) -> String {
93    format!(
94        r#"{{"cascade_count":{},"active_cascade":{},"show_splits":{},"overlay_opacity":{:.4}}}"#,
95        cfg.cascade_count, cfg.active_cascade, cfg.show_splits, cfg.overlay_opacity
96    )
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_default() {
105        let c = ShadowDebugConfig::default();
106        assert_eq!(c.cascade_count, 4);
107    }
108
109    #[test]
110    fn test_set_active_cascade_clamp() {
111        let mut c = ShadowDebugConfig::default();
112        sd_set_active_cascade(&mut c, 99);
113        assert!(c.active_cascade < c.cascade_count);
114    }
115
116    #[test]
117    fn test_set_opacity_clamp() {
118        let mut c = ShadowDebugConfig::default();
119        sd_set_opacity(&mut c, 5.0);
120        assert!((c.overlay_opacity - 1.0).abs() < 1e-6);
121    }
122
123    #[test]
124    fn test_toggle_splits() {
125        let mut c = ShadowDebugConfig::default();
126        sd_toggle_splits(&mut c);
127        assert!(!c.show_splits);
128        sd_toggle_splits(&mut c);
129        assert!(c.show_splits);
130    }
131
132    #[test]
133    fn test_active_cascade_color() {
134        let c = ShadowDebugConfig::default();
135        let col = sd_active_cascade_color(&c);
136        assert!((col[0] - 1.0).abs() < 1e-6);
137    }
138
139    #[test]
140    fn test_cascade_split_ordered() {
141        let s0 = sd_cascade_split(0.1, 100.0, 0, 4);
142        let s1 = sd_cascade_split(0.1, 100.0, 1, 4);
143        let s2 = sd_cascade_split(0.1, 100.0, 2, 4);
144        assert!(s0 < s1);
145        assert!(s1 < s2);
146    }
147
148    #[test]
149    fn test_cascade_split_zero_total() {
150        let s = sd_cascade_split(0.1, 100.0, 0, 0);
151        assert!((s - 100.0).abs() < 1e-5);
152    }
153
154    #[test]
155    fn test_to_json() {
156        let j = shadow_debug_to_json(&ShadowDebugConfig::default());
157        assert!(j.contains("cascade_count"));
158        assert!(j.contains("show_splits"));
159    }
160
161    #[test]
162    fn test_set_active_cascade_valid() {
163        let mut c = ShadowDebugConfig::default();
164        sd_set_active_cascade(&mut c, 2);
165        assert_eq!(c.active_cascade, 2);
166    }
167
168    #[test]
169    fn test_overlay_opacity_range() {
170        let c = ShadowDebugConfig::default();
171        assert!((0.0..=1.0).contains(&c.overlay_opacity));
172    }
173}