Skip to main content

quantized_mesh/
encoding.rs

1//! Encoding functions and main encoder for quantized-mesh format.
2//!
3//! The streaming writer uses inline zigzag-delta and high-water-mark
4//! encoding with a small chunked buffer, so no intermediate `Vec<u16>` /
5//! `Vec<u32>` is allocated for the encoded streams.
6
7use std::io::{self, Write};
8
9use flate2::Compression;
10use flate2::write::GzEncoder;
11
12use crate::{
13    EdgeIndices, ExtensionId, QuantizedMeshHeader, QuantizedVertices, TileMetadata, WaterMask,
14};
15
16/// Encode a value using zig-zag encoding.
17///
18/// Maps signed integers to unsigned integers so that small magnitude values
19/// (positive or negative) have small encoded values.
20///
21/// ```text
22/// 0 -> 0, -1 -> 1, 1 -> 2, -2 -> 3, 2 -> 4, ...
23/// ```
24#[inline]
25pub fn zigzag_encode(value: i32) -> u32 {
26    ((value << 1) ^ (value >> 31)) as u32
27}
28
29/// Decode a zig-zag encoded value.
30#[inline]
31pub fn zigzag_decode(value: u32) -> i32 {
32    ((value >> 1) as i32) ^ (-((value & 1) as i32))
33}
34
35/// Oct-encode a unit normal vector to 2 bytes.
36///
37/// Uses octahedron encoding for efficient normal compression.
38pub fn oct_encode_normal(normal: [f32; 3]) -> [u8; 2] {
39    let [mut x, mut y, z] = normal;
40
41    // Project to octahedron
42    let inv_l1 = 1.0 / (x.abs() + y.abs() + z.abs());
43    x *= inv_l1;
44    y *= inv_l1;
45
46    // Unfold lower hemisphere
47    if z < 0.0 {
48        let ox = x;
49        x = (1.0 - y.abs()) * if ox >= 0.0 { 1.0 } else { -1.0 };
50        y = (1.0 - ox.abs()) * if y >= 0.0 { 1.0 } else { -1.0 };
51    }
52
53    // Map from [-1, 1] to [0, 255]
54    let encode = |v: f32| -> u8 { ((v * 0.5 + 0.5) * 255.0).clamp(0.0, 255.0) as u8 };
55
56    [encode(x), encode(y)]
57}
58
59/// Options for encoding quantized mesh.
60#[derive(Debug, Clone, Default)]
61pub struct EncodeOptions {
62    /// Include oct-encoded vertex normals
63    pub include_normals: bool,
64    /// Vertex normals (required if include_normals is true)
65    pub normals: Option<Vec<[f32; 3]>>,
66    /// Include water mask
67    pub include_water_mask: bool,
68    /// Water mask data
69    pub water_mask: Option<WaterMask>,
70    /// Include metadata extension with tile availability
71    pub include_metadata: bool,
72    /// Metadata for tile availability
73    pub metadata: Option<TileMetadata>,
74    /// Gzip compression level (0-9, default 6)
75    pub compression_level: u32,
76}
77
78/// Quantized mesh encoder.
79///
80/// Encodes terrain mesh data into the quantized-mesh-1.0 format.
81pub struct QuantizedMeshEncoder {
82    header: QuantizedMeshHeader,
83    vertices: QuantizedVertices,
84    indices: Vec<u32>,
85    edge_indices: EdgeIndices,
86}
87
88impl QuantizedMeshEncoder {
89    /// Create a new encoder with mesh data.
90    pub fn new(
91        header: QuantizedMeshHeader,
92        vertices: QuantizedVertices,
93        indices: Vec<u32>,
94        edge_indices: EdgeIndices,
95    ) -> Self {
96        Self {
97            header,
98            vertices,
99            indices,
100            edge_indices,
101        }
102    }
103
104    /// Encode to quantized-mesh format without compression.
105    pub fn encode(&self) -> Vec<u8> {
106        self.encode_with_options(&EncodeOptions::default())
107    }
108
109    /// Encode with options (extensions, compression).
110    pub fn encode_with_options(&self, options: &EncodeOptions) -> Vec<u8> {
111        let mut output = Vec::new();
112        self.encode_to_with_options(&mut output, options)
113            .expect("Failed to encode to Vec");
114        output
115    }
116
117    /// Encode to a writer without compression.
118    pub fn encode_to<W: Write>(&self, writer: W) -> io::Result<()> {
119        self.encode_to_with_options(writer, &EncodeOptions::default())
120    }
121
122    /// Encode to a writer with options (extensions, compression).
123    pub fn encode_to_with_options<W: Write>(
124        &self,
125        writer: W,
126        options: &EncodeOptions,
127    ) -> io::Result<()> {
128        if options.compression_level == 0 {
129            self.encode_uncompressed_to(writer, options)
130        } else {
131            let mut encoder = GzEncoder::new(writer, Compression::new(options.compression_level));
132            self.encode_uncompressed_to(&mut encoder, options)?;
133            encoder.finish()?;
134            Ok(())
135        }
136    }
137
138    /// Encode without compression to a writer, streaming each section
139    /// directly without intermediate Vec allocations.
140    fn encode_uncompressed_to<W: Write>(
141        &self,
142        mut writer: W,
143        options: &EncodeOptions,
144    ) -> io::Result<()> {
145        let vertex_count = self.vertices.len();
146        let use_32bit = vertex_count > 65535;
147
148        // Header (88 bytes).
149        writer.write_all(&self.header.to_bytes())?;
150        // Vertex count.
151        writer.write_all(&(vertex_count as u32).to_le_bytes())?;
152
153        // Vertex u/v/height streams (zigzag-delta).
154        write_zigzag_delta_to(&mut writer, &self.vertices.u)?;
155        write_zigzag_delta_to(&mut writer, &self.vertices.v)?;
156        write_zigzag_delta_to(&mut writer, &self.vertices.height)?;
157
158        // Pad to index alignment. After header+count+vertices the offset is:
159        let current_pos = 88 + 4 + vertex_count * 6;
160        let align = if use_32bit { 4 } else { 2 };
161        let padding = (align - (current_pos % align)) % align;
162        if padding > 0 {
163            let zeros = [0u8; 4];
164            writer.write_all(&zeros[..padding])?;
165        }
166
167        // Triangle count + high-water-mark indices.
168        let triangle_count = self.indices.len() / 3;
169        writer.write_all(&(triangle_count as u32).to_le_bytes())?;
170        write_high_water_mark_to(&mut writer, &self.indices, use_32bit)?;
171
172        // Edge index streams.
173        for edge in [
174            &self.edge_indices.west,
175            &self.edge_indices.south,
176            &self.edge_indices.east,
177            &self.edge_indices.north,
178        ] {
179            writer.write_all(&(edge.len() as u32).to_le_bytes())?;
180            write_indices_to(&mut writer, edge, use_32bit)?;
181        }
182
183        // Extensions.
184        if options.include_normals
185            && let Some(normals) = &options.normals
186        {
187            write_normals_extension_to(&mut writer, normals)?;
188        }
189        if options.include_water_mask {
190            let water_mask = options.water_mask.as_ref().cloned().unwrap_or_default();
191            write_water_mask_extension_to(&mut writer, &water_mask)?;
192        }
193        if options.include_metadata
194            && let Some(metadata) = &options.metadata
195        {
196            write_metadata_extension_to(&mut writer, metadata)?;
197        }
198
199        Ok(())
200    }
201}
202
203// ---------------------------------------------------------------------------
204// Streaming write helpers
205// ---------------------------------------------------------------------------
206
207/// Buffer size for streaming writes. 4 KiB = 2048 u16s or 1024 u32s — large
208/// enough to amortise per-call `write_all` overhead.
209const WRITE_BUF: usize = 4096;
210
211fn write_zigzag_delta_to<W: Write>(writer: &mut W, values: &[u16]) -> io::Result<()> {
212    let mut buf = [0u8; WRITE_BUF];
213    let mut len = 0;
214    let mut prev = 0i32;
215    for &value in values {
216        let current = value as i32;
217        let delta = current - prev;
218        let bytes = (zigzag_encode(delta) as u16).to_le_bytes();
219        buf[len] = bytes[0];
220        buf[len + 1] = bytes[1];
221        len += 2;
222        prev = current;
223        if len + 2 > WRITE_BUF {
224            writer.write_all(&buf[..len])?;
225            len = 0;
226        }
227    }
228    if len > 0 {
229        writer.write_all(&buf[..len])?;
230    }
231    Ok(())
232}
233
234fn write_high_water_mark_to<W: Write>(
235    writer: &mut W,
236    indices: &[u32],
237    use_32bit: bool,
238) -> io::Result<()> {
239    let mut buf = [0u8; WRITE_BUF];
240    let mut len = 0;
241    let mut highest = 0u32;
242    let stride = if use_32bit { 4 } else { 2 };
243    for &index in indices {
244        let code = if index == highest {
245            highest += 1;
246            0
247        } else {
248            highest - index
249        };
250        if use_32bit {
251            buf[len..len + 4].copy_from_slice(&code.to_le_bytes());
252        } else {
253            buf[len..len + 2].copy_from_slice(&(code as u16).to_le_bytes());
254        }
255        len += stride;
256        if len + stride > WRITE_BUF {
257            writer.write_all(&buf[..len])?;
258            len = 0;
259        }
260    }
261    if len > 0 {
262        writer.write_all(&buf[..len])?;
263    }
264    Ok(())
265}
266
267fn write_indices_to<W: Write>(writer: &mut W, indices: &[u32], use_32bit: bool) -> io::Result<()> {
268    let mut buf = [0u8; WRITE_BUF];
269    let mut len = 0;
270    let stride = if use_32bit { 4 } else { 2 };
271    for &idx in indices {
272        if use_32bit {
273            buf[len..len + 4].copy_from_slice(&idx.to_le_bytes());
274        } else {
275            buf[len..len + 2].copy_from_slice(&(idx as u16).to_le_bytes());
276        }
277        len += stride;
278        if len + stride > WRITE_BUF {
279            writer.write_all(&buf[..len])?;
280            len = 0;
281        }
282    }
283    if len > 0 {
284        writer.write_all(&buf[..len])?;
285    }
286    Ok(())
287}
288
289fn write_normals_extension_to<W: Write>(writer: &mut W, normals: &[[f32; 3]]) -> io::Result<()> {
290    writer.write_all(&[ExtensionId::OctEncodedVertexNormals as u8])?;
291    writer.write_all(&((normals.len() * 2) as u32).to_le_bytes())?;
292    let mut buf = [0u8; WRITE_BUF];
293    let mut len = 0;
294    for &normal in normals {
295        let encoded = oct_encode_normal(normal);
296        buf[len] = encoded[0];
297        buf[len + 1] = encoded[1];
298        len += 2;
299        if len + 2 > WRITE_BUF {
300            writer.write_all(&buf[..len])?;
301            len = 0;
302        }
303    }
304    if len > 0 {
305        writer.write_all(&buf[..len])?;
306    }
307    Ok(())
308}
309
310fn write_water_mask_extension_to<W: Write>(
311    writer: &mut W,
312    water_mask: &WaterMask,
313) -> io::Result<()> {
314    writer.write_all(&[ExtensionId::WaterMask as u8])?;
315    match water_mask {
316        WaterMask::Uniform(value) => {
317            writer.write_all(&1u32.to_le_bytes())?;
318            writer.write_all(&[*value])?;
319        }
320        WaterMask::Grid(grid) => {
321            writer.write_all(&(256 * 256u32).to_le_bytes())?;
322            writer.write_all(grid.as_ref())?;
323        }
324    }
325    Ok(())
326}
327
328fn write_metadata_extension_to<W: Write>(
329    writer: &mut W,
330    metadata: &TileMetadata,
331) -> io::Result<()> {
332    let json = serde_json::to_string(metadata)
333        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
334    let json_bytes = json.as_bytes();
335    writer.write_all(&[ExtensionId::Metadata as u8])?;
336    let extension_length = 4 + json_bytes.len() as u32;
337    writer.write_all(&extension_length.to_le_bytes())?;
338    writer.write_all(&(json_bytes.len() as u32).to_le_bytes())?;
339    writer.write_all(json_bytes)?;
340    Ok(())
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    #[test]
348    fn test_zigzag_encode() {
349        assert_eq!(zigzag_encode(0), 0);
350        assert_eq!(zigzag_encode(-1), 1);
351        assert_eq!(zigzag_encode(1), 2);
352        assert_eq!(zigzag_encode(-2), 3);
353        assert_eq!(zigzag_encode(2), 4);
354    }
355
356    #[test]
357    fn test_zigzag_roundtrip() {
358        for i in -1000..1000 {
359            assert_eq!(zigzag_decode(zigzag_encode(i)), i);
360        }
361    }
362
363    #[test]
364    fn test_oct_encode_normal() {
365        let up = [0.0f32, 0.0, 1.0];
366        let encoded = oct_encode_normal(up);
367        assert!((encoded[0] as i32 - 127).abs() < 2);
368        assert!((encoded[1] as i32 - 127).abs() < 2);
369
370        let down = [0.0f32, 0.0, -1.0];
371        let encoded = oct_encode_normal(down);
372        assert!(encoded[0] == 0 || encoded[0] == 255);
373    }
374
375    #[test]
376    fn test_encoder_basic() {
377        let header = QuantizedMeshHeader::default();
378        let vertices = QuantizedVertices {
379            u: vec![0, 32767, 0, 32767],
380            v: vec![0, 0, 32767, 32767],
381            height: vec![0, 0, 0, 0],
382        };
383        let indices = vec![0, 1, 2, 1, 3, 2];
384        let edge_indices = EdgeIndices::from_vertices(&vertices);
385
386        let encoder = QuantizedMeshEncoder::new(header, vertices, indices, edge_indices);
387        let data = encoder.encode_with_options(&EncodeOptions {
388            compression_level: 0,
389            ..Default::default()
390        });
391
392        assert!(data.len() > 88);
393        let parsed_header = QuantizedMeshHeader::from_bytes(&data).unwrap();
394        assert_eq!(parsed_header.min_height, 0.0);
395    }
396
397    #[test]
398    fn test_encoder_with_compression() {
399        let header = QuantizedMeshHeader::default();
400        let vertices = QuantizedVertices {
401            u: vec![0, 32767, 0, 32767],
402            v: vec![0, 0, 32767, 32767],
403            height: vec![0, 0, 0, 0],
404        };
405        let indices = vec![0, 1, 2, 1, 3, 2];
406        let edge_indices = EdgeIndices::from_vertices(&vertices);
407
408        let encoder = QuantizedMeshEncoder::new(header, vertices, indices, edge_indices);
409
410        let compressed = encoder.encode_with_options(&EncodeOptions {
411            compression_level: 6,
412            ..Default::default()
413        });
414
415        assert_eq!(&compressed[0..2], &[0x1f, 0x8b]);
416    }
417
418    #[test]
419    fn test_encoder_with_extensions() {
420        let header = QuantizedMeshHeader::default();
421        let vertices = QuantizedVertices {
422            u: vec![0, 32767, 0, 32767],
423            v: vec![0, 0, 32767, 32767],
424            height: vec![0, 0, 0, 0],
425        };
426        let indices = vec![0, 1, 2, 1, 3, 2];
427        let edge_indices = EdgeIndices::from_vertices(&vertices);
428
429        let encoder = QuantizedMeshEncoder::new(header, vertices, indices, edge_indices);
430
431        let normals = vec![[0.0, 0.0, 1.0]; 4];
432
433        let data = encoder.encode_with_options(&EncodeOptions {
434            compression_level: 0,
435            include_normals: true,
436            normals: Some(normals),
437            include_water_mask: true,
438            water_mask: Some(WaterMask::Uniform(0)),
439            ..Default::default()
440        });
441
442        let without_ext = encoder.encode_with_options(&EncodeOptions {
443            compression_level: 0,
444            ..Default::default()
445        });
446
447        assert!(data.len() > without_ext.len());
448    }
449
450    #[test]
451    fn test_encode_to_writer_matches_encode_with_options() {
452        let header = QuantizedMeshHeader::default();
453        let vertices = QuantizedVertices {
454            u: vec![0, 32767, 0, 32767],
455            v: vec![0, 0, 32767, 32767],
456            height: vec![0, 0, 0, 0],
457        };
458        let indices = vec![0, 1, 2, 1, 3, 2];
459        let edge_indices = EdgeIndices::from_vertices(&vertices);
460
461        let encoder = QuantizedMeshEncoder::new(header, vertices, indices, edge_indices);
462
463        let data_vec = encoder.encode_with_options(&EncodeOptions {
464            compression_level: 0,
465            ..Default::default()
466        });
467
468        let mut data_writer = Vec::new();
469        encoder
470            .encode_to_with_options(
471                &mut data_writer,
472                &EncodeOptions {
473                    compression_level: 0,
474                    ..Default::default()
475                },
476            )
477            .expect("Failed to encode to writer");
478
479        assert_eq!(data_vec, data_writer);
480    }
481
482    #[test]
483    fn test_encode_to_writer_compressed() {
484        let header = QuantizedMeshHeader::default();
485        let vertices = QuantizedVertices {
486            u: vec![0, 32767, 0, 32767],
487            v: vec![0, 0, 32767, 32767],
488            height: vec![0, 0, 0, 0],
489        };
490        let indices = vec![0, 1, 2, 1, 3, 2];
491        let edge_indices = EdgeIndices::from_vertices(&vertices);
492
493        let encoder = QuantizedMeshEncoder::new(header, vertices, indices, edge_indices);
494
495        let mut data_writer = Vec::new();
496        encoder
497            .encode_to_with_options(
498                &mut data_writer,
499                &EncodeOptions {
500                    compression_level: 6,
501                    ..Default::default()
502                },
503            )
504            .expect("Failed to encode to writer");
505
506        assert_eq!(&data_writer[0..2], &[0x1f, 0x8b]);
507    }
508}