Skip to main content

petaplot_core/lod/
pyramid.rs

1use crate::compute::simd::MinMaxPair;
2use crate::error::{Result, TeraError};
3
4/// Factor de reducción entre niveles LOD consecutivos (10x por defecto).
5pub const DEFAULT_LOD_FACTOR: usize = 10;
6
7/// Representación de un único nivel de detalle en la pirámide.
8#[derive(Debug, Clone)]
9pub struct LodLevel {
10    pub level_index: usize,
11    pub step_factor: usize,
12    pub pairs: Vec<MinMaxPair>,
13}
14
15/// Pirámide de Niveles de Detalle (LOD) logarítmica.
16#[derive(Debug, Clone)]
17pub struct LodPyramid {
18    pub total_samples: usize,
19    pub factor: usize,
20    pub levels: Vec<LodLevel>,
21}
22
23impl LodPyramid {
24    /// Crea una pirámide vacía.
25    pub fn new(total_samples: usize, factor: usize) -> Self {
26        Self {
27            total_samples,
28            factor: factor.max(2),
29            levels: Vec::new(),
30        }
31    }
32
33    /// Retorna la cantidad de niveles generados en la pirámide.
34    pub fn num_levels(&self) -> usize {
35        self.levels.len()
36    }
37
38    /// Selecciona el índice del nivel LOD óptimo para renderizar según las muestras visibles y el ancho en píxeles de la pantalla.
39    pub fn select_optimal_lod(&self, visible_samples: usize, screen_width_px: f32, oversampling: f32) -> usize {
40        if self.levels.is_empty() || visible_samples == 0 || screen_width_px <= 0.0 {
41            return 0;
42        }
43
44        let target_bins = screen_width_px * oversampling.max(1.0);
45        let ratio = visible_samples as f32 / target_bins;
46
47        if ratio <= 1.0 {
48            return 0;
49        }
50
51        let lod_float = ratio.log(self.factor as f32).floor();
52        let target_lod = lod_float as usize;
53
54        target_lod.min(self.levels.len() - 1)
55    }
56
57    /// Obtiene el nivel LOD solicitado de forma segura.
58    pub fn get_level(&self, level_index: usize) -> Result<&LodLevel> {
59        self.levels.get(level_index).ok_or_else(|| {
60            TeraError::Lod(format!(
61                "Nivel LOD {} no existe en la pirámide (niveles totales: {})",
62                level_index,
63                self.levels.len()
64            ))
65        })
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_lod_level_selection() {
75        let mut pyramid = LodPyramid::new(1_000_000, 10);
76        pyramid.levels.push(LodLevel { level_index: 0, step_factor: 1, pairs: vec![] });
77        pyramid.levels.push(LodLevel { level_index: 1, step_factor: 10, pairs: vec![] });
78        pyramid.levels.push(LodLevel { level_index: 2, step_factor: 100, pairs: vec![] });
79        pyramid.levels.push(LodLevel { level_index: 3, step_factor: 1000, pairs: vec![] });
80
81        let selected = pyramid.select_optimal_lod(100_000, 1000.0, 1.0);
82        assert_eq!(selected, 2);
83
84        let selected_zoom = pyramid.select_optimal_lod(500, 1000.0, 1.0);
85        assert_eq!(selected_zoom, 0);
86    }
87}