use crate::{PositionEncoding, SampleEncoding, encode_voxj_object, hilbert_bits};
use flate2::{Compression, write::DeflateEncoder};
use std::io::Write;
use voxj::{VoxjCodecObject, VoxjSerdeObject};
const MAX_BITMAP_CELLS: u64 = 8_000_000;
const MAX_HILBERT_BITS: u32 = 17;
pub fn encode_voxj_object_smallest(
object: &VoxjCodecObject,
cell_counts: &[usize],
) -> VoxjSerdeObject {
if object.positions.is_empty() {
return encode_voxj_object(
object,
cell_counts,
PositionEncoding::RawJson,
SampleEncoding::RawJson,
);
}
candidate_positions(object.bounds)
.into_iter()
.flat_map(|position| {
[SampleEncoding::RleJson, SampleEncoding::PackedBase64].map(|sample| (position, sample))
})
.map(|(position, sample)| encode_voxj_object(object, cell_counts, position, sample))
.min_by_key(deflated_len)
.expect("at least one candidate")
}
fn candidate_positions(bounds: [u32; 3]) -> Vec<PositionEncoding> {
let mut positions = Vec::new();
let cells = bounds[0] as u64 * bounds[1] as u64 * bounds[2] as u64;
if cells <= MAX_BITMAP_CELLS {
positions.push(PositionEncoding::BitmapBase64);
}
if hilbert_bits(bounds) <= MAX_HILBERT_BITS {
positions.push(PositionEncoding::Hilbert);
}
if positions.is_empty() {
positions.push(PositionEncoding::RawJson);
}
positions
}
fn deflated_len(object: &VoxjSerdeObject) -> usize {
let Ok(json) = serde_json::to_vec(&(&object.voxel_positions, &object.voxel_samples)) else {
return usize::MAX;
};
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
let _ = encoder.write_all(&json);
encoder.finish().map_or(usize::MAX, |v| v.len())
}
#[cfg(test)]
mod tests {
use crate::encode_voxj_object_smallest;
use voxj::{VoxjCodecObject, VoxjSerdeSampleBlock};
#[test]
fn zero_palette_object_keeps_sample_arity() {
let object = encode_voxj_object_smallest(
&VoxjCodecObject {
name: "o".to_owned(),
palette_refs: Vec::new(),
bounds: [3, 1, 1],
positions: vec![[0, 0, 0], [1, 0, 0], [2, 0, 0]],
samples: vec![Vec::new(), Vec::new(), Vec::new()],
},
&[],
);
match &object.voxel_samples {
VoxjSerdeSampleBlock::RawJson(rows) => assert_eq!(rows.len(), 3),
VoxjSerdeSampleBlock::RleJson(channels) => assert!(channels.is_empty()),
VoxjSerdeSampleBlock::PackedBase64(channels) => assert!(channels.is_empty()),
}
}
}