use crate::error::{NerfError, NerfResult};
use crate::rendering::ray::Ray;
#[derive(Debug, Clone)]
pub struct OccupancyGrid {
pub data: Vec<bool>,
pub resolution: usize,
pub scene_bound: f32,
}
impl OccupancyGrid {
pub fn new(resolution: usize, scene_bound: f32) -> NerfResult<Self> {
if resolution == 0 {
return Err(NerfError::InvalidGridResolution { res: 0 });
}
let total = resolution * resolution * resolution;
Ok(Self {
data: vec![false; total],
resolution,
scene_bound,
})
}
#[inline]
fn voxel_index(&self, ix: usize, iy: usize, iz: usize) -> usize {
ix * self.resolution * self.resolution + iy * self.resolution + iz
}
pub fn set(&mut self, ix: usize, iy: usize, iz: usize, occupied: bool) -> NerfResult<()> {
if ix >= self.resolution || iy >= self.resolution || iz >= self.resolution {
return Err(NerfError::HashLevelOutOfRange {
level: ix.max(iy).max(iz),
});
}
let idx = self.voxel_index(ix, iy, iz);
self.data[idx] = occupied;
Ok(())
}
pub fn get(&self, ix: usize, iy: usize, iz: usize) -> NerfResult<bool> {
if ix >= self.resolution || iy >= self.resolution || iz >= self.resolution {
return Err(NerfError::HashLevelOutOfRange {
level: ix.max(iy).max(iz),
});
}
Ok(self.data[self.voxel_index(ix, iy, iz)])
}
#[must_use]
pub fn is_occupied_world(&self, xyz: [f32; 3]) -> bool {
let bound = self.scene_bound;
if xyz[0] < -bound
|| xyz[0] > bound
|| xyz[1] < -bound
|| xyz[1] > bound
|| xyz[2] < -bound
|| xyz[2] > bound
{
return false;
}
let res = self.resolution as f32;
let to_idx = |v: f32| -> usize {
let norm = (v + bound) / (2.0 * bound);
(norm * res).floor().clamp(0.0, res - 1.0) as usize
};
let ix = to_idx(xyz[0]);
let iy = to_idx(xyz[1]);
let iz = to_idx(xyz[2]);
self.data[self.voxel_index(ix, iy, iz)]
}
pub fn update_from_density(&mut self, density: &[f32], threshold: f32) -> NerfResult<()> {
let expected = self.resolution * self.resolution * self.resolution;
if density.len() != expected {
return Err(NerfError::DimensionMismatch {
expected,
got: density.len(),
});
}
for (occ, &den) in self.data.iter_mut().zip(density.iter()) {
*occ = den > threshold;
}
Ok(())
}
#[must_use]
pub fn march_ray_occupied(
&self,
ray: &Ray,
t_near: f32,
t_far: f32,
step_size: f32,
) -> Vec<f32> {
if step_size <= 0.0 || t_far <= t_near {
return Vec::new();
}
let mut t = t_near;
let mut occupied_t = Vec::new();
while t <= t_far {
let pt = ray.at(t);
if self.is_occupied_world(pt) {
occupied_t.push(t);
}
t += step_size;
}
occupied_t
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn occupancy_set_get() {
let mut grid = OccupancyGrid::new(8, 1.0).unwrap();
grid.set(2, 3, 4, true).unwrap();
assert!(grid.get(2, 3, 4).unwrap());
assert!(!grid.get(0, 0, 0).unwrap());
}
#[test]
fn world_query_inside_bound() {
let mut grid = OccupancyGrid::new(4, 1.0).unwrap();
grid.set(2, 2, 2, true).unwrap();
assert!(grid.is_occupied_world([0.25, 0.25, 0.25]));
}
#[test]
fn update_from_density() {
let mut grid = OccupancyGrid::new(2, 1.0).unwrap();
let density = vec![0.1, 0.2, 0.5, 0.8, 0.0, 0.9, 0.3, 0.7];
grid.update_from_density(&density, 0.4).unwrap();
assert!(!grid.data[0]);
assert!(grid.data[3]);
assert!(grid.data[5]);
assert!(grid.data[7]);
}
}