A compressed voxel data library.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

638 lines
16 KiB

use bzvx::*;
use bzvx::voxelize::*;
use bzvx::voxelize::Triangle;
use cgmath::InnerSpace;
use pbr::ProgressBar;
use noise;
use noise::NoiseFn;
use std::path::PathBuf;
#[test]
fn test_voxel_dag_obj_shell_buff_doge() {
println!("Converting {:?}", "./data/obj/BuffDoge.OBJ");
convert_obj_file(
PathBuf::from("./data/obj/BuffDoge.OBJ"),
PathBuf::from("./data/dag/BuffDoge.svdag"),
12
);
println!("");
println!("Converting {:?}", "./data/obj/BuffDoge.OBJ");
convert_obj_file(
PathBuf::from("./data/obj/MegaBuffDoge.OBJ"),
PathBuf::from("./data/dag/MegaBuffDoge.svdag"),
12
);
println!("");
println!("Converting {:?}", "./data/obj/BuffDoge.OBJ");
convert_obj_file(
PathBuf::from("./data/obj/Cheem.OBJ"),
PathBuf::from("./data/dag/Cheem.svdag"),
12
);
}
#[test]
fn test_voxel_dag_obj_shell_teapot() {
convert_obj_file(
PathBuf::from("./data/obj/teapot.obj"),
PathBuf::from("./data/dag/teapot.svdag"),
12
);
}
#[test]
fn test_voxel_dag_obj_shell_sponza() {
convert_obj_file_with_materials(
PathBuf::from("./data/obj/Sponza/sponza.obj"),
PathBuf::from("./data/dag/sponza_mats.svdag"),
PathBuf::from("./data/dag/sponza_mats.mats"),
12
);
}
#[test]
fn test_voxel_dag_obj_shell_sponza_textured() {
convert_obj_file_textured(
PathBuf::from("./data/obj/sponza-modified/sponza.obj"),
PathBuf::from("./data/dag/sponza_tex_1k.svdag"),
PathBuf::from("./data/dag/sponza_tex_1k.mats"),
10
);
}
#[test]
fn test_voxel_dag_obj_shell_sibenik() {
convert_obj_file_with_materials(
PathBuf::from("./data/obj/sibenik/sibenik.obj"),
PathBuf::from("./data/dag/sibenik_mats.svdag"),
PathBuf::from("./data/dag/sibenik_mats.mats"),
10
);
}
#[test]
fn test_voxel_dag_obj_shell_hairball() {
convert_obj_file(
PathBuf::from("./data/obj/hairball.obj"),
PathBuf::from("./data/dag/hairball.svdag"),
9
);
}
#[test]
fn test_voxel_dag_tri_shell() {
use std::path::Path;
use std::fs;
let v0 = Vec3::new(1.0, 0.0, 0.0);
let v1 = Vec3::new(0.0, 1.0, 0.0);
let v2 = Vec3::new(0.0, 0.0, 1.0);
let triangles = vec![
Triangle{
points : [v0, v1, v2],
normal : (v0 - v1).cross(v1 - v2),
mat : 1,
..Default::default()
}
];
let min = Vec3::new(0.0, 0.0, 0.0);
let size = 1.0;
println!("Triangles: {}", triangles.len());
use std::time::*;
let mut pb = ProgressBar::new(8*8*8*8);
let start = Instant::now();
let vchunk = VoxelChunk::from_mesh(8, &triangles, min, size, &mut |t| { pb.total = t; pb.inc(); });
let elapsed = start.elapsed();
pb.finish();
println!("DAG nodes: {}", vchunk.len());
println!("Time to assemble: {:?}", elapsed);
let serialized = bincode::serialize(&vchunk).unwrap();
fs::write("./data/dag/tri.svdag", serialized).unwrap();
}
/// Construct an SVDAG of a ct-scan of the stanford bunny;
#[test]
fn test_voxel_dag_bunny() {
let mut data : Vec<u16> = Vec::with_capacity(512 * 512 * 361);
use std::path::Path;
use std::fs;
use std::u16;
use std::time::*;
let dir = Path::new("./data/dense/bunny/");
if dir.is_dir() {
for entry in fs::read_dir(dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if !path.is_dir() {
println!("Loading {:?}", path);
let slice = fs::read(path).unwrap();
assert_eq!(slice.len(), 512*512*2);
data.append(&mut slice.chunks_exact(2).map(|v| u16::from_be_bytes([v[0],v[1]])).collect::<Vec<_>>());
}
}
}
assert_eq!(data.len(), 512 * 512 * 361);
println!("Converting...");
// scan data has a solid cylinder around the bunny, so this code removes that.
for z in 0..361 {
for y in 0..512 {
for x in 0..512 {
let dx = x as i32 - 256;
let dy = y as i32 - 256;
let i = x + 512 * (y + 512 * z);
if dx * dx + dy * dy > 255 * 255 {
data[i] = 0;
}
}
}
}
// threshold for the ct scan data
let data = data.iter().map(|&v| if v > 0x06ff {1i32} else {0i32}).collect::<Vec<i32>>();
let start = Instant::now();
let mut chunk = VoxelChunk::from_dense_voxels(&data, [512, 512, 361]);
let runtime = start.elapsed();
println!("Compression took {:?}", runtime);
chunk.topological_sort();
let out_path = Path::new("./data/dag/bunny.svdag");
println!("Writing File... ({:?})", out_path);
use bincode;
let serialized = bincode::serialize(&chunk).unwrap();
fs::write(out_path, serialized).unwrap();
println!("Num Voxels: {} (from {})", chunk.voxels.len(), 512*512*361);
}
#[test]
fn test_voxel_dag_implicit() {
use std::time::*;
use std::fs;
use bincode;
use std::path::*;
println!("Compressing implicit gyroid...");
let start = Instant::now();
let chunk = VoxelChunk::from_implicit_array(8, |x, y, z| {
let scale = 1.0/16.0;
let threshold = 0.05;
let x = x as f32 * scale;
let y = y as f32 * scale;
let z = z as f32 * scale;
let sdf = x.sin() * y.cos() + y.sin() * z.cos() + z.sin() * x.cos();
if sdf.abs() < threshold {
1
} else {
0
}
});
let runtime = start.elapsed();
println!("Compression took {:?}", runtime);
let out_path = Path::new("./data/dag/gyroid.svdag");
println!("Writing File... ({:?})", out_path);
let serialized = bincode::serialize(&chunk).unwrap();
fs::write(out_path, serialized).unwrap();
println!("Num Voxels: {} (from {})", chunk.voxels.len(), 512*512*512);
}
#[test]
fn test_voxel_dag_de_gyroid() {
use std::time::*;
use std::fs;
use bincode;
use std::path::*;
println!("Compressing DE gyroid...");
let start = Instant::now();
let chunk = VoxelChunk::from_distance_equation(10, |x, y, z| {
let scale = std::f32::consts::PI * 4.0;
let x = x as f32 * scale;
let y = y as f32 * scale;
let z = z as f32 * scale;
let sdf = x.sin() * y.cos() + y.sin() * z.cos() + z.sin() * x.cos();
sdf.abs() / scale
});
let runtime = start.elapsed();
println!("Compression took {:?}", runtime);
println!("Num Voxels: {} (uncompress: {})", chunk.voxels.len(), 512*512*512);
assert!(!chunk.detect_cycles(), "Cycle Detected!");
let out_path = Path::new("./data/dag/gyroid_de.svdag");
println!("Writing File... ({:?})", out_path);
let serialized = bincode::serialize(&chunk).unwrap();
fs::write(out_path, serialized).unwrap();
}
#[test]
fn test_voxel_dag_intersection_sphere() {
use std::time::*;
use std::fs;
use bincode;
use std::path::*;
println!("Creating SVDAG from sphere intersection test...");
let start = Instant::now();
let sc = Vec3::new(0.5, 0.5, 0.5);
let sr = 0.01;
let depth = 10;
let uncompressed_size = (1 << depth) * (1 << depth) * (1 << depth);
let chunk = VoxelChunk::from_intersection_test(depth, |v, s| {
let mut sq_dist = 0.0;
if sc.x < v.x - s { sq_dist += (v.x - s - sc.x).powi(2); }
if sc.x > v.x + s { sq_dist += (v.x + s - sc.x).powi(2); }
if sc.y < v.y - s { sq_dist += (v.y - s - sc.y).powi(2); }
if sc.y > v.y + s { sq_dist += (v.y + s - sc.y).powi(2); }
if sc.z < v.z - s { sq_dist += (v.z - s - sc.z).powi(2); }
if sc.z > v.z + s { sq_dist += (v.z + s - sc.z).powi(2); }
sq_dist < sr * sr
});
let runtime = start.elapsed();
println!("Compression took {:?}", runtime);
println!("Num Voxels: {} (uncompressed: {} - ) [{:3.3}%]", chunk.voxels.len(), uncompressed_size, 100.0 * chunk.voxels.len() as f32 / uncompressed_size as f32);
assert!(!chunk.detect_cycles(), "Cycle Detected!");
let out_path = Path::new("./data/dag/sphere.svdag");
println!("Writing File... ({:?})", out_path);
let serialized = bincode::serialize(&chunk).unwrap();
fs::write(out_path, serialized).unwrap();
}
#[test]
fn test_voxel_dag_de_mandelbulb() {
use std::time::*;
use std::fs;
use bincode;
use std::path::*;
println!("Compressing DE mandelbulb...");
let start = Instant::now();
const DEPTH : usize = 11;
const DIM : usize = 1 << DEPTH;
let chunk = VoxelChunk::from_distance_equation(DEPTH, |x, y, z| {
const SCALE : f32 = 4.0;
let pos = Vec3::new(x - 0.5, y - 0.5, z - 0.5) * SCALE;
let mut z = pos;
let mut dr = 1.0;
let mut r = 0.0;
const ITERS : usize = 8;
const POWER : f32 = 8.0;
const BAILOUT : f32 = 2.0;
for _ in 0..ITERS {
r = z.magnitude(); //length(z);
if r>BAILOUT {
break
};
// convert to polar coordinates
let mut theta = (z.z/r).acos();
let mut phi = (z.y).atan2(z.x);
dr = r.powf( POWER -1.0) * POWER * dr + 1.0;
// scale and rotate the point
let zr = r.powf(POWER);
theta = theta*POWER;
phi = phi*POWER;
// convert back to cartesian coordinates
z = zr*Vec3::new(theta.sin()*phi.cos(), phi.sin()*theta.sin(), theta.cos());
z += pos;
}
return 0.5*r.ln()*r/dr / SCALE;
});
let runtime = start.elapsed();
println!("Compression took {:?}", runtime);
println!("Num Voxels: {} (uncompressed: {} ({:2.1}%))", chunk.voxels.len(), DIM*DIM*DIM,chunk.voxels.len() as f32 / (DIM*DIM*DIM) as f32);
assert!(!chunk.detect_cycles(), "Cycle Detected!");
let out_path = Path::new("./data/dag/mandelbulb.svdag");
println!("Writing File... ({:?})", out_path);
let serialized = bincode::serialize(&chunk).unwrap();
fs::write(out_path, serialized).unwrap();
}
/// This test constructs a simple sphere as a test
#[test]
fn test_voxel_dag_sphere() {
use std::path::Path;
use std::fs;
let data : [i32; 8*8*8]= [
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
];
let chunk = VoxelChunk::from_dense_voxels(&data, [8, 8, 8]);
let out_path = Path::new("./data/dag/sphere.svdag");
{
use bincode;
use serde_json::to_string_pretty;
let serialized = bincode::serialize(&chunk).unwrap();
fs::write(out_path, serialized).unwrap();
println!("{}", to_string_pretty(&chunk).unwrap());
}
println!("Num Voxels: {} (from {})", chunk.voxels.len(), 8*8*8);
}
/// This test constructs a simple sphere as a test
#[test]
fn test_voxel_dag_checkers() {
use std::path::Path;
use std::fs;
for i in 1..6 {
let d = 1 << i;
let data : Vec<i32> = (0..d).map(|z| {
(0..d).map(move |y| {
(0..d).map(move |x| (x + y + z) % 2)
}).flatten()
}).flatten().collect();
let chunk = VoxelChunk::from_dense_voxels(&data, [d as usize, d as usize, d as usize]);
let path = format!("./data/dag/checker{:0>2}.svdag", d);
let out_path = Path::new(&path);
{
use bincode;
use serde_json::to_string_pretty;
let serialized = bincode::serialize(&chunk).unwrap();
fs::write(out_path, serialized).unwrap();
println!("Checker (d={})", d);
println!("{}", to_string_pretty(&chunk).unwrap());
println!("Num Voxels: {} (from {})", chunk.voxels.len(), d*d*d);
}
}
}
#[test]
fn test_voxel_dag_octohedron() {
use std::path::Path;
use std::fs;
for i in 1..6 {
let d = 1 << i;
let dd = d >> 1;
let data : Vec<i32> = (0..d).map(|z| {
(0..d).map(move |y| {
(0..d).map(move |x| if (x == dd || x == dd - 1) && (y == dd || y == dd - 1) && (z == dd || z == dd - 1) {1} else {0})
}).flatten()
}).flatten().collect();
let chunk = VoxelChunk::from_dense_voxels(&data, [d as usize, d as usize, d as usize]);
let path = format!("./data/dag/octohedron{:0>2}.svdag", d);
let out_path = Path::new(&path);
{
use bincode;
use serde_json::to_string_pretty;
let serialized = bincode::serialize(&chunk).unwrap();
fs::write(out_path, serialized).unwrap();
println!("Checker (d={})", d);
println!("{}", to_string_pretty(&chunk).unwrap());
println!("Num Voxels: {} (from {})", chunk.voxels.len(), d*d*d);
}
}
}
#[test]
fn test_voxel_index_distance() {
let data = std::fs::read("./data/dag/hairball.svdag").expect("could not read file");
let mut chunk : VoxelChunk = bincode::deserialize(&data).expect("error deserializing voxels");
let mut n = 0usize;
let mut avg = 0.0;
let mut avg_sq = 0.0;
let mut max = 0.0;
let mut min = chunk.voxels.len() as f64;
let mut n_less_16bit = 0;
chunk.topological_sort();
for i in 0..(chunk.voxels.len()) {
for j in 0..8 {
let v = chunk.voxels[i].sub_voxels[j];
if v > 0 {
let d = (v as isize - 1 - i as isize).abs() ;
if d < (1<<16) {
n_less_16bit += 1;
}
let d = d as f64;
max = if d > max {d} else {max};
min = if d < min {d} else {min};
avg += d;
avg_sq += d*d;
n += 1;
}
}
}
avg /= n as f64;
avg_sq /= n as f64;
println!("AVG: {}", avg);
println!("STD: {}", (avg_sq - avg*avg).sqrt());
println!("MAX: {}", max);
println!("MIN: {}", min);
println!("16B: {}", n_less_16bit);
println!("LEN: {}", n);
}