oxihuman_viewer/
occlusion_map.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum AoAlgorithm {
11 Ssao,
12 Hbao,
13 Gtao,
14 BakedTexture,
15}
16
17#[allow(dead_code)]
19#[derive(Debug, Clone)]
20pub struct OcclusionMapConfig {
21 pub algorithm: AoAlgorithm,
22 pub radius: f32,
23 pub bias: f32,
24 pub intensity: f32,
25 pub sample_count: u32,
26 pub enabled: bool,
27}
28
29#[allow(dead_code)]
31#[derive(Debug, Clone)]
32pub struct OcclusionBuffer {
33 pub width: usize,
34 pub height: usize,
35 pub data: Vec<f32>,
36}
37
38#[allow(dead_code)]
39pub fn default_occlusion_map_config() -> OcclusionMapConfig {
40 OcclusionMapConfig {
41 algorithm: AoAlgorithm::Ssao,
42 radius: 0.5,
43 bias: 0.025,
44 intensity: 1.5,
45 sample_count: 16,
46 enabled: true,
47 }
48}
49
50#[allow(dead_code)]
51pub fn new_occlusion_buffer(width: usize, height: usize) -> OcclusionBuffer {
52 OcclusionBuffer {
53 width,
54 height,
55 data: vec![1.0; width * height],
56 }
57}
58
59#[allow(dead_code)]
60pub fn om_set_pixel(buf: &mut OcclusionBuffer, x: usize, y: usize, value: f32) {
61 if x < buf.width && y < buf.height {
62 let idx = y * buf.width + x;
63 buf.data[idx] = value.clamp(0.0, 1.0);
64 }
65}
66
67#[allow(dead_code)]
68pub fn om_get_pixel(buf: &OcclusionBuffer, x: usize, y: usize) -> f32 {
69 if x < buf.width && y < buf.height {
70 buf.data[y * buf.width + x]
71 } else {
72 1.0
73 }
74}
75
76#[allow(dead_code)]
77pub fn om_clear(buf: &mut OcclusionBuffer) {
78 for v in buf.data.iter_mut() {
79 *v = 1.0;
80 }
81}
82
83#[allow(dead_code)]
84pub fn om_average(buf: &OcclusionBuffer) -> f32 {
85 if buf.data.is_empty() {
86 return 1.0;
87 }
88 buf.data.iter().sum::<f32>() / buf.data.len() as f32
89}
90
91#[allow(dead_code)]
92pub fn om_apply_intensity(cfg: &OcclusionMapConfig, raw_ao: f32) -> f32 {
93 if !cfg.enabled {
94 return 1.0;
95 }
96 raw_ao.powf(cfg.intensity).clamp(0.0, 1.0)
97}
98
99#[allow(dead_code)]
100pub fn om_set_intensity(cfg: &mut OcclusionMapConfig, v: f32) {
101 cfg.intensity = v.clamp(0.0, 5.0);
102}
103
104#[allow(dead_code)]
105pub fn om_set_radius(cfg: &mut OcclusionMapConfig, v: f32) {
106 cfg.radius = v.clamp(0.001, 10.0);
107}
108
109#[allow(dead_code)]
110pub fn om_to_json(cfg: &OcclusionMapConfig) -> String {
111 let algo = match cfg.algorithm {
112 AoAlgorithm::Ssao => "ssao",
113 AoAlgorithm::Hbao => "hbao",
114 AoAlgorithm::Gtao => "gtao",
115 AoAlgorithm::BakedTexture => "baked",
116 };
117 format!(
118 r#"{{"algorithm":"{}","radius":{:.4},"intensity":{:.4},"enabled":{}}}"#,
119 algo, cfg.radius, cfg.intensity, cfg.enabled
120 )
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn default_config_enabled() {
129 let cfg = default_occlusion_map_config();
130 assert!(cfg.enabled);
131 }
132
133 #[test]
134 fn new_buffer_all_ones() {
135 let buf = new_occlusion_buffer(4, 4);
136 assert!(buf.data.iter().all(|&v| (v - 1.0).abs() < 1e-6));
137 }
138
139 #[test]
140 fn set_and_get_pixel() {
141 let mut buf = new_occlusion_buffer(4, 4);
142 om_set_pixel(&mut buf, 1, 2, 0.5);
143 assert!((om_get_pixel(&buf, 1, 2) - 0.5).abs() < 1e-6);
144 }
145
146 #[test]
147 fn get_out_of_bounds_returns_one() {
148 let buf = new_occlusion_buffer(2, 2);
149 assert!((om_get_pixel(&buf, 10, 10) - 1.0).abs() < 1e-6);
150 }
151
152 #[test]
153 fn clear_resets() {
154 let mut buf = new_occlusion_buffer(2, 2);
155 om_set_pixel(&mut buf, 0, 0, 0.0);
156 om_clear(&mut buf);
157 assert!((om_average(&buf) - 1.0).abs() < 1e-6);
158 }
159
160 #[test]
161 fn average_computed() {
162 let mut buf = new_occlusion_buffer(2, 1);
163 om_set_pixel(&mut buf, 0, 0, 0.0);
164 om_set_pixel(&mut buf, 1, 0, 1.0);
165 assert!((om_average(&buf) - 0.5).abs() < 1e-6);
166 }
167
168 #[test]
169 fn apply_intensity_disabled() {
170 let mut cfg = default_occlusion_map_config();
171 cfg.enabled = false;
172 assert!((om_apply_intensity(&cfg, 0.3) - 1.0).abs() < 1e-6);
173 }
174
175 #[test]
176 fn set_intensity_clamps() {
177 let mut cfg = default_occlusion_map_config();
178 om_set_intensity(&mut cfg, 100.0);
179 assert!((cfg.intensity - 5.0).abs() < 1e-6);
180 }
181
182 #[test]
183 fn set_radius_clamps() {
184 let mut cfg = default_occlusion_map_config();
185 om_set_radius(&mut cfg, 0.0);
186 assert!(cfg.radius > 0.0);
187 }
188
189 #[test]
190 fn to_json_fields() {
191 let cfg = default_occlusion_map_config();
192 let j = om_to_json(&cfg);
193 assert!(j.contains("algorithm"));
194 assert!(j.contains("intensity"));
195 }
196}