mlt_core/frames/v01/geometry/
optimizer.rs1use std::collections::HashMap;
2
3use crate::codecs::morton::z_order_params;
4use crate::v01::encode::encode_geometry;
5use crate::v01::{
6 DataProfile, DictionaryType, EncodedGeometry, GeometryEncoder, GeometryValues, IntEncoder,
7 LengthType, OffsetType, StreamType, VertexBufferType,
8};
9use crate::{MltError, MltResult};
10
11const MORTON_UNIQUENESS_THRESHOLD: f64 = 0.5;
17
18#[derive(Debug, Clone, PartialEq)]
28pub struct GeometryProfile {
29 stream_candidates: HashMap<StreamType, Vec<IntEncoder>>,
33}
34
35impl GeometryProfile {
36 #[doc(hidden)]
37 #[must_use]
38 pub fn new(stream_candidates: HashMap<StreamType, Vec<IntEncoder>>) -> Self {
39 Self { stream_candidates }
40 }
41
42 pub fn from_sample(decoded: &GeometryValues) -> MltResult<Self> {
44 let vertex_buffer_type = decoded
45 .vertices
46 .as_deref()
47 .map_or(VertexBufferType::Vec2, select_vertex_strategy);
48
49 let mut probe = GeometryEncoder::all(IntEncoder::varint());
50 probe.vertex_buffer_type(vertex_buffer_type);
51
52 let mut stream_candidates: HashMap<StreamType, Vec<IntEncoder>> = HashMap::new();
53 encode_geometry(
54 decoded,
55 &probe,
56 Some(&mut |st: StreamType, data: &[u32]| {
57 let candidates = DataProfile::prune_candidates::<i32>(data);
58 stream_candidates.insert(st, candidates);
59 }),
60 )?;
61
62 Ok(Self { stream_candidates })
63 }
64
65 #[must_use]
69 pub fn merge(mut self, other: &Self) -> Self {
70 for (st, candidates) in &other.stream_candidates {
71 let entry = self.stream_candidates.entry(*st).or_default();
72 for &enc in candidates {
73 if !entry.contains(&enc) {
74 entry.push(enc);
75 }
76 }
77 }
78 self
79 }
80}
81
82fn optimize(decoded: &GeometryValues) -> MltResult<GeometryEncoder> {
91 let vertex_buffer_type = decoded
92 .vertices
93 .as_deref()
94 .map_or(VertexBufferType::Vec2, select_vertex_strategy);
95
96 let mut probe = GeometryEncoder::all(IntEncoder::varint());
99 probe.vertex_buffer_type(vertex_buffer_type);
100
101 let mut payloads: HashMap<StreamType, Vec<u32>> = HashMap::new();
102 encode_geometry(
103 decoded,
104 &probe,
105 Some(&mut |st: StreamType, data: &[u32]| {
106 payloads.insert(st, data.to_vec());
107 }),
108 )?;
109
110 let opt = |st: StreamType| -> IntEncoder {
111 payloads
112 .get(&st)
113 .map_or_else(IntEncoder::varint, |data| IntEncoder::auto_u32(data))
114 };
115
116 Ok(build_encoder(decoded, vertex_buffer_type, opt))
117}
118
119fn apply_profile(
126 decoded: &GeometryValues,
127 profile: &GeometryProfile,
128) -> MltResult<GeometryEncoder> {
129 let vertex_buffer_type = decoded
130 .vertices
131 .as_deref()
132 .map_or(VertexBufferType::Vec2, select_vertex_strategy);
133
134 let mut probe = GeometryEncoder::all(IntEncoder::varint());
135 probe.vertex_buffer_type(vertex_buffer_type);
136
137 let mut payloads: HashMap<StreamType, Vec<u32>> = HashMap::new();
138 encode_geometry(
139 decoded,
140 &probe,
141 Some(&mut |st: StreamType, data: &[u32]| {
142 payloads.insert(st, data.to_vec());
143 }),
144 )?;
145
146 let opt = |st: StreamType| -> IntEncoder {
147 let data = payloads.get(&st);
148 let candidates = profile.stream_candidates.get(&st).map(Vec::as_slice);
149 match (data, candidates) {
150 (Some(data), Some(candidates)) if !candidates.is_empty() => {
151 DataProfile::compete_u32(candidates, data)
152 }
153 (Some(data), _) => IntEncoder::auto_u32(data),
154 _ => IntEncoder::varint(),
155 }
156 };
157
158 Ok(build_encoder(decoded, vertex_buffer_type, opt))
159}
160
161fn build_encoder(
166 decoded: &GeometryValues,
167 vertex_buffer_type: VertexBufferType,
168 mut opt: impl FnMut(StreamType) -> IntEncoder,
169) -> GeometryEncoder {
170 let has_geom_offs = decoded.geometry_offsets.is_some();
174 let has_ring_offs = decoded.ring_offsets.is_some();
175
176 let parts_enc = opt(StreamType::Length(LengthType::Parts));
177 let rings_enc = opt(StreamType::Length(LengthType::Rings));
178
179 let vertex_st = match vertex_buffer_type {
181 VertexBufferType::Vec2 => StreamType::Data(DictionaryType::Vertex),
182 VertexBufferType::Morton => StreamType::Data(DictionaryType::Morton),
183 };
184
185 let mut encoder = GeometryEncoder::all(IntEncoder::varint());
186 encoder
187 .meta(opt(StreamType::Length(LengthType::VarBinary)))
188 .geometries(opt(StreamType::Length(LengthType::Geometries)))
189 .triangles(opt(StreamType::Length(LengthType::Triangles)))
190 .triangles_indexes(opt(StreamType::Offset(OffsetType::Index)))
191 .vertex(opt(vertex_st))
192 .vertex_offsets(opt(StreamType::Offset(OffsetType::Vertex)))
193 .vertex_buffer_type(vertex_buffer_type);
194
195 match (has_geom_offs, has_ring_offs) {
196 (true, true) => {
197 encoder.rings(parts_enc).rings2(rings_enc);
198 }
199 (true, false) => {
200 encoder.no_rings(parts_enc);
201 }
202 (false, true) => {
203 encoder.parts(parts_enc).parts_ring(rings_enc);
204 }
205 (false, false) => {
206 encoder.only_parts(parts_enc);
207 }
208 }
209 encoder
210}
211
212fn select_vertex_strategy(vertices: &[i32]) -> VertexBufferType {
219 let total = vertices.len() / 2;
220 if total == 0 {
221 return VertexBufferType::Vec2;
222 }
223
224 if z_order_params(vertices).is_err() {
226 return VertexBufferType::Vec2;
227 }
228
229 let unique_count = vertices
231 .chunks_exact(2)
232 .map(|c| (c[0], c[1]))
233 .collect::<std::collections::HashSet<_>>()
234 .len();
235
236 #[expect(clippy::cast_precision_loss)]
237 let uniqueness_ratio = unique_count as f64 / total as f64;
238
239 if uniqueness_ratio < MORTON_UNIQUENESS_THRESHOLD {
240 VertexBufferType::Morton
241 } else {
242 VertexBufferType::Vec2
243 }
244}
245
246impl GeometryValues {
247 pub fn encode(self, encoder: GeometryEncoder) -> MltResult<EncodedGeometry> {
249 EncodedGeometry::encode(&self, encoder)
250 }
251
252 pub fn encode_with_profile(
254 &self,
255 profile: &GeometryProfile,
256 ) -> Result<(EncodedGeometry, GeometryEncoder), MltError> {
257 let enc = apply_profile(self, profile)?;
258 let encoded = EncodedGeometry::encode(self, enc)?;
259 Ok((encoded, enc))
260 }
261
262 pub fn encode_auto(self) -> Result<(EncodedGeometry, GeometryEncoder), MltError> {
264 let enc = optimize(&self)?;
265 let encoded = EncodedGeometry::encode(&self, enc)?;
266 Ok((encoded, enc))
267 }
268}