Skip to main content

mlt_core/encoder/geometry/
encode01.rs

1//! Geometry section writer for tag `0x01` (v1) layers.
2
3use super::model::VertexBufferType;
4use super::streams::{
5    dict_may_be_beneficial, encode_hilbert_vertex_streams, encode_level1_length_stream,
6    encode_level1_without_ring_buffer_length_stream, encode_level2_length_stream,
7    encode_morton_vertex_streams, encode_ring_lengths_for_mixed, encode_root_length_stream,
8    encode_vec2_vertex_stream, normalize_geometry_offsets, normalize_part_offsets_for_rings,
9    write_geo_u32_stream,
10};
11use crate::MltResult;
12use crate::decoder::GeometryType::{LineString, Point, Polygon};
13use crate::decoder::{
14    ColumnType, GeometryType, GeometryValues, LengthType, Morton, OffsetType, StreamType,
15};
16use crate::encoder::model::{CurveParams, StreamCtx};
17use crate::encoder::{Codecs, Encoder};
18
19impl GeometryValues {
20    /// Write the geometry column to `enc`.
21    #[hotpath::measure]
22    pub fn write_to(self, enc: &mut Encoder, codecs: &mut Codecs) -> MltResult<()> {
23        let Self {
24            vector_types,
25            geometry_offsets,
26            part_offsets,
27            ring_offsets,
28            index_buffer,
29            triangles,
30            vertices,
31        } = self;
32
33        // Flatten every Option<Vec> -> Vec  (empty == not present).
34        // triangles: None means no tessellation; Some([]) can't occur in practice (each
35        // push_geom appends a count), so empty == absent is safe here too.
36        // vertices: None means no coordinate data (e.g. empty layer).
37        let geom_offsets = geometry_offsets.unwrap_or_default();
38        let part_offsets = part_offsets.unwrap_or_default();
39        let ring_offsets = ring_offsets.unwrap_or_default();
40        let index_buffer = index_buffer.unwrap_or_default();
41        let triangles = triangles.unwrap_or_default();
42        let vertices = vertices.unwrap_or_default();
43
44        // Direct callers (tests, custom drivers) skip `StagedLayer::encode_into`
45        // and arrive with empty caches; populate from `vertices` so the
46        // dictionary builders can rely on them unconditionally.
47        if enc.hilbert_cache.is_none() {
48            enc.hilbert_cache = Some(CurveParams::from_vertices(&vertices));
49        }
50        if enc.morton_cache.is_none() {
51            let p = enc.hilbert_cache.expect("populated above");
52            enc.morton_cache = Morton::new(p.bits, p.shift).ok();
53        }
54
55        let meta: Vec<u32> = vector_types.iter().map(|t| *t as u32).collect();
56
57        let part_offsets = if geom_offsets.is_empty()
58            && !ring_offsets.is_empty()
59            && !part_offsets.is_empty()
60            && part_offsets.len() != vector_types.len() + 1
61        {
62            // Normalize part_offsets when there are no geometry offsets but ring offsets exist.
63            normalize_part_offsets_for_rings(&vector_types, &part_offsets, &ring_offsets)
64        } else {
65            part_offsets
66        };
67
68        // Write column type to meta; reserve exactly 1 byte for stream count
69        // (geometry never exceeds ~8 streams, always fits in a single varint byte).
70        enc.write_column_type(ColumnType::Geometry)?;
71        let stream_count_pos = enc.data().len();
72        enc.data_mut().push(0); // placeholder - patched below
73        let mut n: u8 = 0;
74
75        // Meta stream - always written, even for a zero-feature layer.
76        let ctx = StreamCtx::geom(StreamType::Length(LengthType::VarBinary), "meta");
77        codecs.write_int_stream(&meta, &ctx, enc)?;
78        n += 1;
79
80        // Topology: compute each length stream and write it immediately.
81        if !geom_offsets.is_empty() {
82            let geom_offsets = if geom_offsets.len() == vector_types.len() + 1 {
83                geom_offsets
84            } else {
85                normalize_geometry_offsets(&vector_types, &geom_offsets)
86            };
87            let data = encode_root_length_stream(&vector_types, &geom_offsets, Polygon);
88            let ctx = StreamCtx::geom(StreamType::Length(LengthType::Geometries), "geometries");
89            n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
90
91            // part_offsets is intentionally kept sparse here (polygon-only cumulative
92            // ring counts). encode_level1/2_length_stream navigate it with a running
93            // part_idx counter that advances only for Polygon/LineString types, which
94            // matches the sparse layout. Densifying via normalize_part_offsets_for_rings
95            // would insert Point slots and corrupt the counter arithmetic.
96            if !part_offsets.is_empty() {
97                if ring_offsets.is_empty() {
98                    // geom -> parts only (no rings).
99                    let data = encode_level1_without_ring_buffer_length_stream(
100                        &vector_types,
101                        &geom_offsets,
102                        &part_offsets,
103                    );
104                    let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "no_rings");
105                    n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
106                } else {
107                    // Full topology: geom -> parts -> rings.
108                    // LineStrings contribute to rings here, not to parts.
109                    let data = encode_level1_length_stream(
110                        &vector_types,
111                        &geom_offsets,
112                        &part_offsets,
113                        false,
114                    );
115                    let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "rings");
116                    n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
117
118                    let data = encode_level2_length_stream(
119                        &vector_types,
120                        &geom_offsets,
121                        &part_offsets,
122                        &ring_offsets,
123                    );
124                    let ctx = StreamCtx::geom(StreamType::Length(LengthType::Rings), "rings2");
125                    n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
126                }
127            }
128        } else if !part_offsets.is_empty() {
129            if ring_offsets.is_empty() {
130                let data = encode_root_length_stream(&vector_types, &part_offsets, Point);
131                let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "no_rings");
132                n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
133            } else {
134                // No Multi* types; parts -> rings (Polygon / mixed Point+Polygon).
135                // Java writes an empty GEOMETRIES stream here for tessellated polygons; only do
136                // so when explicitly forced (e.g. to preserve byte-for-byte Java compatibility).
137                let ctx = StreamCtx::geom(StreamType::Length(LengthType::Geometries), "geometries");
138                n += write_geo_u32_stream(&[], ctx, enc, codecs)?;
139
140                let data = encode_root_length_stream(&vector_types, &part_offsets, LineString);
141                let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "parts");
142                n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
143
144                // part_offs is a dense N+1 array (one slot per geometry incl. Points);
145                // ring_offs stores vertex offsets per slot.  The dense-aware helper skips
146                // Point slots by index rather than a running counter.
147                let has_line_string = vector_types
148                    .iter()
149                    .copied()
150                    .any(GeometryType::is_linestring);
151                let data = encode_ring_lengths_for_mixed(
152                    &vector_types,
153                    &part_offsets,
154                    &ring_offsets,
155                    has_line_string,
156                );
157                let ctx = StreamCtx::geom(StreamType::Length(LengthType::Rings), "parts_ring");
158                n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
159            }
160        }
161
162        let ctx = StreamCtx::geom(StreamType::Length(LengthType::Triangles), "triangles");
163        n += write_geo_u32_stream(&triangles, ctx, enc, codecs)?;
164        let ctx = StreamCtx::geom(StreamType::Offset(OffsetType::Index), "triangles_indexes");
165        n += write_geo_u32_stream(&index_buffer, ctx, enc, codecs)?;
166
167        if let Some(forced) = enc.override_vertex_buffer_type() {
168            n += match forced {
169                VertexBufferType::Vec2 => encode_vec2_vertex_stream(&vertices, enc, codecs)?,
170                VertexBufferType::Morton => encode_morton_vertex_streams(&vertices, enc, codecs)?,
171                VertexBufferType::Hilbert => encode_hilbert_vertex_streams(&vertices, enc, codecs)?,
172            };
173        } else if dict_may_be_beneficial(&vertices, enc) {
174            // Morton fits (the gate above ensures it), so race all three.
175            let mut winner_size: usize = usize::MAX;
176            let mut winner_stream_cnt: u8 = 0;
177            let mut alt = enc.try_alternatives();
178            alt.with(|e| {
179                let ds = e.data().len();
180                let ms = e.meta().len();
181                winner_stream_cnt = encode_vec2_vertex_stream(&vertices, e, codecs)?;
182                winner_size = (e.data().len() - ds) + (e.meta().len() - ms);
183                Ok(())
184            })?;
185            alt.with(|e| {
186                let ds = e.data().len();
187                let ms = e.meta().len();
188                let cnt = encode_hilbert_vertex_streams(&vertices, e, codecs)?;
189                let size = (e.data().len() - ds) + (e.meta().len() - ms);
190                if size < winner_size {
191                    winner_stream_cnt = cnt;
192                    winner_size = size;
193                }
194                Ok(())
195            })?;
196            alt.with(|e| {
197                let ds = e.data().len();
198                let ms = e.meta().len();
199                let cnt = encode_morton_vertex_streams(&vertices, e, codecs)?;
200                let size = (e.data().len() - ds) + (e.meta().len() - ms);
201                if size < winner_size {
202                    winner_stream_cnt = cnt;
203                }
204                Ok(())
205            })?;
206            drop(alt);
207            n += winner_stream_cnt;
208        } else {
209            n += encode_vec2_vertex_stream(&vertices, enc, codecs)?;
210        }
211
212        // Patch the reserved stream-count byte.
213        debug_assert!(n <= 127, "geometry stream count must fit in one byte");
214        enc.data_mut()[stream_count_pos] = n;
215        Ok(())
216    }
217}