pub mod bake;
pub mod export;
pub mod mipchain;
#[cfg(feature = "gpu")]
pub mod gpu_bake;
use glam::Vec3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BakeChannels {
Distance,
DistanceGradient,
DistanceMaterial,
}
#[derive(Debug, Clone)]
pub struct BakeConfig {
pub resolution: [u32; 3],
pub bounds_min: Vec3,
pub bounds_max: Vec3,
pub channels: BakeChannels,
pub generate_mips: bool,
pub padding: u32,
}
impl Default for BakeConfig {
fn default() -> Self {
Self {
resolution: [64, 64, 64],
bounds_min: Vec3::splat(-2.0),
bounds_max: Vec3::splat(2.0),
channels: BakeChannels::Distance,
generate_mips: false,
padding: 0,
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct VoxelDistGrad {
pub distance: f32,
pub nx: f32,
pub ny: f32,
pub nz: f32,
}
#[cfg(feature = "gpu")]
unsafe impl bytemuck::Pod for VoxelDistGrad {}
#[cfg(feature = "gpu")]
unsafe impl bytemuck::Zeroable for VoxelDistGrad {}
#[derive(Debug, Clone)]
pub struct Volume3D<T: Copy + Default> {
pub data: Vec<T>,
pub resolution: [u32; 3],
pub world_min: Vec3,
pub world_max: Vec3,
pub mips: Vec<Vec<T>>,
}
impl<T: Copy + Default> Volume3D<T> {
pub fn new(resolution: [u32; 3], world_min: Vec3, world_max: Vec3) -> Self {
let total = resolution[0] as usize * resolution[1] as usize * resolution[2] as usize;
Self {
data: vec![T::default(); total],
resolution,
world_min,
world_max,
mips: Vec::new(),
}
}
#[inline(always)]
pub const fn voxel_count(&self) -> usize {
self.resolution[0] as usize * self.resolution[1] as usize * self.resolution[2] as usize
}
#[inline(always)]
pub fn world_size(&self) -> Vec3 {
self.world_max - self.world_min
}
#[inline(always)]
pub fn voxel_size(&self) -> Vec3 {
let size = self.world_size();
Vec3::new(
size.x / (self.resolution[0] as f32 - 1.0).max(1.0),
size.y / (self.resolution[1] as f32 - 1.0).max(1.0),
size.z / (self.resolution[2] as f32 - 1.0).max(1.0),
)
}
#[inline(always)]
pub const fn index(&self, x: u32, y: u32, z: u32) -> usize {
x as usize
+ y as usize * self.resolution[0] as usize
+ z as usize * self.resolution[0] as usize * self.resolution[1] as usize
}
#[inline(always)]
pub fn get(&self, x: u32, y: u32, z: u32) -> T {
self.data[self.index(x, y, z)]
}
#[inline(always)]
pub fn set(&mut self, x: u32, y: u32, z: u32, value: T) {
let idx = self.index(x, y, z);
self.data[idx] = value;
}
#[inline(always)]
pub fn voxel_to_world(&self, x: u32, y: u32, z: u32) -> Vec3 {
let step = self.voxel_size();
Vec3::new(
(x as f32).mul_add(step.x, self.world_min.x),
(y as f32).mul_add(step.y, self.world_min.y),
(z as f32).mul_add(step.z, self.world_min.z),
)
}
pub fn mip_count(&self) -> usize {
self.mips.len() + 1 }
}
impl Volume3D<f32> {
pub fn sample_trilinear(&self, pos: Vec3) -> f32 {
let size = self.world_size();
let res = [
self.resolution[0] as f32,
self.resolution[1] as f32,
self.resolution[2] as f32,
];
let fx = ((pos.x - self.world_min.x) / size.x * (res[0] - 1.0)).clamp(0.0, res[0] - 1.0);
let fy = ((pos.y - self.world_min.y) / size.y * (res[1] - 1.0)).clamp(0.0, res[1] - 1.0);
let fz = ((pos.z - self.world_min.z) / size.z * (res[2] - 1.0)).clamp(0.0, res[2] - 1.0);
let x0 = fx as u32;
let y0 = fy as u32;
let z0 = fz as u32;
let x1 = (x0 + 1).min(self.resolution[0] - 1);
let y1 = (y0 + 1).min(self.resolution[1] - 1);
let z1 = (z0 + 1).min(self.resolution[2] - 1);
let tx = fx - x0 as f32;
let ty = fy - y0 as f32;
let tz = fz - z0 as f32;
let c000 = self.get(x0, y0, z0);
let c100 = self.get(x1, y0, z0);
let c010 = self.get(x0, y1, z0);
let c110 = self.get(x1, y1, z0);
let c001 = self.get(x0, y0, z1);
let c101 = self.get(x1, y0, z1);
let c011 = self.get(x0, y1, z1);
let c111 = self.get(x1, y1, z1);
let c00 = c000 * (1.0 - tx) + c100 * tx;
let c10 = c010 * (1.0 - tx) + c110 * tx;
let c01 = c001 * (1.0 - tx) + c101 * tx;
let c11 = c011 * (1.0 - tx) + c111 * tx;
let c0 = c00 * (1.0 - ty) + c10 * ty;
let c1 = c01 * (1.0 - ty) + c11 * ty;
c0 * (1.0 - tz) + c1 * tz
}
}
impl Volume3D<VoxelDistGrad> {
pub fn sample_trilinear(&self, pos: Vec3) -> VoxelDistGrad {
let size = self.world_size();
let res = [
self.resolution[0] as f32,
self.resolution[1] as f32,
self.resolution[2] as f32,
];
let fx = ((pos.x - self.world_min.x) / size.x * (res[0] - 1.0)).clamp(0.0, res[0] - 1.0);
let fy = ((pos.y - self.world_min.y) / size.y * (res[1] - 1.0)).clamp(0.0, res[1] - 1.0);
let fz = ((pos.z - self.world_min.z) / size.z * (res[2] - 1.0)).clamp(0.0, res[2] - 1.0);
let x0 = fx as u32;
let y0 = fy as u32;
let z0 = fz as u32;
let x1 = (x0 + 1).min(self.resolution[0] - 1);
let y1 = (y0 + 1).min(self.resolution[1] - 1);
let z1 = (z0 + 1).min(self.resolution[2] - 1);
let tx = fx - x0 as f32;
let ty = fy - y0 as f32;
let tz = fz - z0 as f32;
let corners = [
self.get(x0, y0, z0),
self.get(x1, y0, z0),
self.get(x0, y1, z0),
self.get(x1, y1, z0),
self.get(x0, y0, z1),
self.get(x1, y0, z1),
self.get(x0, y1, z1),
self.get(x1, y1, z1),
];
let lerp = |a: f32, b: f32, t: f32| a.mul_add(1.0 - t, b * t);
let interp_component = |f: fn(&VoxelDistGrad) -> f32| -> f32 {
let c00 = lerp(f(&corners[0]), f(&corners[1]), tx);
let c10 = lerp(f(&corners[2]), f(&corners[3]), tx);
let c01 = lerp(f(&corners[4]), f(&corners[5]), tx);
let c11 = lerp(f(&corners[6]), f(&corners[7]), tx);
let c0 = lerp(c00, c10, ty);
let c1 = lerp(c01, c11, ty);
lerp(c0, c1, tz)
};
VoxelDistGrad {
distance: interp_component(|v| v.distance),
nx: interp_component(|v| v.nx),
ny: interp_component(|v| v.ny),
nz: interp_component(|v| v.nz),
}
}
}
pub use bake::{bake_volume, bake_volume_with_normals};
pub use export::{export_dds_3d, export_raw};
pub use mipchain::generate_mip_chain;
#[cfg(feature = "gpu")]
pub use gpu_bake::gpu_bake_volume;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_volume_creation() {
let vol: Volume3D<f32> = Volume3D::new([4, 4, 4], Vec3::splat(-1.0), Vec3::splat(1.0));
assert_eq!(vol.voxel_count(), 64);
assert_eq!(vol.data.len(), 64);
}
#[test]
fn test_volume_indexing() {
let mut vol: Volume3D<f32> = Volume3D::new([4, 4, 4], Vec3::splat(-1.0), Vec3::splat(1.0));
vol.set(1, 2, 3, 42.0);
assert_eq!(vol.get(1, 2, 3), 42.0);
assert_eq!(vol.index(1, 2, 3), 1 + 2 * 4 + 3 * 16);
}
#[test]
fn test_trilinear_sampling_corners() {
let mut vol: Volume3D<f32> = Volume3D::new([2, 2, 2], Vec3::ZERO, Vec3::ONE);
vol.set(0, 0, 0, 0.0);
vol.set(1, 0, 0, 1.0);
vol.set(0, 1, 0, 0.0);
vol.set(1, 1, 0, 1.0);
vol.set(0, 0, 1, 0.0);
vol.set(1, 0, 1, 1.0);
vol.set(0, 1, 1, 0.0);
vol.set(1, 1, 1, 1.0);
let d = vol.sample_trilinear(Vec3::ZERO);
assert!((d - 0.0).abs() < 1e-5);
let d = vol.sample_trilinear(Vec3::ONE);
assert!((d - 1.0).abs() < 1e-5);
let d = vol.sample_trilinear(Vec3::splat(0.5));
assert!((d - 0.5).abs() < 1e-5);
}
#[test]
fn test_trilinear_clamping() {
let vol: Volume3D<f32> = Volume3D::new([4, 4, 4], Vec3::splat(-1.0), Vec3::splat(1.0));
let _ = vol.sample_trilinear(Vec3::splat(-10.0));
let _ = vol.sample_trilinear(Vec3::splat(10.0));
}
#[test]
fn test_voxel_to_world() {
let vol: Volume3D<f32> = Volume3D::new([3, 3, 3], Vec3::splat(-1.0), Vec3::splat(1.0));
let p = vol.voxel_to_world(0, 0, 0);
assert!((p - Vec3::splat(-1.0)).length() < 1e-5);
let p = vol.voxel_to_world(2, 2, 2);
assert!((p - Vec3::splat(1.0)).length() < 1e-5);
let p = vol.voxel_to_world(1, 1, 1);
assert!((p - Vec3::ZERO).length() < 1e-5);
}
}