1#[allow(dead_code)]
7pub struct DeltaChunk {
8 pub target_name: String,
9 pub weight: f32,
10 pub start_vertex: usize,
11 pub deltas: Vec<[f32; 3]>,
12}
13
14#[allow(dead_code)]
15pub struct DeltaStream {
16 pub chunks: Vec<DeltaChunk>,
17 pub total_vertices: usize,
18 pub dirty: bool,
19}
20
21#[allow(dead_code)]
22pub struct StreamConfig {
23 pub chunk_size: usize,
24 pub threshold: f32,
25 pub accumulate: bool,
26}
27
28#[allow(dead_code)]
29pub fn default_stream_config() -> StreamConfig {
30 StreamConfig {
31 chunk_size: 256,
32 threshold: 1e-6,
33 accumulate: true,
34 }
35}
36
37#[allow(dead_code)]
38pub fn new_delta_stream(vertex_count: usize) -> DeltaStream {
39 DeltaStream {
40 chunks: Vec::new(),
41 total_vertices: vertex_count,
42 dirty: false,
43 }
44}
45
46#[allow(dead_code)]
47pub fn push_chunk(stream: &mut DeltaStream, chunk: DeltaChunk) {
48 stream.chunks.push(chunk);
49 stream.dirty = true;
50}
51
52#[allow(dead_code)]
53pub fn apply_stream(stream: &DeltaStream, positions: &mut [[f32; 3]]) {
54 for chunk in &stream.chunks {
55 apply_chunk(chunk, positions);
56 }
57}
58
59#[allow(dead_code)]
60pub fn apply_chunk(chunk: &DeltaChunk, positions: &mut [[f32; 3]]) {
61 for (i, delta) in chunk.deltas.iter().enumerate() {
62 let vi = chunk.start_vertex + i;
63 if vi < positions.len() {
64 positions[vi][0] += delta[0] * chunk.weight;
65 positions[vi][1] += delta[1] * chunk.weight;
66 positions[vi][2] += delta[2] * chunk.weight;
67 }
68 }
69}
70
71#[allow(dead_code)]
72pub fn split_deltas_into_chunks(
73 target: &str,
74 weight: f32,
75 deltas: &[(usize, [f32; 3])],
76 chunk_size: usize,
77) -> Vec<DeltaChunk> {
78 if deltas.is_empty() {
79 return Vec::new();
80 }
81 let mut result = Vec::new();
82 for window in deltas.chunks(chunk_size) {
83 let start_vertex = window[0].0;
84 let chunk_deltas: Vec<[f32; 3]> = window.iter().map(|(_, d)| *d).collect();
85 result.push(DeltaChunk {
86 target_name: target.to_string(),
87 weight,
88 start_vertex,
89 deltas: chunk_deltas,
90 });
91 }
92 result
93}
94
95#[allow(dead_code)]
96pub fn merge_chunks(chunks: &[DeltaChunk]) -> DeltaChunk {
97 if chunks.is_empty() {
98 return DeltaChunk {
99 target_name: String::new(),
100 weight: 0.0,
101 start_vertex: 0,
102 deltas: Vec::new(),
103 };
104 }
105 let target_name = chunks[0].target_name.clone();
106 let weight = chunks[0].weight;
107 let start_vertex = chunks.iter().map(|c| c.start_vertex).min().unwrap_or(0);
108 let max_end = chunks
109 .iter()
110 .map(|c| c.start_vertex + c.deltas.len())
111 .max()
112 .unwrap_or(0);
113 let capacity = max_end.saturating_sub(start_vertex);
114 let mut merged: Vec<[f32; 3]> = vec![[0.0, 0.0, 0.0]; capacity];
115 for chunk in chunks {
116 for (i, delta) in chunk.deltas.iter().enumerate() {
117 let vi = chunk.start_vertex + i;
118 if vi >= start_vertex && vi - start_vertex < merged.len() {
119 let idx = vi - start_vertex;
120 merged[idx][0] += delta[0];
121 merged[idx][1] += delta[1];
122 merged[idx][2] += delta[2];
123 }
124 }
125 }
126 DeltaChunk {
127 target_name,
128 weight,
129 start_vertex,
130 deltas: merged,
131 }
132}
133
134#[allow(dead_code)]
135pub fn stream_delta_count(stream: &DeltaStream) -> usize {
136 stream.chunks.iter().map(|c| c.deltas.len()).sum()
137}
138
139#[allow(dead_code)]
140pub fn clear_stream(stream: &mut DeltaStream) {
141 stream.chunks.clear();
142 stream.dirty = false;
143}
144
145#[allow(dead_code)]
146pub fn stream_chunk_count(stream: &DeltaStream) -> usize {
147 stream.chunks.len()
148}
149
150#[allow(dead_code)]
151pub fn filter_stream_threshold(stream: &mut DeltaStream, threshold: f32) {
152 for chunk in &mut stream.chunks {
153 chunk.deltas.retain(|d| {
154 let mag = (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt();
155 mag >= threshold
156 });
157 }
158 stream.chunks.retain(|c| !c.deltas.is_empty());
159}
160
161#[allow(dead_code)]
162pub fn stream_memory_bytes(stream: &DeltaStream) -> usize {
163 let chunk_overhead = stream.chunks.len()
164 * (std::mem::size_of::<usize>() * 2
165 + std::mem::size_of::<f32>()
166 + std::mem::size_of::<usize>());
167 let delta_bytes: usize = stream
168 .chunks
169 .iter()
170 .map(|c| c.deltas.len() * std::mem::size_of::<[f32; 3]>())
171 .sum();
172 chunk_overhead + delta_bytes
173}
174
175#[allow(dead_code)]
176pub fn stream_to_flat_deltas(stream: &DeltaStream) -> Vec<(usize, [f32; 3])> {
177 let mut per_vertex: std::collections::HashMap<usize, [f32; 3]> =
178 std::collections::HashMap::new();
179 for chunk in &stream.chunks {
180 for (i, delta) in chunk.deltas.iter().enumerate() {
181 let vi = chunk.start_vertex + i;
182 let entry = per_vertex.entry(vi).or_insert([0.0, 0.0, 0.0]);
183 entry[0] += delta[0] * chunk.weight;
184 entry[1] += delta[1] * chunk.weight;
185 entry[2] += delta[2] * chunk.weight;
186 }
187 }
188 let mut result: Vec<(usize, [f32; 3])> = per_vertex.into_iter().collect();
189 result.sort_by_key(|(vi, _)| *vi);
190 result
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_default_stream_config() {
199 let cfg = default_stream_config();
200 assert_eq!(cfg.chunk_size, 256);
201 assert!(cfg.threshold < 1e-5);
202 assert!(cfg.accumulate);
203 }
204
205 #[test]
206 fn test_new_delta_stream() {
207 let stream = new_delta_stream(1000);
208 assert_eq!(stream.total_vertices, 1000);
209 assert_eq!(stream.chunks.len(), 0);
210 assert!(!stream.dirty);
211 }
212
213 #[test]
214 fn test_push_chunk() {
215 let mut stream = new_delta_stream(100);
216 let chunk = DeltaChunk {
217 target_name: "smile".to_string(),
218 weight: 1.0,
219 start_vertex: 0,
220 deltas: vec![[1.0, 0.0, 0.0]],
221 };
222 push_chunk(&mut stream, chunk);
223 assert_eq!(stream_chunk_count(&stream), 1);
224 assert!(stream.dirty);
225 }
226
227 #[test]
228 fn test_apply_stream() {
229 let mut stream = new_delta_stream(4);
230 let chunk = DeltaChunk {
231 target_name: "test".to_string(),
232 weight: 0.5,
233 start_vertex: 0,
234 deltas: vec![[2.0, 0.0, 0.0], [0.0, 2.0, 0.0]],
235 };
236 push_chunk(&mut stream, chunk);
237 let mut positions = [[0.0f32; 3]; 4];
238 apply_stream(&stream, &mut positions);
239 assert!((positions[0][0] - 1.0).abs() < 1e-5);
240 assert!((positions[1][1] - 1.0).abs() < 1e-5);
241 }
242
243 #[test]
244 fn test_apply_chunk() {
245 let chunk = DeltaChunk {
246 target_name: "brow".to_string(),
247 weight: 2.0,
248 start_vertex: 1,
249 deltas: vec![[1.0, 0.5, 0.0]],
250 };
251 let mut positions = [[0.0f32; 3]; 4];
252 apply_chunk(&chunk, &mut positions);
253 assert!((positions[1][0] - 2.0).abs() < 1e-5);
254 assert!((positions[1][1] - 1.0).abs() < 1e-5);
255 }
256
257 #[test]
258 fn test_split_deltas_into_chunks() {
259 let deltas: Vec<(usize, [f32; 3])> = (0..10).map(|i| (i, [1.0, 0.0, 0.0])).collect();
260 let chunks = split_deltas_into_chunks("target", 1.0, &deltas, 4);
261 assert_eq!(chunks.len(), 3);
262 assert_eq!(chunks[0].deltas.len(), 4);
263 assert_eq!(chunks[1].deltas.len(), 4);
264 assert_eq!(chunks[2].deltas.len(), 2);
265 }
266
267 #[test]
268 fn test_split_empty_deltas() {
269 let chunks = split_deltas_into_chunks("target", 1.0, &[], 4);
270 assert!(chunks.is_empty());
271 }
272
273 #[test]
274 fn test_stream_chunk_count() {
275 let mut stream = new_delta_stream(100);
276 assert_eq!(stream_chunk_count(&stream), 0);
277 push_chunk(
278 &mut stream,
279 DeltaChunk {
280 target_name: "t".to_string(),
281 weight: 1.0,
282 start_vertex: 0,
283 deltas: vec![[0.0, 0.0, 0.0]],
284 },
285 );
286 assert_eq!(stream_chunk_count(&stream), 1);
287 }
288
289 #[test]
290 fn test_stream_delta_count() {
291 let mut stream = new_delta_stream(100);
292 push_chunk(
293 &mut stream,
294 DeltaChunk {
295 target_name: "a".to_string(),
296 weight: 1.0,
297 start_vertex: 0,
298 deltas: vec![[1.0, 0.0, 0.0]; 5],
299 },
300 );
301 push_chunk(
302 &mut stream,
303 DeltaChunk {
304 target_name: "b".to_string(),
305 weight: 1.0,
306 start_vertex: 5,
307 deltas: vec![[0.0, 1.0, 0.0]; 3],
308 },
309 );
310 assert_eq!(stream_delta_count(&stream), 8);
311 }
312
313 #[test]
314 fn test_clear_stream() {
315 let mut stream = new_delta_stream(100);
316 push_chunk(
317 &mut stream,
318 DeltaChunk {
319 target_name: "t".to_string(),
320 weight: 1.0,
321 start_vertex: 0,
322 deltas: vec![[1.0, 0.0, 0.0]],
323 },
324 );
325 clear_stream(&mut stream);
326 assert_eq!(stream_chunk_count(&stream), 0);
327 assert!(!stream.dirty);
328 }
329
330 #[test]
331 fn test_filter_stream_threshold() {
332 let mut stream = new_delta_stream(10);
333 push_chunk(
334 &mut stream,
335 DeltaChunk {
336 target_name: "t".to_string(),
337 weight: 1.0,
338 start_vertex: 0,
339 deltas: vec![[0.0001, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0001, 0.0]],
340 },
341 );
342 filter_stream_threshold(&mut stream, 0.01);
343 assert_eq!(stream_delta_count(&stream), 1);
344 }
345
346 #[test]
347 fn test_stream_to_flat_deltas() {
348 let mut stream = new_delta_stream(10);
349 push_chunk(
350 &mut stream,
351 DeltaChunk {
352 target_name: "a".to_string(),
353 weight: 1.0,
354 start_vertex: 0,
355 deltas: vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
356 },
357 );
358 push_chunk(
359 &mut stream,
360 DeltaChunk {
361 target_name: "b".to_string(),
362 weight: 2.0,
363 start_vertex: 0,
364 deltas: vec![[0.5, 0.0, 0.0]],
365 },
366 );
367 let flat = stream_to_flat_deltas(&stream);
368 assert!(!flat.is_empty());
369 let v0 = flat
370 .iter()
371 .find(|(vi, _)| *vi == 0)
372 .expect("should succeed");
373 assert!((v0.1[0] - 2.0).abs() < 1e-5);
374 }
375
376 #[test]
377 fn test_merge_chunks() {
378 let chunks = vec![
379 DeltaChunk {
380 target_name: "smile".to_string(),
381 weight: 1.0,
382 start_vertex: 0,
383 deltas: vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
384 },
385 DeltaChunk {
386 target_name: "smile".to_string(),
387 weight: 1.0,
388 start_vertex: 2,
389 deltas: vec![[0.0, 0.0, 1.0]],
390 },
391 ];
392 let merged = merge_chunks(&chunks);
393 assert_eq!(merged.start_vertex, 0);
394 assert_eq!(merged.deltas.len(), 3);
395 }
396
397 #[test]
398 fn test_stream_memory_bytes() {
399 let mut stream = new_delta_stream(100);
400 push_chunk(
401 &mut stream,
402 DeltaChunk {
403 target_name: "t".to_string(),
404 weight: 1.0,
405 start_vertex: 0,
406 deltas: vec![[1.0, 0.0, 0.0]; 10],
407 },
408 );
409 let bytes = stream_memory_bytes(&stream);
410 assert!(bytes > 0);
411 }
412
413 #[test]
414 fn test_apply_stream_out_of_bounds() {
415 let mut stream = new_delta_stream(2);
416 push_chunk(
417 &mut stream,
418 DeltaChunk {
419 target_name: "t".to_string(),
420 weight: 1.0,
421 start_vertex: 100,
422 deltas: vec![[1.0, 0.0, 0.0]],
423 },
424 );
425 let mut positions = [[0.0f32; 3]; 2];
426 apply_stream(&stream, &mut positions);
428 assert!((positions[0][0]).abs() < 1e-5);
429 }
430}