Skip to main content

quantized_mesh/
decoding.rs

1//! Zero-copy decoder for quantized-mesh format.
2//!
3//! [`QuantizedMeshView`] borrows directly from the input byte slice and
4//! exposes each section (vertices, indices, edges, extensions) as
5//! `&[u8]` slices, with iterator helpers that lazily apply the
6//! zigzag-delta and high-water-mark decoding without allocating
7//! intermediate `Vec`s.
8//!
9//! For a fully-owned result use [`DecodedMesh::decode`] / [`DecodedMesh::decode_from`],
10//! which transparently handle gzip decompression and materialise every
11//! section into `Vec`s.
12
13use std::io::{self, Read};
14
15use flate2::read::GzDecoder;
16
17use crate::{
18    EdgeIndices, QuantizedMeshHeader, QuantizedVertices, TileMetadata, WaterMask, zigzag_decode,
19};
20
21/// Error type for quantized-mesh decoding.
22#[derive(Debug)]
23pub enum DecodeError {
24    /// Input data is too short.
25    UnexpectedEof,
26    /// Invalid or corrupted data.
27    InvalidData(String),
28    /// Gzip decompression failed.
29    DecompressionError(String),
30    /// JSON parsing failed for metadata extension.
31    JsonError(String),
32    /// IO error.
33    IoError(io::Error),
34}
35
36impl std::fmt::Display for DecodeError {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        match self {
39            DecodeError::UnexpectedEof => write!(f, "Unexpected end of data"),
40            DecodeError::InvalidData(msg) => write!(f, "Invalid data: {msg}"),
41            DecodeError::DecompressionError(msg) => write!(f, "Decompression error: {msg}"),
42            DecodeError::JsonError(msg) => write!(f, "JSON error: {msg}"),
43            DecodeError::IoError(err) => write!(f, "IO error: {err}"),
44        }
45    }
46}
47
48impl std::error::Error for DecodeError {}
49
50impl From<io::Error> for DecodeError {
51    fn from(err: io::Error) -> Self {
52        if err.kind() == io::ErrorKind::UnexpectedEof {
53            DecodeError::UnexpectedEof
54        } else {
55            DecodeError::IoError(err)
56        }
57    }
58}
59
60/// Result type for quantized-mesh decoding.
61pub type DecodeResult<T> = Result<T, DecodeError>;
62
63// ---------------------------------------------------------------------------
64// View types (zero-copy)
65// ---------------------------------------------------------------------------
66
67/// Triangle / edge indices viewed as borrowed bytes.
68///
69/// The original quantized-mesh stream stores indices as either u16 or u32
70/// depending on whether `vertex_count > 65535`. The view keeps the raw
71/// little-endian bytes and decodes lazily via [`Self::iter`].
72#[derive(Debug, Clone, Copy)]
73pub enum IndicesView<'a> {
74    /// 16-bit indices (used when `vertex_count <= 65535`). 2 bytes per index.
75    U16(&'a [u8]),
76    /// 32-bit indices. 4 bytes per index.
77    U32(&'a [u8]),
78}
79
80impl<'a> IndicesView<'a> {
81    /// Number of indices.
82    #[inline]
83    pub fn len(&self) -> usize {
84        match self {
85            Self::U16(b) => b.len() / 2,
86            Self::U32(b) => b.len() / 4,
87        }
88    }
89
90    /// `true` if there are no indices.
91    #[inline]
92    pub fn is_empty(&self) -> bool {
93        self.len() == 0
94    }
95
96    /// Iterate the raw (still high-water-mark encoded) indices.
97    pub fn iter_raw(&self) -> RawIndexIter<'a> {
98        match self {
99            Self::U16(b) => RawIndexIter::U16(b.chunks_exact(2)),
100            Self::U32(b) => RawIndexIter::U32(b.chunks_exact(4)),
101        }
102    }
103
104    /// Iterate the fully decoded triangle indices.
105    pub fn iter(&self) -> HighWaterMarkIter<RawIndexIter<'a>> {
106        HighWaterMarkIter::new(self.iter_raw())
107    }
108
109    /// Decode into an owned `Vec<u32>`.
110    pub fn to_vec(&self) -> Vec<u32> {
111        let mut out = Vec::with_capacity(self.len());
112        out.extend(self.iter());
113        out
114    }
115}
116
117/// Iterator over raw little-endian-encoded indices (u16 or u32) yielding `u32`.
118pub enum RawIndexIter<'a> {
119    /// 16-bit chunks (2 bytes each).
120    U16(std::slice::ChunksExact<'a, u8>),
121    /// 32-bit chunks (4 bytes each).
122    U32(std::slice::ChunksExact<'a, u8>),
123}
124
125impl<'a> Iterator for RawIndexIter<'a> {
126    type Item = u32;
127
128    #[inline]
129    fn next(&mut self) -> Option<u32> {
130        match self {
131            Self::U16(it) => it.next().map(|c| u16::from_le_bytes([c[0], c[1]]) as u32),
132            Self::U32(it) => it
133                .next()
134                .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]])),
135        }
136    }
137
138    fn size_hint(&self) -> (usize, Option<usize>) {
139        match self {
140            Self::U16(it) => it.size_hint(),
141            Self::U32(it) => it.size_hint(),
142        }
143    }
144}
145
146impl<'a> ExactSizeIterator for RawIndexIter<'a> {}
147
148/// Iterator that applies inverse zigzag-delta decoding to a stream of raw
149/// little-endian u16 bytes, yielding decoded `u16` values.
150pub struct ZigzagDeltaIter<'a> {
151    chunks: std::slice::ChunksExact<'a, u8>,
152    state: i32,
153}
154
155impl<'a> ZigzagDeltaIter<'a> {
156    /// Wrap a byte slice (length must be a multiple of 2). Trailing odd
157    /// bytes are silently ignored.
158    #[inline]
159    pub fn new(bytes: &'a [u8]) -> Self {
160        Self {
161            chunks: bytes.chunks_exact(2),
162            state: 0,
163        }
164    }
165}
166
167impl<'a> Iterator for ZigzagDeltaIter<'a> {
168    type Item = u16;
169
170    #[inline]
171    fn next(&mut self) -> Option<u16> {
172        let chunk = self.chunks.next()?;
173        let enc = u16::from_le_bytes([chunk[0], chunk[1]]) as u32;
174        let delta = zigzag_decode(enc);
175        self.state = self.state.wrapping_add(delta);
176        Some(self.state as u16)
177    }
178
179    fn size_hint(&self) -> (usize, Option<usize>) {
180        self.chunks.size_hint()
181    }
182}
183
184impl<'a> ExactSizeIterator for ZigzagDeltaIter<'a> {}
185
186/// Iterator that applies inverse high-water-mark decoding to an inner
187/// iterator of raw `u32` codes.
188pub struct HighWaterMarkIter<I> {
189    inner: I,
190    highest: u32,
191}
192
193impl<I> HighWaterMarkIter<I> {
194    /// Wrap a code iterator.
195    #[inline]
196    pub fn new(inner: I) -> Self {
197        Self { inner, highest: 0 }
198    }
199}
200
201impl<I: Iterator<Item = u32>> Iterator for HighWaterMarkIter<I> {
202    type Item = u32;
203
204    #[inline]
205    fn next(&mut self) -> Option<u32> {
206        let code = self.inner.next()?;
207        let index = self.highest.wrapping_sub(code);
208        if code == 0 {
209            self.highest = self.highest.wrapping_add(1);
210        }
211        Some(index)
212    }
213
214    fn size_hint(&self) -> (usize, Option<usize>) {
215        self.inner.size_hint()
216    }
217}
218
219impl<I: Iterator<Item = u32> + ExactSizeIterator> ExactSizeIterator for HighWaterMarkIter<I> {}
220
221/// Water mask viewed as borrowed bytes.
222#[derive(Debug, Clone, Copy)]
223pub enum WaterMaskView<'a> {
224    /// Uniform mask: single byte (0 = land, 255 = water).
225    Uniform(u8),
226    /// 256×256 byte grid borrowed from the source buffer.
227    Grid(&'a [u8; 256 * 256]),
228}
229
230impl<'a> WaterMaskView<'a> {
231    /// Convert to the owned [`WaterMask`] form (copies the 64 KiB grid).
232    pub fn to_owned(self) -> WaterMask {
233        match self {
234            Self::Uniform(v) => WaterMask::Uniform(v),
235            Self::Grid(g) => {
236                let mut owned = Box::new([0u8; 256 * 256]);
237                owned.copy_from_slice(g.as_ref());
238                WaterMask::Grid(owned)
239            }
240        }
241    }
242}
243
244/// Extensions viewed as borrowed slices into the source buffer.
245#[derive(Debug, Clone, Copy, Default)]
246pub struct ExtensionsView<'a> {
247    /// Oct-encoded normals — 2 bytes per vertex. Use [`oct_decode_normal`]
248    /// or [`iter_oct_normals`] to decode.
249    pub normals_oct: Option<&'a [u8]>,
250    /// Water mask.
251    pub water_mask: Option<WaterMaskView<'a>>,
252    /// Raw metadata JSON. Use [`TileMetadata`]::from a `serde_json::from_str`
253    /// to materialise.
254    pub metadata_json: Option<&'a str>,
255}
256
257impl<'a> ExtensionsView<'a> {
258    /// Materialise into the owned [`DecodedExtensions`] form.
259    pub fn to_owned(&self) -> DecodeResult<DecodedExtensions> {
260        Ok(DecodedExtensions {
261            normals: self
262                .normals_oct
263                .map(|b| iter_oct_normals(b).collect::<Vec<_>>()),
264            water_mask: self.water_mask.map(|w| w.to_owned()),
265            metadata: self
266                .metadata_json
267                .map(serde_json::from_str::<TileMetadata>)
268                .transpose()
269                .map_err(|e| DecodeError::JsonError(e.to_string()))?,
270        })
271    }
272}
273
274/// Iterator over oct-encoded normals (2 bytes each), yielding `[f32; 3]`.
275pub fn iter_oct_normals(bytes: &[u8]) -> impl Iterator<Item = [f32; 3]> + '_ {
276    bytes
277        .chunks_exact(2)
278        .map(|c| oct_decode_normal([c[0], c[1]]))
279}
280
281/// Zero-copy view over a parsed quantized-mesh stream.
282///
283/// Every byte section (vertex / index / edge / extension data) is a slice
284/// borrowed from the input. Use the `iter_*` helpers to decode on the fly,
285/// or [`Self::into_owned`] to materialise everything into a [`DecodedMesh`].
286#[derive(Debug, Clone, Copy)]
287pub struct QuantizedMeshView<'a> {
288    /// Parsed 88-byte header (copied — it's small and not aligned in the source).
289    pub header: QuantizedMeshHeader,
290    /// Number of vertices.
291    pub vertex_count: u32,
292    /// Whether the index stream uses 32-bit indices (`vertex_count > 65535`).
293    pub use_32bit_indices: bool,
294    /// Number of triangles.
295    pub triangle_count: u32,
296
297    /// Zigzag-delta encoded `u` coordinate bytes (2 bytes per vertex).
298    pub encoded_u_bytes: &'a [u8],
299    /// Zigzag-delta encoded `v` coordinate bytes.
300    pub encoded_v_bytes: &'a [u8],
301    /// Zigzag-delta encoded `height` bytes.
302    pub encoded_height_bytes: &'a [u8],
303
304    /// High-water-mark encoded triangle indices.
305    pub indices: IndicesView<'a>,
306    /// West-edge indices (raw little-endian).
307    pub edge_west: IndicesView<'a>,
308    /// South-edge indices.
309    pub edge_south: IndicesView<'a>,
310    /// East-edge indices.
311    pub edge_east: IndicesView<'a>,
312    /// North-edge indices.
313    pub edge_north: IndicesView<'a>,
314
315    /// Parsed extension views.
316    pub extensions: ExtensionsView<'a>,
317}
318
319impl<'a> QuantizedMeshView<'a> {
320    /// Parse a raw (already gzip-decompressed) quantized-mesh byte stream.
321    pub fn parse(data: &'a [u8]) -> DecodeResult<Self> {
322        let mut cur = Cursor::new(data);
323        let header = QuantizedMeshHeader::from_bytes(cur.take(88)?)
324            .ok_or_else(|| DecodeError::InvalidData("Invalid header".to_string()))?;
325
326        let vertex_count = cur.read_u32()?;
327        let use_32bit = vertex_count > 65535;
328        let vc = vertex_count as usize;
329
330        let encoded_u_bytes = cur.take(vc * 2)?;
331        let encoded_v_bytes = cur.take(vc * 2)?;
332        let encoded_height_bytes = cur.take(vc * 2)?;
333
334        cur.align_to(if use_32bit { 4 } else { 2 });
335
336        let triangle_count = cur.read_u32()?;
337        let index_stride = if use_32bit { 4 } else { 2 };
338        let index_bytes = cur.take(triangle_count as usize * 3 * index_stride)?;
339        let indices = if use_32bit {
340            IndicesView::U32(index_bytes)
341        } else {
342            IndicesView::U16(index_bytes)
343        };
344
345        let edge_west = read_edge_indices(&mut cur, use_32bit)?;
346        let edge_south = read_edge_indices(&mut cur, use_32bit)?;
347        let edge_east = read_edge_indices(&mut cur, use_32bit)?;
348        let edge_north = read_edge_indices(&mut cur, use_32bit)?;
349
350        let extensions = parse_extensions(&mut cur, vc)?;
351
352        Ok(Self {
353            header,
354            vertex_count,
355            use_32bit_indices: use_32bit,
356            triangle_count,
357            encoded_u_bytes,
358            encoded_v_bytes,
359            encoded_height_bytes,
360            indices,
361            edge_west,
362            edge_south,
363            edge_east,
364            edge_north,
365            extensions,
366        })
367    }
368
369    /// Lazily decoded `u` coordinates.
370    #[inline]
371    pub fn iter_u(&self) -> ZigzagDeltaIter<'a> {
372        ZigzagDeltaIter::new(self.encoded_u_bytes)
373    }
374
375    /// Lazily decoded `v` coordinates.
376    #[inline]
377    pub fn iter_v(&self) -> ZigzagDeltaIter<'a> {
378        ZigzagDeltaIter::new(self.encoded_v_bytes)
379    }
380
381    /// Lazily decoded `height` values.
382    #[inline]
383    pub fn iter_height(&self) -> ZigzagDeltaIter<'a> {
384        ZigzagDeltaIter::new(self.encoded_height_bytes)
385    }
386
387    /// Materialise into a fully owned [`DecodedMesh`].
388    pub fn into_owned(self) -> DecodeResult<DecodedMesh> {
389        let vertices = QuantizedVertices {
390            u: self.iter_u().collect(),
391            v: self.iter_v().collect(),
392            height: self.iter_height().collect(),
393        };
394        let indices = self.indices.to_vec();
395        let edge_indices = EdgeIndices {
396            west: self.edge_west.iter_raw().collect(),
397            south: self.edge_south.iter_raw().collect(),
398            east: self.edge_east.iter_raw().collect(),
399            north: self.edge_north.iter_raw().collect(),
400        };
401        let extensions = self.extensions.to_owned()?;
402        Ok(DecodedMesh {
403            header: self.header,
404            vertices,
405            indices,
406            edge_indices,
407            extensions,
408        })
409    }
410}
411
412fn read_edge_indices<'a>(cur: &mut Cursor<'a>, use_32bit: bool) -> DecodeResult<IndicesView<'a>> {
413    let count = cur.read_u32()? as usize;
414    let stride = if use_32bit { 4 } else { 2 };
415    let bytes = cur.take(count * stride)?;
416    Ok(if use_32bit {
417        IndicesView::U32(bytes)
418    } else {
419        IndicesView::U16(bytes)
420    })
421}
422
423fn parse_extensions<'a>(
424    cur: &mut Cursor<'a>,
425    vertex_count: usize,
426) -> DecodeResult<ExtensionsView<'a>> {
427    let mut ext = ExtensionsView::default();
428    while cur.remaining() >= 5 {
429        let id = cur.read_u8()?;
430        let length = cur.read_u32()? as usize;
431        if cur.remaining() < length {
432            // Truncated extension; bail without erroring (matches old behaviour).
433            break;
434        }
435        let payload = cur.take(length)?;
436        match id {
437            1 => {
438                // Oct-encoded normals: 2 bytes per vertex.
439                let want = vertex_count * 2;
440                let bytes = if payload.len() >= want {
441                    &payload[..want]
442                } else {
443                    payload
444                };
445                ext.normals_oct = Some(bytes);
446            }
447            2 => {
448                ext.water_mask = Some(match length {
449                    1 => WaterMaskView::Uniform(payload[0]),
450                    n if n == 256 * 256 => {
451                        let arr: &[u8; 256 * 256] = payload
452                            .try_into()
453                            .map_err(|_| DecodeError::InvalidData("water mask size".into()))?;
454                        WaterMaskView::Grid(arr)
455                    }
456                    _ => WaterMaskView::Uniform(0),
457                });
458            }
459            4 => {
460                if payload.len() < 4 {
461                    return Err(DecodeError::UnexpectedEof);
462                }
463                let json_len =
464                    u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]) as usize;
465                if payload.len() < 4 + json_len {
466                    return Err(DecodeError::UnexpectedEof);
467                }
468                let json_bytes = &payload[4..4 + json_len];
469                let json_str = std::str::from_utf8(json_bytes)
470                    .map_err(|e| DecodeError::JsonError(e.to_string()))?;
471                ext.metadata_json = Some(json_str);
472            }
473            _ => {} // unknown extension — already advanced past it
474        }
475    }
476    Ok(ext)
477}
478
479// ---------------------------------------------------------------------------
480// Owned form (backward-compatible)
481// ---------------------------------------------------------------------------
482
483/// Decoded extensions from quantized-mesh format.
484#[derive(Debug, Clone, Default)]
485pub struct DecodedExtensions {
486    /// Oct-decoded per-vertex normals (if present).
487    pub normals: Option<Vec<[f32; 3]>>,
488    /// Water mask (if present).
489    pub water_mask: Option<WaterMask>,
490    /// Metadata (if present).
491    pub metadata: Option<TileMetadata>,
492}
493
494/// Fully owned decoded quantized-mesh data.
495#[derive(Debug, Clone)]
496pub struct DecodedMesh {
497    /// Header with tile metadata.
498    pub header: QuantizedMeshHeader,
499    /// Quantized vertex data.
500    pub vertices: QuantizedVertices,
501    /// Triangle indices.
502    pub indices: Vec<u32>,
503    /// Edge indices for skirt generation.
504    pub edge_indices: EdgeIndices,
505    /// Decoded extensions.
506    pub extensions: DecodedExtensions,
507}
508
509impl DecodedMesh {
510    /// Decode a (possibly gzip-compressed) quantized-mesh byte slice into the
511    /// fully owned form.
512    pub fn decode(data: &[u8]) -> DecodeResult<Self> {
513        if is_gzip(data) {
514            let mut decompressed = Vec::new();
515            GzDecoder::new(data)
516                .read_to_end(&mut decompressed)
517                .map_err(|e| DecodeError::DecompressionError(e.to_string()))?;
518            QuantizedMeshView::parse(&decompressed)?.into_owned()
519        } else {
520            QuantizedMeshView::parse(data)?.into_owned()
521        }
522    }
523
524    /// Decode from a `Read`er. The reader is fully consumed.
525    pub fn decode_from<R: Read>(mut reader: R) -> DecodeResult<Self> {
526        let mut buf = Vec::new();
527        reader.read_to_end(&mut buf)?;
528        Self::decode(&buf)
529    }
530}
531
532/// Check if `data` starts with the gzip magic number.
533#[inline]
534pub fn is_gzip(data: &[u8]) -> bool {
535    data.len() >= 2 && data[0] == 0x1f && data[1] == 0x8b
536}
537
538/// Decompress a gzip-compressed quantized-mesh blob. Returns the input unchanged
539/// (copied) if it isn't gzip.
540pub fn decompress_gzip(data: &[u8]) -> DecodeResult<Vec<u8>> {
541    if is_gzip(data) {
542        let mut out = Vec::new();
543        GzDecoder::new(data)
544            .read_to_end(&mut out)
545            .map_err(|e| DecodeError::DecompressionError(e.to_string()))?;
546        Ok(out)
547    } else {
548        Ok(data.to_vec())
549    }
550}
551
552// ---------------------------------------------------------------------------
553// Internal cursor
554// ---------------------------------------------------------------------------
555
556struct Cursor<'a> {
557    data: &'a [u8],
558    offset: usize,
559}
560
561impl<'a> Cursor<'a> {
562    fn new(data: &'a [u8]) -> Self {
563        Self { data, offset: 0 }
564    }
565
566    #[inline]
567    fn remaining(&self) -> usize {
568        self.data.len().saturating_sub(self.offset)
569    }
570
571    fn take(&mut self, n: usize) -> DecodeResult<&'a [u8]> {
572        if self.remaining() < n {
573            return Err(DecodeError::UnexpectedEof);
574        }
575        let slice = &self.data[self.offset..self.offset + n];
576        self.offset += n;
577        Ok(slice)
578    }
579
580    fn read_u8(&mut self) -> DecodeResult<u8> {
581        Ok(self.take(1)?[0])
582    }
583
584    fn read_u32(&mut self) -> DecodeResult<u32> {
585        let b = self.take(4)?;
586        Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
587    }
588
589    fn align_to(&mut self, alignment: usize) {
590        let rem = self.offset % alignment;
591        if rem != 0 {
592            self.offset += alignment - rem;
593        }
594    }
595}
596
597// ---------------------------------------------------------------------------
598// Oct decode (also used by encoder tests)
599// ---------------------------------------------------------------------------
600
601/// Decode oct-encoded normal to unit vector.
602///
603/// Reverses the octahedron encoding used for normal compression.
604pub fn oct_decode_normal(encoded: [u8; 2]) -> [f32; 3] {
605    let mut x = (encoded[0] as f32 / 255.0) * 2.0 - 1.0;
606    let mut y = (encoded[1] as f32 / 255.0) * 2.0 - 1.0;
607    let z = 1.0 - x.abs() - y.abs();
608    if z < 0.0 {
609        let ox = x;
610        x = (1.0 - y.abs()) * if ox >= 0.0 { 1.0 } else { -1.0 };
611        y = (1.0 - ox.abs()) * if y >= 0.0 { 1.0 } else { -1.0 };
612    }
613    let len = (x * x + y * y + z * z).sqrt();
614    if len > 0.0 {
615        [x / len, y / len, z / len]
616    } else {
617        [0.0, 0.0, 1.0]
618    }
619}
620
621#[cfg(test)]
622mod tests {
623    use super::*;
624    use crate::{EncodeOptions, QuantizedMeshEncoder, oct_encode_normal};
625
626    fn sample_mesh() -> (
627        QuantizedMeshHeader,
628        QuantizedVertices,
629        Vec<u32>,
630        EdgeIndices,
631    ) {
632        let header = QuantizedMeshHeader::default();
633        let vertices = QuantizedVertices {
634            u: vec![0, 32767, 0, 32767],
635            v: vec![0, 0, 32767, 32767],
636            height: vec![0, 0, 0, 0],
637        };
638        let indices = vec![0, 1, 2, 1, 3, 2];
639        let edge_indices = EdgeIndices::from_vertices(&vertices);
640        (header, vertices, indices, edge_indices)
641    }
642
643    #[test]
644    fn test_oct_decode_normal_roundtrip() {
645        let test_normals = [
646            [0.0f32, 0.0, 1.0],
647            [0.0, 0.0, -1.0],
648            [1.0, 0.0, 0.0],
649            [0.0, 1.0, 0.0],
650            [0.577, 0.577, 0.577],
651        ];
652        for normal in test_normals {
653            let encoded = oct_encode_normal(normal);
654            let decoded = oct_decode_normal(encoded);
655            let dot = normal[0] * decoded[0] + normal[1] * decoded[1] + normal[2] * decoded[2];
656            assert!(dot > 0.95, "{normal:?} → {encoded:?} → {decoded:?}");
657        }
658    }
659
660    #[test]
661    fn view_parses_uncompressed_mesh() {
662        let (header, vertices, indices, edge_indices) = sample_mesh();
663        let encoder = QuantizedMeshEncoder::new(
664            header,
665            vertices.clone(),
666            indices.clone(),
667            edge_indices.clone(),
668        );
669        let encoded = encoder.encode_with_options(&EncodeOptions {
670            compression_level: 0,
671            ..Default::default()
672        });
673
674        let view = QuantizedMeshView::parse(&encoded).expect("parse");
675        assert_eq!(view.vertex_count as usize, vertices.u.len());
676        assert_eq!(view.triangle_count as usize * 3, indices.len());
677
678        let u: Vec<u16> = view.iter_u().collect();
679        let v: Vec<u16> = view.iter_v().collect();
680        let h: Vec<u16> = view.iter_height().collect();
681        assert_eq!(u, vertices.u);
682        assert_eq!(v, vertices.v);
683        assert_eq!(h, vertices.height);
684
685        let idx: Vec<u32> = view.indices.iter().collect();
686        assert_eq!(idx, indices);
687    }
688
689    #[test]
690    fn owned_decode_roundtrips() {
691        let (header, vertices, indices, edge_indices) = sample_mesh();
692        let encoder = QuantizedMeshEncoder::new(
693            header,
694            vertices.clone(),
695            indices.clone(),
696            edge_indices.clone(),
697        );
698        let encoded = encoder.encode_with_options(&EncodeOptions {
699            compression_level: 0,
700            ..Default::default()
701        });
702
703        let decoded = DecodedMesh::decode(&encoded).expect("decode");
704        assert_eq!(decoded.header.min_height, header.min_height);
705        assert_eq!(decoded.vertices.u, vertices.u);
706        assert_eq!(decoded.vertices.v, vertices.v);
707        assert_eq!(decoded.vertices.height, vertices.height);
708        assert_eq!(decoded.indices, indices);
709        assert_eq!(decoded.edge_indices.west, edge_indices.west);
710        assert_eq!(decoded.edge_indices.south, edge_indices.south);
711        assert_eq!(decoded.edge_indices.east, edge_indices.east);
712        assert_eq!(decoded.edge_indices.north, edge_indices.north);
713    }
714
715    #[test]
716    fn owned_decode_handles_gzip() {
717        let (header, vertices, indices, edge_indices) = sample_mesh();
718        let encoder =
719            QuantizedMeshEncoder::new(header, vertices.clone(), indices.clone(), edge_indices);
720        let encoded = encoder.encode_with_options(&EncodeOptions {
721            compression_level: 6,
722            ..Default::default()
723        });
724        assert_eq!(&encoded[0..2], &[0x1f, 0x8b]);
725
726        let decoded = DecodedMesh::decode(&encoded).expect("decode");
727        assert_eq!(decoded.vertices.u, vertices.u);
728        assert_eq!(decoded.indices, indices);
729    }
730
731    #[test]
732    fn owned_decode_with_extensions() {
733        let (header, vertices, indices, edge_indices) = sample_mesh();
734        let normals = vec![[0.0_f32, 0.0, 1.0]; 4];
735        let encoder = QuantizedMeshEncoder::new(header, vertices.clone(), indices, edge_indices);
736        let encoded = encoder.encode_with_options(&EncodeOptions {
737            compression_level: 0,
738            include_normals: true,
739            normals: Some(normals),
740            include_water_mask: true,
741            water_mask: Some(WaterMask::Uniform(128)),
742            ..Default::default()
743        });
744
745        let decoded = DecodedMesh::decode(&encoded).expect("decode");
746        let ns = decoded.extensions.normals.expect("normals");
747        assert_eq!(ns.len(), 4);
748        for n in ns {
749            assert!(n[2] > 0.9);
750        }
751        match decoded.extensions.water_mask.expect("water mask") {
752            WaterMask::Uniform(v) => assert_eq!(v, 128),
753            _ => panic!("expected uniform"),
754        }
755    }
756
757    #[test]
758    fn owned_decode_from_reader_handles_gzip() {
759        use std::io::Cursor;
760        let (header, vertices, indices, edge_indices) = sample_mesh();
761        let encoder =
762            QuantizedMeshEncoder::new(header, vertices.clone(), indices.clone(), edge_indices);
763        let encoded = encoder.encode_with_options(&EncodeOptions {
764            compression_level: 6,
765            ..Default::default()
766        });
767        let decoded = DecodedMesh::decode_from(Cursor::new(encoded)).expect("decode");
768        assert_eq!(decoded.vertices.u, vertices.u);
769        assert_eq!(decoded.indices, indices);
770    }
771}