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