voxj_codec/
encode_voxj_object_smallest.rs1use crate::{PositionEncoding, Result, SampleEncoding, encode_voxj_object, hilbert_bits};
2use flate2::{Compression, write::DeflateEncoder};
3use std::io::Write;
4use voxj::{VoxjCodecObject, VoxjSerdeObject};
5
6const MAX_BITMAP_CELLS: u64 = 8_000_000;
8
9const MAX_HILBERT_BITS: u32 = 17;
12
13pub fn encode_voxj_object_smallest(
20 object: &VoxjCodecObject,
21 cell_counts: &[usize],
22) -> Result<VoxjSerdeObject> {
23 if object.positions.is_empty() {
24 return encode_voxj_object(
25 object,
26 cell_counts,
27 PositionEncoding::RawJson,
28 SampleEncoding::RawJson,
29 );
30 }
31 let smallest = candidate_positions(object.bounds)
32 .into_iter()
33 .flat_map(|position| {
34 [SampleEncoding::RleJson, SampleEncoding::PackedBase64].map(|sample| (position, sample))
35 })
36 .map(|(position, sample)| encode_voxj_object(object, cell_counts, position, sample))
37 .collect::<Result<Vec<_>>>()?
38 .into_iter()
39 .min_by_key(deflated_len)
40 .expect("at least one candidate");
41 Ok(smallest)
42}
43
44fn candidate_positions(bounds: [u32; 3]) -> Vec<PositionEncoding> {
47 let mut positions = Vec::new();
48
49 let cells = bounds[0] as u64 * bounds[1] as u64 * bounds[2] as u64;
50 if cells <= MAX_BITMAP_CELLS {
51 positions.push(PositionEncoding::BitmapBase64);
52 }
53
54 if hilbert_bits(bounds) <= MAX_HILBERT_BITS {
55 positions.push(PositionEncoding::Hilbert);
56 }
57
58 if positions.is_empty() {
59 positions.push(PositionEncoding::RawJson);
60 }
61
62 positions
63}
64
65fn deflated_len(object: &VoxjSerdeObject) -> usize {
67 let Ok(json) = serde_json::to_vec(&(&object.voxel_positions, &object.voxel_samples)) else {
68 return usize::MAX;
69 };
70 let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
71 let _ = encoder.write_all(&json);
72 encoder.finish().map_or(usize::MAX, |v| v.len())
73}
74
75#[cfg(test)]
76mod tests {
77 use crate::encode_voxj_object_smallest;
78 use voxj::{VoxjCodecObject, VoxjSerdeSampleBlock};
79
80 #[test]
83 fn zero_palette_object_keeps_sample_arity() {
84 let object = encode_voxj_object_smallest(
85 &VoxjCodecObject {
86 name: "o".to_owned(),
87 palette_refs: Vec::new(),
88 bounds: [3, 1, 1],
89 positions: vec![[0, 0, 0], [1, 0, 0], [2, 0, 0]],
90 samples: vec![Vec::new(), Vec::new(), Vec::new()],
91 },
92 &[],
93 )
94 .unwrap();
95 match &object.voxel_samples {
96 VoxjSerdeSampleBlock::RawJson(rows) => assert_eq!(rows.len(), 3),
97 VoxjSerdeSampleBlock::RleJson(channels) => assert!(channels.is_empty()),
98 VoxjSerdeSampleBlock::PackedBase64(channels) => assert!(channels.is_empty()),
99 }
100 }
101}