voxj_codec/
encode_voxj_object_smallest.rs1use crate::{PositionEncoding, 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) -> 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 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 .min_by_key(deflated_len)
38 .expect("at least one candidate")
39}
40
41fn candidate_positions(bounds: [u32; 3]) -> Vec<PositionEncoding> {
44 let mut positions = Vec::new();
45
46 let cells = bounds[0] as u64 * bounds[1] as u64 * bounds[2] as u64;
47 if cells <= MAX_BITMAP_CELLS {
48 positions.push(PositionEncoding::BitmapBase64);
49 }
50
51 if hilbert_bits(bounds) <= MAX_HILBERT_BITS {
52 positions.push(PositionEncoding::Hilbert);
53 }
54
55 if positions.is_empty() {
56 positions.push(PositionEncoding::RawJson);
57 }
58
59 positions
60}
61
62fn deflated_len(object: &VoxjSerdeObject) -> usize {
64 let Ok(json) = serde_json::to_vec(&(&object.voxel_positions, &object.voxel_samples)) else {
65 return usize::MAX;
66 };
67 let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
68 let _ = encoder.write_all(&json);
69 encoder.finish().map_or(usize::MAX, |v| v.len())
70}
71
72#[cfg(test)]
73mod tests {
74 use crate::encode_voxj_object_smallest;
75 use voxj::{VoxjCodecObject, VoxjSerdeSampleBlock};
76
77 #[test]
80 fn zero_palette_object_keeps_sample_arity() {
81 let object = encode_voxj_object_smallest(
82 &VoxjCodecObject {
83 name: "o".to_owned(),
84 palette_refs: Vec::new(),
85 bounds: [3, 1, 1],
86 positions: vec![[0, 0, 0], [1, 0, 0], [2, 0, 0]],
87 samples: vec![Vec::new(), Vec::new(), Vec::new()],
88 },
89 &[],
90 );
91 match &object.voxel_samples {
92 VoxjSerdeSampleBlock::RawJson(rows) => assert_eq!(rows.len(), 3),
93 VoxjSerdeSampleBlock::RleJson(channels) => assert!(channels.is_empty()),
94 VoxjSerdeSampleBlock::PackedBase64(channels) => assert!(channels.is_empty()),
95 }
96 }
97}