use crate::eval::eval;
use crate::types::SdfNode;
use glam::Vec3;
#[derive(Debug, thiserror::Error)]
pub enum VdbError {
#[error("vdb-rs IO error: {0}")]
Io(String),
#[error("invalid grid bounds")]
InvalidBounds,
}
pub fn bake_dense_grid(node: &SdfNode, bounds: (f32, f32), resolution: u32) -> Vec<f32> {
let (min, max) = bounds;
let n = resolution as usize;
let step = (max - min) / (resolution - 1).max(1) as f32;
let mut grid = Vec::with_capacity(n * n * n);
for k in 0..n {
let z = (k as f32) * step + min;
for j in 0..n {
let y = (j as f32) * step + min;
for i in 0..n {
let x = (i as f32) * step + min;
let d = eval(node, Vec3::new(x, y, z));
grid.push(d);
}
}
}
grid
}
pub fn bake_to_vdb(
node: &SdfNode,
bounds: (f32, f32),
resolution: u32,
) -> Result<Vec<u8>, VdbError> {
if bounds.1 <= bounds.0 || resolution == 0 {
return Err(VdbError::InvalidBounds);
}
let _dense = bake_dense_grid(node, bounds, resolution);
let mut buf = Vec::new();
buf.extend_from_slice(b"ALICEVDB1"); buf.extend_from_slice(&resolution.to_le_bytes());
buf.extend_from_slice(&bounds.0.to_le_bytes());
buf.extend_from_slice(&bounds.1.to_le_bytes());
for v in _dense.iter() {
buf.extend_from_slice(&v.to_le_bytes());
}
Ok(buf)
}
pub type DenseGrid = (Vec<f32>, u32, (f32, f32));
pub fn load_dense_grid_from_vdb(bytes: &[u8]) -> Result<DenseGrid, VdbError> {
if bytes.len() < 9 + 4 + 4 + 4 {
return Err(VdbError::Io("buffer too small".into()));
}
if &bytes[0..9] != b"ALICEVDB1" {
return Err(VdbError::Io("bad magic".into()));
}
let resolution = u32::from_le_bytes(bytes[9..13].try_into().unwrap());
let bmin = f32::from_le_bytes(bytes[13..17].try_into().unwrap());
let bmax = f32::from_le_bytes(bytes[17..21].try_into().unwrap());
let n = resolution as usize;
let expected = 21 + n * n * n * 4;
if bytes.len() < expected {
return Err(VdbError::Io("truncated grid data".into()));
}
let mut grid = Vec::with_capacity(n * n * n);
let mut off = 21;
for _ in 0..(n * n * n) {
let v = f32::from_le_bytes(bytes[off..off + 4].try_into().unwrap());
grid.push(v);
off += 4;
}
Ok((grid, resolution, (bmin, bmax)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::SdfNode;
#[test]
fn bake_dense_grid_returns_correct_size() {
let n = SdfNode::sphere(1.0);
let g = bake_dense_grid(&n, (-2.0, 2.0), 8);
assert_eq!(g.len(), 8 * 8 * 8);
}
#[test]
fn bake_dense_grid_centerpoint_is_inside() {
let n = SdfNode::sphere(1.0);
let g = bake_dense_grid(&n, (-2.0, 2.0), 9); let center = g[4 * 9 * 9 + 4 * 9 + 4];
assert!(
center < 0.0,
"centerpoint should be inside sphere: {center}"
);
}
#[test]
fn vdb_roundtrip() {
let n = SdfNode::sphere(1.0);
let bytes = bake_to_vdb(&n, (-2.0, 2.0), 8).unwrap();
let (grid, res, bounds) = load_dense_grid_from_vdb(&bytes).unwrap();
assert_eq!(res, 8);
assert_eq!(bounds, (-2.0, 2.0));
assert_eq!(grid.len(), 8 * 8 * 8);
}
#[test]
fn vdb_invalid_bounds_returns_err() {
let n = SdfNode::sphere(1.0);
assert!(bake_to_vdb(&n, (2.0, -2.0), 8).is_err());
}
}