Skip to main content

oxihuman_viewer/
depth_sample.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Depth buffer sampling and linearisation utilities.
6
7use std::f32::consts::FRAC_PI_4;
8
9#[allow(dead_code)]
10#[derive(Debug, Clone)]
11pub struct DepthSampleConfig {
12    pub near: f32,
13    pub far: f32,
14    pub reversed_z: bool,
15}
16
17impl Default for DepthSampleConfig {
18    fn default() -> Self {
19        Self {
20            near: 0.1,
21            far: 1000.0,
22            reversed_z: false,
23        }
24    }
25}
26
27#[allow(dead_code)]
28pub fn default_depth_sample_config() -> DepthSampleConfig {
29    DepthSampleConfig::default()
30}
31
32#[allow(dead_code)]
33pub fn ds_linearize(cfg: &DepthSampleConfig, ndc_depth: f32) -> f32 {
34    let z = ndc_depth.clamp(0.0, 1.0);
35    let z = if cfg.reversed_z { 1.0 - z } else { z };
36    let n = cfg.near;
37    let f = cfg.far;
38    (2.0 * n * f) / (f + n - z * (f - n))
39}
40
41#[allow(dead_code)]
42pub fn ds_ndc_from_linear(cfg: &DepthSampleConfig, linear_depth: f32) -> f32 {
43    let n = cfg.near;
44    let f = cfg.far;
45    let z = (2.0 * n * f / linear_depth - f - n) / (n - f);
46    let z = z.clamp(0.0, 1.0);
47    if cfg.reversed_z {
48        1.0 - z
49    } else {
50        z
51    }
52}
53
54#[allow(dead_code)]
55pub fn ds_depth_range(cfg: &DepthSampleConfig) -> f32 {
56    cfg.far - cfg.near
57}
58
59#[allow(dead_code)]
60pub fn ds_is_valid(cfg: &DepthSampleConfig, ndc_depth: f32) -> bool {
61    (0.0..=1.0).contains(&ndc_depth) && cfg.near > 0.0 && cfg.far > cfg.near
62}
63
64#[allow(dead_code)]
65pub fn ds_perspective_angle_rad(cfg: &DepthSampleConfig) -> f32 {
66    (cfg.near / cfg.far).atan().min(FRAC_PI_4)
67}
68
69#[allow(dead_code)]
70pub fn ds_sample_buffer(cfg: &DepthSampleConfig, buf: &[f32]) -> Vec<f32> {
71    buf.iter().map(|&d| ds_linearize(cfg, d)).collect()
72}
73
74#[allow(dead_code)]
75pub fn ds_average_linear(cfg: &DepthSampleConfig, buf: &[f32]) -> f32 {
76    if buf.is_empty() {
77        return 0.0;
78    }
79    let sum: f32 = buf.iter().map(|&d| ds_linearize(cfg, d)).sum();
80    sum / buf.len() as f32
81}
82
83#[allow(dead_code)]
84pub fn ds_to_json(cfg: &DepthSampleConfig) -> String {
85    format!(
86        "{{\"near\":{:.4},\"far\":{:.4},\"rev\":{}}}",
87        cfg.near, cfg.far, cfg.reversed_z
88    )
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    #[test]
95    fn default_near_far() {
96        let c = default_depth_sample_config();
97        assert!(c.near > 0.0 && c.far > c.near);
98    }
99    #[test]
100    fn linearize_zero_near() {
101        let c = default_depth_sample_config();
102        let l = ds_linearize(&c, 0.0);
103        assert!(l > 0.0);
104    }
105    #[test]
106    fn linearize_one_is_far() {
107        let c = default_depth_sample_config();
108        let l = ds_linearize(&c, 1.0);
109        assert!((l - c.far).abs() / c.far < 0.01);
110    }
111    #[test]
112    fn depth_range_correct() {
113        let c = default_depth_sample_config();
114        assert!((ds_depth_range(&c) - (c.far - c.near)).abs() < 1e-4);
115    }
116    #[test]
117    fn is_valid_mid() {
118        let c = default_depth_sample_config();
119        assert!(ds_is_valid(&c, 0.5));
120    }
121    #[test]
122    fn is_invalid_neg() {
123        let c = default_depth_sample_config();
124        assert!(!ds_is_valid(&c, -0.1));
125    }
126    #[test]
127    fn perspective_angle_nonneg() {
128        assert!(ds_perspective_angle_rad(&default_depth_sample_config()) >= 0.0);
129    }
130    #[test]
131    fn sample_buffer_same_len() {
132        let c = default_depth_sample_config();
133        let b = vec![0.2, 0.5, 0.8];
134        assert_eq!(ds_sample_buffer(&c, &b).len(), b.len());
135    }
136    #[test]
137    fn average_linear_empty_zero() {
138        assert!(ds_average_linear(&default_depth_sample_config(), &[]).abs() < 1e-6);
139    }
140    #[test]
141    fn to_json_has_near() {
142        assert!(ds_to_json(&default_depth_sample_config()).contains("\"near\""));
143    }
144}