Skip to main content

oxihuman_viewer/
bent_normal_debug.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Bent normal debug visualizer — renders bent normal directions as colored overlays.
6
7use std::f32::consts::FRAC_PI_2;
8
9/// Bent normal visualization mode.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[allow(dead_code)]
12pub enum BentNormalMode {
13    WorldSpace,
14    TangentSpace,
15    AoWeighted,
16}
17
18/// Configuration for bent normal debug display.
19#[derive(Debug, Clone, PartialEq)]
20#[allow(dead_code)]
21pub struct BentNormalConfig {
22    pub mode: BentNormalMode,
23    pub scale: f32,
24    pub ao_threshold: f32,
25    pub enabled: bool,
26}
27
28impl Default for BentNormalConfig {
29    fn default() -> Self {
30        Self {
31            mode: BentNormalMode::WorldSpace,
32            scale: 1.0,
33            ao_threshold: 0.5,
34            enabled: false,
35        }
36    }
37}
38
39/// A single bent normal entry.
40#[derive(Debug, Clone, PartialEq, Default)]
41#[allow(dead_code)]
42pub struct BentNormalEntry {
43    pub position: [f32; 3],
44    pub bent_normal: [f32; 3],
45    pub ao: f32,
46}
47
48/// Bent normal debug buffer.
49#[derive(Debug, Clone, Default)]
50#[allow(dead_code)]
51pub struct BentNormalDebug {
52    pub config: BentNormalConfig,
53    pub entries: Vec<BentNormalEntry>,
54}
55
56/// Create default config.
57#[allow(dead_code)]
58pub fn default_bent_normal_config() -> BentNormalConfig {
59    BentNormalConfig::default()
60}
61
62/// Create new debug buffer.
63#[allow(dead_code)]
64pub fn new_bent_normal_debug(cfg: BentNormalConfig) -> BentNormalDebug {
65    BentNormalDebug {
66        config: cfg,
67        entries: Vec::new(),
68    }
69}
70
71/// Add an entry.
72#[allow(dead_code)]
73pub fn add_bent_normal(d: &mut BentNormalDebug, pos: [f32; 3], bn: [f32; 3], ao: f32) {
74    d.entries.push(BentNormalEntry {
75        position: pos,
76        bent_normal: bn,
77        ao: ao.clamp(0.0, 1.0),
78    });
79}
80
81/// Clear all entries.
82#[allow(dead_code)]
83pub fn clear_bent_normals(d: &mut BentNormalDebug) {
84    d.entries.clear();
85}
86
87/// Count of entries.
88#[allow(dead_code)]
89pub fn bent_normal_count(d: &BentNormalDebug) -> usize {
90    d.entries.len()
91}
92
93/// Enable or disable the debug view.
94#[allow(dead_code)]
95pub fn set_bent_normal_enabled(d: &mut BentNormalDebug, enabled: bool) {
96    d.config.enabled = enabled;
97}
98
99/// Filter entries by AO threshold.
100#[allow(dead_code)]
101pub fn filtered_entries(d: &BentNormalDebug) -> Vec<&BentNormalEntry> {
102    d.entries
103        .iter()
104        .filter(|e| e.ao >= d.config.ao_threshold)
105        .collect()
106}
107
108/// Normalize a 3D vector.
109#[allow(dead_code)]
110pub fn normalize_bn(v: [f32; 3]) -> [f32; 3] {
111    let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
112    if len < 1e-9 {
113        [0.0, 0.0, 1.0]
114    } else {
115        [v[0] / len, v[1] / len, v[2] / len]
116    }
117}
118
119/// Convert bent normal to RGB color (map [-1,1] to `[0,1]`).
120#[allow(dead_code)]
121pub fn bent_normal_to_color(bn: [f32; 3]) -> [f32; 3] {
122    [
123        (bn[0] * 0.5 + 0.5).clamp(0.0, 1.0),
124        (bn[1] * 0.5 + 0.5).clamp(0.0, 1.0),
125        (bn[2] * 0.5 + 0.5).clamp(0.0, 1.0),
126    ]
127}
128
129/// Compute angle between bent normal and surface normal using FRAC_PI_2 as reference.
130#[allow(dead_code)]
131pub fn bent_normal_angle(bn: [f32; 3], surface_normal: [f32; 3]) -> f32 {
132    let dot = bn[0] * surface_normal[0] + bn[1] * surface_normal[1] + bn[2] * surface_normal[2];
133    let angle = dot.clamp(-1.0, 1.0).acos();
134    // normalize to [0,1] using FRAC_PI_2 as half-range
135    angle / FRAC_PI_2
136}
137
138/// Export debug state to JSON-like string.
139#[allow(dead_code)]
140pub fn bent_normal_debug_to_json(d: &BentNormalDebug) -> String {
141    format!(
142        r#"{{"count":{},"enabled":{}}}"#,
143        d.entries.len(),
144        d.config.enabled
145    )
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn default_config_disabled() {
154        assert!(!BentNormalConfig::default().enabled);
155    }
156
157    #[test]
158    fn add_and_count() {
159        let mut d = new_bent_normal_debug(default_bent_normal_config());
160        add_bent_normal(&mut d, [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], 0.8);
161        assert_eq!(bent_normal_count(&d), 1);
162    }
163
164    #[test]
165    fn clear_empties() {
166        let mut d = new_bent_normal_debug(default_bent_normal_config());
167        add_bent_normal(&mut d, [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], 0.5);
168        clear_bent_normals(&mut d);
169        assert_eq!(bent_normal_count(&d), 0);
170    }
171
172    #[test]
173    fn enable_sets_flag() {
174        let mut d = new_bent_normal_debug(default_bent_normal_config());
175        set_bent_normal_enabled(&mut d, true);
176        assert!(d.config.enabled);
177    }
178
179    #[test]
180    fn filter_by_ao() {
181        let mut d = new_bent_normal_debug(BentNormalConfig {
182            ao_threshold: 0.6,
183            ..Default::default()
184        });
185        add_bent_normal(&mut d, [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], 0.9);
186        add_bent_normal(&mut d, [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], 0.3);
187        assert_eq!(filtered_entries(&d).len(), 1);
188    }
189
190    #[test]
191    fn normalize_unit_vector() {
192        let n = normalize_bn([0.0, 0.0, 1.0]);
193        assert!((n[2] - 1.0).abs() < 1e-6);
194    }
195
196    #[test]
197    fn normalize_zero_returns_up() {
198        let n = normalize_bn([0.0, 0.0, 0.0]);
199        assert!((n[2] - 1.0).abs() < 1e-6);
200    }
201
202    #[test]
203    fn color_up_normal() {
204        let c = bent_normal_to_color([0.0, 1.0, 0.0]);
205        assert!((c[1] - 1.0).abs() < 1e-6);
206    }
207
208    #[test]
209    fn angle_parallel_is_zero() {
210        let angle = bent_normal_angle([0.0, 1.0, 0.0], [0.0, 1.0, 0.0]);
211        assert!(angle < 1e-5);
212    }
213
214    #[test]
215    fn json_contains_count() {
216        let d = new_bent_normal_debug(default_bent_normal_config());
217        assert!(bent_normal_debug_to_json(&d).contains("count"));
218    }
219}