#![allow(dead_code)]
use std::f32::consts::LN_2;
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub struct DepthRange {
pub near: f32,
pub far: f32,
pub reversed_z: bool,
}
impl Default for DepthRange {
fn default() -> Self {
Self {
near: 0.1,
far: 1000.0,
reversed_z: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[allow(dead_code)]
pub struct DepthPrecisionStats {
pub range_ratio: f32,
pub log_depth_range: f32,
pub recommended_near: f32,
pub bits_needed: u32,
}
#[allow(dead_code)]
pub fn default_depth_range() -> DepthRange {
DepthRange::default()
}
#[allow(dead_code)]
pub fn dr_set_planes(dr: &mut DepthRange, near: f32, far: f32) {
dr.near = near.max(1e-4);
dr.far = far.max(dr.near + 1e-3);
}
#[allow(dead_code)]
pub fn dr_set_reversed(dr: &mut DepthRange, reversed: bool) {
dr.reversed_z = reversed;
}
#[allow(dead_code)]
pub fn depth_range_ratio(dr: &DepthRange) -> f32 {
dr.far / dr.near
}
#[allow(dead_code)]
pub fn linearize_depth_value(depth: f32, dr: &DepthRange) -> f32 {
let d = if dr.reversed_z { 1.0 - depth } else { depth };
let z_ndc = d * 2.0 - 1.0;
2.0 * dr.near * dr.far / (dr.far + dr.near - z_ndc * (dr.far - dr.near))
}
#[allow(dead_code)]
pub fn compute_precision_stats(dr: &DepthRange) -> DepthPrecisionStats {
let ratio = depth_range_ratio(dr);
let log_range = ratio.ln() / LN_2;
let bits_needed = log_range.ceil() as u32;
DepthPrecisionStats {
range_ratio: ratio,
log_depth_range: log_range,
recommended_near: dr.far / 1_000_000.0_f32,
bits_needed,
}
}
#[allow(dead_code)]
pub fn ndc_to_view_depth(z_ndc: f32, dr: &DepthRange) -> f32 {
2.0 * dr.near * dr.far / (dr.far + dr.near - z_ndc * (dr.far - dr.near))
}
#[allow(dead_code)]
pub fn view_to_ndc_depth(view_z: f32, dr: &DepthRange) -> f32 {
let a = -(dr.far + dr.near) / (dr.far - dr.near);
let b = -2.0 * dr.far * dr.near / (dr.far - dr.near);
a + b / view_z
}
#[allow(dead_code)]
pub fn is_valid_depth(depth: f32) -> bool {
(0.0..=1.0).contains(&depth)
}
#[allow(dead_code)]
pub fn depth_range_to_json(dr: &DepthRange) -> String {
format!(
r#"{{"near":{:.4},"far":{:.4},"reversed_z":{}}}"#,
dr.near, dr.far, dr.reversed_z
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_range() {
let dr = default_depth_range();
assert!((dr.near - 0.1).abs() < 1e-6);
assert!((dr.far - 1000.0).abs() < 1e-6);
}
#[test]
fn set_planes() {
let mut dr = default_depth_range();
dr_set_planes(&mut dr, 0.5, 500.0);
assert!((dr.near - 0.5).abs() < 1e-6);
assert!((dr.far - 500.0).abs() < 1e-6);
}
#[test]
fn range_ratio() {
let dr = DepthRange {
near: 1.0,
far: 1000.0,
reversed_z: false,
};
assert!((depth_range_ratio(&dr) - 1000.0).abs() < 1e-4);
}
#[test]
fn linearize_near() {
let dr = DepthRange {
near: 1.0,
far: 100.0,
reversed_z: false,
};
let d = linearize_depth_value(0.0, &dr);
assert!(d.is_finite());
}
#[test]
fn precision_stats_bits() {
let dr = DepthRange {
near: 0.1,
far: 1000.0,
reversed_z: false,
};
let stats = compute_precision_stats(&dr);
assert!(stats.bits_needed > 0);
}
#[test]
fn is_valid_depth_zero() {
assert!(is_valid_depth(0.0));
}
#[test]
fn is_valid_depth_one() {
assert!(is_valid_depth(1.0));
}
#[test]
fn is_valid_depth_out_of_range() {
assert!(!is_valid_depth(1.5));
}
#[test]
fn json_contains_near() {
let dr = default_depth_range();
assert!(depth_range_to_json(&dr).contains("near"));
}
#[test]
fn ln2_used_in_stats() {
let dr = DepthRange {
near: 1.0,
far: 2.0,
reversed_z: false,
};
let stats = compute_precision_stats(&dr);
assert!((stats.log_depth_range - 1.0).abs() < 1e-4);
}
}