1use crate::gltf_error::{GltfError, Result};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum MeshoptMode {
15 Attributes,
17 Triangles,
19 Indices,
21}
22
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
25pub enum MeshoptFilter {
26 #[default]
28 None,
29 Octahedral,
31 Quaternion,
33 Exponential,
35 Color,
38}
39
40impl MeshoptMode {
41 pub fn from_name(name: &str) -> Result<Self> {
43 match name {
44 "ATTRIBUTES" => Ok(Self::Attributes),
45 "TRIANGLES" => Ok(Self::Triangles),
46 "INDICES" => Ok(Self::Indices),
47 other => Err(GltfError::Unsupported(format!(
48 "EXT_meshopt_compression mode {other}"
49 ))),
50 }
51 }
52}
53
54impl MeshoptFilter {
55 pub fn from_name(name: &str) -> Result<Self> {
57 match name {
58 "NONE" => Ok(Self::None),
59 "OCTAHEDRAL" => Ok(Self::Octahedral),
60 "QUATERNION" => Ok(Self::Quaternion),
61 "EXPONENTIAL" => Ok(Self::Exponential),
62 "COLOR" => Ok(Self::Color),
63 other => Err(GltfError::Unsupported(format!(
64 "EXT_meshopt_compression filter {other}"
65 ))),
66 }
67 }
68}
69
70pub fn decode_buffer_view(
75 destination: &mut [u8],
76 source: &[u8],
77 mode: MeshoptMode,
78 filter: MeshoptFilter,
79 count: usize,
80 stride: usize,
81) -> Result<()> {
82 let expected = count
83 .checked_mul(stride)
84 .ok_or_else(|| invalid("buffer view size overflow"))?;
85 if destination.len() != expected {
86 return Err(invalid(
87 "buffer view byteLength does not match count times byteStride",
88 ));
89 }
90 match mode {
91 MeshoptMode::Attributes => {
92 decode_vertex_buffer(destination, count, stride, source)?;
93 apply_filter(destination, filter, count, stride)
94 }
95 MeshoptMode::Triangles => {
96 if filter != MeshoptFilter::None {
97 return Err(invalid("index streams cannot carry a filter"));
98 }
99 decode_index_buffer(destination, count, stride, source)
100 }
101 MeshoptMode::Indices => {
102 if filter != MeshoptFilter::None {
103 return Err(invalid("index streams cannot carry a filter"));
104 }
105 decode_index_sequence(destination, count, stride, source)
106 }
107 }
108}
109
110fn invalid(message: &str) -> GltfError {
111 GltfError::InvalidGltf(format!("EXT_meshopt_compression: {message}"))
112}
113
114const VERTEX_HEADER: u8 = 0xa0;
115const MAX_VERTEX_VERSION: u8 = 1;
116const VERTEX_BLOCK_SIZE_BYTES: usize = 8192;
117const VERTEX_BLOCK_MAX_SIZE: usize = 256;
118const BYTE_GROUP_SIZE: usize = 16;
119const BYTE_GROUP_DECODE_LIMIT: usize = 24;
120const TAIL_MIN_SIZE_V0: usize = 32;
121const TAIL_MIN_SIZE_V1: usize = 24;
122const BITS_V0: [u32; 4] = [0, 2, 4, 8];
123const BITS_V1: [u32; 5] = [0, 1, 2, 4, 8];
124
125fn vertex_block_size(vertex_size: usize) -> usize {
126 let result = (VERTEX_BLOCK_SIZE_BYTES / vertex_size) & !(BYTE_GROUP_SIZE - 1);
127 result.min(VERTEX_BLOCK_MAX_SIZE)
128}
129
130pub fn decode_vertex_buffer(
132 destination: &mut [u8],
133 count: usize,
134 stride: usize,
135 source: &[u8],
136) -> Result<()> {
137 if stride == 0 || stride > 256 || !stride.is_multiple_of(4) {
138 return Err(invalid(
139 "attribute byteStride must be 4..=256 and a multiple of 4",
140 ));
141 }
142 let header = *source
143 .first()
144 .ok_or_else(|| invalid("empty vertex stream"))?;
145 if header & 0xf0 != VERTEX_HEADER {
146 return Err(invalid("vertex stream header is invalid"));
147 }
148 let version = header & 0x0f;
149 if version > MAX_VERTEX_VERSION {
150 return Err(GltfError::Unsupported(format!(
151 "EXT_meshopt_compression vertex codec version {version}"
152 )));
153 }
154 let mut pos = 1usize;
155
156 let tail_size = stride + if version == 0 { 0 } else { stride / 4 };
157 let tail_min = if version == 0 {
158 TAIL_MIN_SIZE_V0
159 } else {
160 TAIL_MIN_SIZE_V1
161 };
162 let tail_padded = tail_size.max(tail_min);
163 if source.len() - pos < tail_padded {
164 return Err(invalid("vertex stream is truncated"));
165 }
166 let tail = source.len() - tail_size;
167
168 let mut last_vertex = [0u8; 256];
169 last_vertex[..stride].copy_from_slice(&source[tail..tail + stride]);
170 let channels = if version == 0 {
171 Vec::new()
172 } else {
173 source[tail + stride..tail + tail_size].to_vec()
174 };
175
176 let block_capacity = vertex_block_size(stride);
177 let mut scratch = vec![0u8; VERTEX_BLOCK_MAX_SIZE * 4];
178 let mut offset = 0usize;
179 while offset < count {
180 let block = block_capacity.min(count - offset);
181 let start = offset * stride;
182 pos = decode_vertex_block(
183 source,
184 pos,
185 &mut destination[start..start + block * stride],
186 block,
187 stride,
188 &mut last_vertex,
189 &channels,
190 version,
191 &mut scratch,
192 )?;
193 offset += block;
194 }
195
196 if source.len() - pos != tail_padded {
197 return Err(invalid("vertex stream has trailing data"));
198 }
199 Ok(())
200}
201
202#[allow(clippy::too_many_arguments)]
203fn decode_vertex_block(
204 source: &[u8],
205 mut pos: usize,
206 destination: &mut [u8],
207 count: usize,
208 stride: usize,
209 last_vertex: &mut [u8; 256],
210 channels: &[u8],
211 version: u8,
212 scratch: &mut [u8],
213) -> Result<usize> {
214 debug_assert!(count > 0 && count <= VERTEX_BLOCK_MAX_SIZE);
215 let count_aligned = (count + BYTE_GROUP_SIZE - 1) & !(BYTE_GROUP_SIZE - 1);
216
217 let control_size = if version == 0 { 0 } else { stride / 4 };
218 if source.len() - pos < control_size {
219 return Err(invalid("vertex block control bytes are truncated"));
220 }
221 let control = source[pos..pos + control_size].to_vec();
222 pos += control_size;
223
224 for k in (0..stride).step_by(4) {
225 let control_byte = if version == 0 { 0 } else { control[k / 4] };
226 for j in 0..4usize {
227 let plane = j * count;
228 match (control_byte >> (j * 2)) & 3 {
229 3 => {
230 if source.len() - pos < count {
231 return Err(invalid("vertex block literal plane is truncated"));
232 }
233 scratch[plane..plane + count].copy_from_slice(&source[pos..pos + count]);
234 pos += count;
235 }
236 2 => scratch[plane..plane + count].fill(0),
237 control => {
238 let bits: &[u32] = if version == 0 {
239 &BITS_V0
240 } else {
241 &BITS_V1[control as usize..]
242 };
243 pos = decode_bytes(
244 source,
245 pos,
246 &mut scratch[plane..plane + count_aligned],
247 bits,
248 )?;
249 }
250 }
251 }
252
253 let channel = if version == 0 { 0 } else { channels[k / 4] };
254 match channel & 3 {
255 0 => decode_deltas(
256 scratch,
257 destination,
258 count,
259 stride,
260 last_vertex,
261 k,
262 1,
263 false,
264 0,
265 ),
266 1 => decode_deltas(
267 scratch,
268 destination,
269 count,
270 stride,
271 last_vertex,
272 k,
273 2,
274 false,
275 0,
276 ),
277 2 => {
278 let rotation = (32 - u32::from(channel >> 4)) & 31;
279 decode_deltas(
280 scratch,
281 destination,
282 count,
283 stride,
284 last_vertex,
285 k,
286 4,
287 true,
288 rotation,
289 )
290 }
291 _ => return Err(invalid("vertex block channel type is invalid")),
292 }
293 }
294
295 last_vertex[..stride].copy_from_slice(&destination[stride * (count - 1)..stride * count]);
296 Ok(pos)
297}
298
299#[allow(clippy::too_many_arguments)]
301fn decode_deltas(
302 scratch: &[u8],
303 destination: &mut [u8],
304 count: usize,
305 stride: usize,
306 last_vertex: &[u8; 256],
307 k: usize,
308 size: usize,
309 xor: bool,
310 rotation: u32,
311) {
312 let mask = if size == 4 {
313 u32::MAX
314 } else {
315 (1u32 << (8 * size)) - 1
316 };
317 let mut plane = 0usize;
318 for sub in (0..4).step_by(size) {
319 let mut previous = 0u32;
320 for byte in 0..size {
321 previous |= u32::from(last_vertex[k + sub + byte]) << (8 * byte);
322 }
323 let mut offset = k + sub;
324 for i in 0..count {
325 let mut value = 0u32;
326 for byte in 0..size {
327 value |= u32::from(scratch[plane + i + count * byte]) << (8 * byte);
328 }
329 value = if xor {
330 (value.rotate_left(rotation) ^ previous) & mask
331 } else {
332 unzigzag(value).wrapping_add(previous) & mask
333 };
334 for byte in 0..size {
335 destination[offset + byte] = (value >> (8 * byte)) as u8;
336 }
337 previous = value;
338 offset += stride;
339 }
340 plane += count * size;
341 }
342}
343
344fn unzigzag(value: u32) -> u32 {
345 (0u32.wrapping_sub(value & 1)) ^ (value >> 1)
346}
347
348fn decode_bytes(
349 source: &[u8],
350 mut pos: usize,
351 destination: &mut [u8],
352 bits: &[u32],
353) -> Result<usize> {
354 debug_assert!(destination.len().is_multiple_of(BYTE_GROUP_SIZE));
355 let header_size = (destination.len() / BYTE_GROUP_SIZE).div_ceil(4);
356 if source.len() - pos < header_size {
357 return Err(invalid("byte group header is truncated"));
358 }
359 let header = pos;
360 pos += header_size;
361
362 for (group, chunk) in destination.chunks_mut(BYTE_GROUP_SIZE).enumerate() {
363 if source.len() - pos < BYTE_GROUP_DECODE_LIMIT {
364 return Err(invalid("byte group data is truncated"));
365 }
366 let selector = (source[header + group / 4] >> ((group % 4) * 2)) & 3;
367 pos = decode_bytes_group(source, pos, chunk, bits[selector as usize]);
368 }
369 Ok(pos)
370}
371
372fn decode_bytes_group(source: &[u8], pos: usize, destination: &mut [u8], bits: u32) -> usize {
374 match bits {
375 0 => {
376 destination.fill(0);
377 pos
378 }
379 8 => {
380 destination.copy_from_slice(&source[pos..pos + BYTE_GROUP_SIZE]);
381 pos + BYTE_GROUP_SIZE
382 }
383 bits => {
384 let per_byte = 8 / bits as usize;
385 let control_bytes = BYTE_GROUP_SIZE / per_byte;
386 let sentinel = (1u8 << bits) - 1;
387 let mut extra = pos + control_bytes;
388 for group in 0..control_bytes {
389 let mut byte = source[pos + group];
390 if bits == 1 {
391 byte = byte.reverse_bits();
393 }
394 for slot in 0..per_byte {
395 let encoded = byte >> (8 - bits);
396 byte <<= bits;
397 destination[group * per_byte + slot] = if encoded == sentinel {
398 let value = source[extra];
399 extra += 1;
400 value
401 } else {
402 encoded
403 };
404 }
405 }
406 extra
407 }
408 }
409}
410
411const INDEX_HEADER: u8 = 0xe0;
412const SEQUENCE_HEADER: u8 = 0xd0;
413const MAX_INDEX_VERSION: u8 = 1;
414
415pub fn decode_index_buffer(
417 destination: &mut [u8],
418 count: usize,
419 size: usize,
420 source: &[u8],
421) -> Result<()> {
422 if !count.is_multiple_of(3) {
423 return Err(invalid("triangle index count is not a multiple of 3"));
424 }
425 if size != 2 && size != 4 {
426 return Err(invalid("index byteStride must be 2 or 4"));
427 }
428 if source.len() < 1 + count / 3 + 16 {
429 return Err(invalid("index stream is truncated"));
430 }
431 if source[0] & 0xf0 != INDEX_HEADER {
432 return Err(invalid("index stream header is invalid"));
433 }
434 let version = source[0] & 0x0f;
435 if version > MAX_INDEX_VERSION {
436 return Err(GltfError::Unsupported(format!(
437 "EXT_meshopt_compression index codec version {version}"
438 )));
439 }
440
441 let mut edge_fifo = [[0u32; 2]; 16];
442 let mut vertex_fifo = [0u32; 16];
443 let mut edge_offset = 0usize;
444 let mut vertex_offset = 0usize;
445 let mut next = 0u32;
446 let mut last = 0u32;
447 let fec_max = if version >= 1 { 13 } else { 15 };
448
449 let code_end = 1 + count / 3;
450 let mut data = code_end;
451 let safe_end = source.len() - 16;
452 let table = safe_end;
453 let mut written = 0usize;
454
455 for code in 1..code_end {
456 let code_tri = source[code];
457 if code_tri < 0xf0 {
458 let fe = usize::from(code_tri >> 4);
459 let edge = edge_fifo[(edge_offset.wrapping_sub(1 + fe)) & 15];
460 let (a, b) = (edge[0], edge[1]);
461 let c;
462
463 let fec = i32::from(code_tri & 15);
464 if fec < fec_max {
465 let cached = vertex_fifo[(vertex_offset.wrapping_sub(1 + fec as usize)) & 15];
466 c = if fec == 0 { next } else { cached };
467 let first = usize::from(fec == 0);
468 next += u32::from(fec == 0);
469 push_vertex_fifo(&mut vertex_fifo, c, &mut vertex_offset, first);
470 } else {
471 if data > safe_end {
472 return Err(invalid("index stream data is truncated"));
473 }
474 c = if fec != 15 {
475 last.wrapping_add((fec * 2 - 27) as u32)
476 } else {
477 decode_index(source, &mut data, last)?
478 };
479 last = c;
480 push_vertex_fifo(&mut vertex_fifo, c, &mut vertex_offset, 1);
481 }
482
483 push_edge_fifo(&mut edge_fifo, c, b, &mut edge_offset);
484 push_edge_fifo(&mut edge_fifo, a, c, &mut edge_offset);
485 write_triangle(destination, &mut written, size, a, b, c);
486 } else if code_tri < 0xfe {
487 let code_aux = source[table + usize::from(code_tri & 15)];
488 let feb = usize::from(code_aux >> 4);
489 let fec = usize::from(code_aux & 15);
490
491 let a = next;
492 next += 1;
493
494 let b = if feb == 0 {
495 next
496 } else {
497 vertex_fifo[(vertex_offset.wrapping_sub(feb)) & 15]
498 };
499 let feb0 = usize::from(feb == 0);
500 next += feb0 as u32;
501
502 let c = if fec == 0 {
503 next
504 } else {
505 vertex_fifo[(vertex_offset.wrapping_sub(fec)) & 15]
506 };
507 let fec0 = usize::from(fec == 0);
508 next += fec0 as u32;
509
510 write_triangle(destination, &mut written, size, a, b, c);
511
512 push_vertex_fifo(&mut vertex_fifo, a, &mut vertex_offset, 1);
513 push_vertex_fifo(&mut vertex_fifo, b, &mut vertex_offset, feb0);
514 push_vertex_fifo(&mut vertex_fifo, c, &mut vertex_offset, fec0);
515
516 push_edge_fifo(&mut edge_fifo, b, a, &mut edge_offset);
517 push_edge_fifo(&mut edge_fifo, c, b, &mut edge_offset);
518 push_edge_fifo(&mut edge_fifo, a, c, &mut edge_offset);
519 } else {
520 if data > safe_end {
521 return Err(invalid("index stream data is truncated"));
522 }
523 let code_aux = source[data];
524 data += 1;
525
526 let fea = if code_tri == 0xfe { 0usize } else { 15 };
527 let feb = usize::from(code_aux >> 4);
528 let fec = usize::from(code_aux & 15);
529
530 if code_aux == 0 {
532 next = 0;
533 }
534
535 let mut a = 0u32;
536 if fea == 0 {
537 a = next;
538 next += 1;
539 }
540 let mut b = if feb == 0 {
541 let value = next;
542 next += 1;
543 value
544 } else {
545 vertex_fifo[(vertex_offset.wrapping_sub(feb)) & 15]
546 };
547 let mut c = if fec == 0 {
548 let value = next;
549 next += 1;
550 value
551 } else {
552 vertex_fifo[(vertex_offset.wrapping_sub(fec)) & 15]
553 };
554
555 if fea == 15 {
556 a = decode_index(source, &mut data, last)?;
557 last = a;
558 }
559 if feb == 15 {
560 b = decode_index(source, &mut data, last)?;
561 last = b;
562 }
563 if fec == 15 {
564 c = decode_index(source, &mut data, last)?;
565 last = c;
566 }
567
568 write_triangle(destination, &mut written, size, a, b, c);
569
570 push_vertex_fifo(&mut vertex_fifo, a, &mut vertex_offset, 1);
571 push_vertex_fifo(
572 &mut vertex_fifo,
573 b,
574 &mut vertex_offset,
575 usize::from(feb == 0 || feb == 15),
576 );
577 push_vertex_fifo(
578 &mut vertex_fifo,
579 c,
580 &mut vertex_offset,
581 usize::from(fec == 0 || fec == 15),
582 );
583
584 push_edge_fifo(&mut edge_fifo, b, a, &mut edge_offset);
585 push_edge_fifo(&mut edge_fifo, c, b, &mut edge_offset);
586 push_edge_fifo(&mut edge_fifo, a, c, &mut edge_offset);
587 }
588 }
589
590 if data != safe_end {
591 return Err(invalid("index stream has trailing data"));
592 }
593 Ok(())
594}
595
596pub fn decode_index_sequence(
598 destination: &mut [u8],
599 count: usize,
600 size: usize,
601 source: &[u8],
602) -> Result<()> {
603 if size != 2 && size != 4 {
604 return Err(invalid("index byteStride must be 2 or 4"));
605 }
606 if source.len() < 1 + count + 4 {
607 return Err(invalid("index sequence is truncated"));
608 }
609 if source[0] & 0xf0 != SEQUENCE_HEADER {
610 return Err(invalid("index sequence header is invalid"));
611 }
612 let version = source[0] & 0x0f;
613 if version > MAX_INDEX_VERSION {
614 return Err(GltfError::Unsupported(format!(
615 "EXT_meshopt_compression index codec version {version}"
616 )));
617 }
618
619 let mut data = 1usize;
620 let safe_end = source.len() - 4;
621 let mut last = [0u32; 2];
622
623 for i in 0..count {
624 if data >= safe_end {
625 return Err(invalid("index sequence data is truncated"));
626 }
627 let value = decode_vbyte(source, &mut data)?;
628 let baseline = (value & 1) as usize;
629 let value = value >> 1;
630 let delta = (value >> 1) ^ (0u32.wrapping_sub(value & 1));
631 let index = last[baseline].wrapping_add(delta);
632 last[baseline] = index;
633 write_index(destination, i * size, size, index);
634 }
635
636 if data != safe_end {
637 return Err(invalid("index sequence has trailing data"));
638 }
639 Ok(())
640}
641
642fn push_edge_fifo(fifo: &mut [[u32; 2]; 16], a: u32, b: u32, offset: &mut usize) {
643 fifo[*offset] = [a, b];
644 *offset = (*offset + 1) & 15;
645}
646
647fn push_vertex_fifo(fifo: &mut [u32; 16], v: u32, offset: &mut usize, advance: usize) {
648 fifo[*offset] = v;
649 *offset = (*offset + advance) & 15;
650}
651
652fn decode_vbyte(source: &[u8], pos: &mut usize) -> Result<u32> {
653 let lead = *source
654 .get(*pos)
655 .ok_or_else(|| invalid("variable-length index is truncated"))?;
656 *pos += 1;
657 if lead < 128 {
658 return Ok(u32::from(lead));
659 }
660 let mut result = u32::from(lead & 127);
661 let mut shift = 7;
662 for _ in 0..4 {
663 let group = *source
664 .get(*pos)
665 .ok_or_else(|| invalid("variable-length index is truncated"))?;
666 *pos += 1;
667 result |= u32::from(group & 127) << shift;
668 shift += 7;
669 if group < 128 {
670 break;
671 }
672 }
673 Ok(result)
674}
675
676fn decode_index(source: &[u8], pos: &mut usize, last: u32) -> Result<u32> {
677 let value = decode_vbyte(source, pos)?;
678 let delta = (value >> 1) ^ (0u32.wrapping_sub(value & 1));
679 Ok(last.wrapping_add(delta))
680}
681
682fn write_triangle(
683 destination: &mut [u8],
684 written: &mut usize,
685 size: usize,
686 a: u32,
687 b: u32,
688 c: u32,
689) {
690 write_index(destination, *written, size, a);
691 write_index(destination, *written + size, size, b);
692 write_index(destination, *written + 2 * size, size, c);
693 *written += 3 * size;
694}
695
696fn write_index(destination: &mut [u8], offset: usize, size: usize, index: u32) {
697 if size == 2 {
698 destination[offset..offset + 2].copy_from_slice(&(index as u16).to_le_bytes());
699 } else {
700 destination[offset..offset + 4].copy_from_slice(&index.to_le_bytes());
701 }
702}
703
704pub fn apply_filter(
706 data: &mut [u8],
707 filter: MeshoptFilter,
708 count: usize,
709 stride: usize,
710) -> Result<()> {
711 match filter {
712 MeshoptFilter::None => Ok(()),
713 MeshoptFilter::Octahedral => {
714 if stride != 4 && stride != 8 {
715 return Err(invalid("OCTAHEDRAL filter needs a 4 or 8 byte stride"));
716 }
717 filter_octahedral(data, count, stride);
718 Ok(())
719 }
720 MeshoptFilter::Quaternion => {
721 if stride != 8 {
722 return Err(invalid("QUATERNION filter needs an 8 byte stride"));
723 }
724 filter_quaternion(data, count);
725 Ok(())
726 }
727 MeshoptFilter::Exponential => {
728 if !stride.is_multiple_of(4) {
729 return Err(invalid("EXPONENTIAL filter needs a 4 byte aligned stride"));
730 }
731 filter_exponential(data, count * (stride / 4));
732 Ok(())
733 }
734 MeshoptFilter::Color => {
735 if stride != 4 && stride != 8 {
736 return Err(invalid("COLOR filter needs a 4 or 8 byte stride"));
737 }
738 filter_color(data, count, stride);
739 Ok(())
740 }
741 }
742}
743
744fn filter_color(data: &mut [u8], count: usize, stride: usize) {
751 let component = stride / 4;
752 let max = if component == 1 {
753 f32::from(u8::MAX)
754 } else {
755 f32::from(u16::MAX)
756 };
757 for i in 0..count {
758 let base = i * stride;
759 let unsigned =
760 |data: &[u8], index: usize| read_unsigned(data, base + index * component, component);
761 let signed =
762 |data: &[u8], index: usize| read_signed(data, base + index * component, component);
763
764 let mut scale = unsigned(data, 3);
767 scale |= scale >> 1;
768 scale |= scale >> 2;
769 scale |= scale >> 4;
770 scale |= scale >> 8;
771
772 let y = unsigned(data, 0);
773 let co = signed(data, 1);
774 let cg = signed(data, 2);
775 let r = y + co - cg;
776 let g = y + cg;
777 let b = y - co - cg;
778
779 let alpha = unsigned(data, 3);
782 let a = ((alpha << 1) & scale) | (alpha & 1);
783
784 let factor = max / scale as f32;
785 let round = |value: i32| (value as f32 * factor + 0.5) as i32;
786 for (index, value) in [r, g, b, a].into_iter().enumerate() {
787 write_unsigned(data, base + index * component, component, round(value));
788 }
789 }
790}
791
792fn filter_octahedral(data: &mut [u8], count: usize, stride: usize) {
793 let component = stride / 4;
794 let max = if component == 1 {
795 f32::from(i8::MAX)
796 } else {
797 f32::from(i16::MAX)
798 };
799 for i in 0..count {
800 let base = i * stride;
801 let read = |index: usize| read_signed(data, base + index * component, component);
802 let x = read(0) as f32;
803 let y = read(1) as f32;
804 let z = read(2) as f32 - x.abs() - y.abs();
805
806 let t = z.min(0.0);
808 let x = x + if x >= 0.0 { t } else { -t };
809 let y = y + if y >= 0.0 { t } else { -t };
810
811 let scale = max / (x * x + y * y + z * z).sqrt();
812 write_signed(data, base, component, round_signed(x * scale));
813 write_signed(data, base + component, component, round_signed(y * scale));
814 write_signed(
815 data,
816 base + 2 * component,
817 component,
818 round_signed(z * scale),
819 );
820 }
821}
822
823fn filter_quaternion(data: &mut [u8], count: usize) {
824 let scale = 32767.0 / 2.0f32.sqrt();
825 for i in 0..count {
826 let base = i * 8;
827 let input = [
828 read_signed(data, base, 2) as f32,
829 read_signed(data, base + 2, 2) as f32,
830 read_signed(data, base + 4, 2) as f32,
831 ];
832 let packed = read_signed(data, base + 6, 2);
833
834 let s = (packed | 3) as f32;
836 let ww = s * s * 2.0 - input[0] * input[0] - input[1] * input[1] - input[2] * input[2];
837 let w = ww.max(0.0).sqrt();
838 let ss = scale / s;
839
840 let component = (packed & 3) as usize;
841 for (axis, value) in input.iter().enumerate() {
842 let slot = (component + axis + 1) & 3;
843 write_signed(data, base + slot * 2, 2, round_signed(value * ss));
844 }
845 write_signed(data, base + component * 2, 2, round_signed(w * ss));
846 }
847}
848
849fn filter_exponential(data: &mut [u8], count: usize) {
850 for i in 0..count {
851 let base = i * 4;
852 let value =
853 u32::from_le_bytes([data[base], data[base + 1], data[base + 2], data[base + 3]]);
854 let mantissa = ((value << 8) as i32) >> 8;
855 let exponent = (value as i32) >> 24;
856 let scale = f32::from_bits(((exponent + 127) as u32) << 23);
858 data[base..base + 4].copy_from_slice(&(scale * mantissa as f32).to_bits().to_le_bytes());
859 }
860}
861
862fn read_signed(data: &[u8], offset: usize, size: usize) -> i32 {
863 if size == 1 {
864 i32::from(data[offset] as i8)
865 } else {
866 i32::from(i16::from_le_bytes([data[offset], data[offset + 1]]))
867 }
868}
869
870fn write_signed(data: &mut [u8], offset: usize, size: usize, value: i32) {
871 if size == 1 {
872 data[offset] = value as u8;
873 } else {
874 data[offset..offset + 2].copy_from_slice(&(value as i16).to_le_bytes());
875 }
876}
877
878fn read_unsigned(data: &[u8], offset: usize, size: usize) -> i32 {
879 if size == 1 {
880 i32::from(data[offset])
881 } else {
882 i32::from(u16::from_le_bytes([data[offset], data[offset + 1]]))
883 }
884}
885
886fn write_unsigned(data: &mut [u8], offset: usize, size: usize, value: i32) {
887 if size == 1 {
888 data[offset] = value as u8;
889 } else {
890 data[offset..offset + 2].copy_from_slice(&(value as u16).to_le_bytes());
891 }
892}
893
894fn round_signed(value: f32) -> i32 {
895 (value + if value >= 0.0 { 0.5 } else { -0.5 }) as i32
896}
897
898#[cfg(test)]
899mod tests {
900 use super::*;
901
902 fn vertex_stream(planes: &[Vec<u8>], baseline: &[u8]) -> Vec<u8> {
907 let count = planes[0].len();
908 let aligned = (count + BYTE_GROUP_SIZE - 1) & !(BYTE_GROUP_SIZE - 1);
909 let mut stream = vec![VERTEX_HEADER];
910 for plane in planes {
911 let groups = aligned / BYTE_GROUP_SIZE;
912 let mut header = vec![0u8; groups.div_ceil(4)];
913 for group in 0..groups {
914 header[group / 4] |= 3 << ((group % 4) * 2);
916 }
917 stream.extend_from_slice(&header);
918 stream.extend_from_slice(plane);
919 stream.resize(stream.len() + aligned - count, 0);
920 }
921 stream.resize(stream.len() + TAIL_MIN_SIZE_V0 - baseline.len(), 0);
923 stream.extend_from_slice(baseline);
924 stream
925 }
926
927 fn zigzag(value: i8) -> u8 {
928 ((value << 1) ^ (value >> 7)) as u8
929 }
930
931 #[test]
932 fn vertex_deltas_accumulate_from_the_stream_tail() {
933 let planes = vec![
934 vec![zigzag(1), zigzag(2)],
935 vec![zigzag(0), zigzag(-1)],
936 vec![zigzag(0), zigzag(0)],
937 vec![zigzag(-4), zigzag(0)],
938 ];
939 let stream = vertex_stream(&planes, &[10, 20, 30, 40]);
940
941 let mut decoded = [0u8; 8];
942 decode_vertex_buffer(&mut decoded, 2, 4, &stream).unwrap();
943
944 assert_eq!(decoded, [11, 20, 30, 36, 13, 19, 30, 36]);
947 }
948
949 #[test]
950 fn vertex_stream_rejects_a_truncated_tail() {
951 let planes = vec![vec![0u8], vec![0], vec![0], vec![0]];
952 let mut stream = vertex_stream(&planes, &[1, 2, 3, 4]);
953 stream.truncate(stream.len() - 1);
954
955 let mut decoded = [0u8; 4];
956 assert!(decode_vertex_buffer(&mut decoded, 1, 4, &stream).is_err());
957 }
958
959 #[test]
960 fn index_buffer_decodes_a_restarted_triangle() {
961 let mut stream = vec![INDEX_HEADER | 1, 0xfe, 0x00];
964 stream.resize(stream.len() + 16, 0);
965
966 let mut decoded = [0u8; 6];
967 decode_index_buffer(&mut decoded, 3, 2, &stream).unwrap();
968
969 assert_eq!(decoded, [0, 0, 1, 0, 2, 0]);
970 }
971
972 #[test]
973 fn index_sequence_decodes_zigzag_deltas() {
974 let stream = vec![SEQUENCE_HEADER, 0x00, 0x04, 0x04, 0, 0, 0, 0];
976
977 let mut decoded = [0u8; 12];
978 decode_index_sequence(&mut decoded, 3, 4, &stream).unwrap();
979
980 assert_eq!(
981 decoded,
982 [0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0],
983 "sequence indices are delta coded against two baselines"
984 );
985 }
986
987 #[test]
1000 fn color_filter_recovers_rgba_from_luma_chroma() {
1001 let mut narrow = vec![
1003 10u8,
1004 (-3i8) as u8,
1005 2,
1006 15,
1007 40,
1008 5,
1009 (-7i8) as u8,
1010 63,
1011 8,
1012 (-2i8) as u8,
1013 3,
1014 9,
1015 ];
1016 apply_filter(&mut narrow, MeshoptFilter::Color, 3, 4).unwrap();
1017 assert_eq!(
1018 narrow,
1019 [85, 204, 187, 255, 210, 134, 170, 255, 51, 187, 119, 51]
1020 );
1021
1022 let mut wide = Vec::new();
1024 for value in [
1025 800u16,
1026 (-100i16) as u16,
1027 60,
1028 1023,
1029 300,
1030 25,
1031 (-40i16) as u16,
1032 511,
1033 700,
1034 90,
1035 (-30i16) as u16,
1036 600,
1037 50000,
1038 (-3000i16) as u16,
1039 1000,
1040 32769,
1041 ] {
1042 wide.extend_from_slice(&value.to_le_bytes());
1043 }
1044 apply_filter(&mut wide, MeshoptFilter::Color, 4, 8).unwrap();
1045 let decoded: Vec<u16> = wide
1046 .chunks_exact(2)
1047 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
1048 .collect();
1049 assert_eq!(
1050 decoded,
1051 [
1052 40999, 55093, 53812, 65535, 46811, 33345, 40398, 65535, 52531, 42921, 40999, 11275,
1053 46000, 51000, 52000, 3
1054 ]
1055 );
1056 }
1057
1058 #[test]
1059 fn octahedral_filter_restores_unit_length_vectors() {
1060 let mut data = vec![0u8, 0, 127, 0, 64, 0, 63, 7];
1061 apply_filter(&mut data, MeshoptFilter::Octahedral, 2, 4).unwrap();
1062
1063 assert_eq!(&data[..4], &[0, 0, 127, 0]);
1064 let x = data[4] as i8 as f32;
1065 let y = data[5] as i8 as f32;
1066 let z = data[6] as i8 as f32;
1067 assert!(
1068 ((x * x + y * y + z * z).sqrt() - 127.0).abs() < 1.0,
1069 "decoded normal {x},{y},{z} is not unit length"
1070 );
1071 assert_eq!(data[7], 7, "the fourth component stays untouched");
1072 }
1073
1074 #[test]
1075 fn quaternion_filter_restores_the_dropped_component() {
1076 let mut data = vec![0u8, 0, 0, 0, 0, 0, 3, 0];
1078 apply_filter(&mut data, MeshoptFilter::Quaternion, 1, 8).unwrap();
1079
1080 let components: Vec<i16> = data
1081 .chunks_exact(2)
1082 .map(|bytes| i16::from_le_bytes([bytes[0], bytes[1]]))
1083 .collect();
1084 assert_eq!(components, [0, 0, 0, 32767]);
1085 }
1086
1087 #[test]
1088 fn exponential_filter_rebuilds_floats() {
1089 let mut data = Vec::new();
1090 data.extend_from_slice(&1u32.to_le_bytes());
1092 data.extend_from_slice(&(((1i32 << 24) | 0x00fffffd) as u32).to_le_bytes());
1093 apply_filter(&mut data, MeshoptFilter::Exponential, 2, 4).unwrap();
1094
1095 let decoded: Vec<f32> = data
1096 .chunks_exact(4)
1097 .map(|bytes| f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
1098 .collect();
1099 assert_eq!(decoded, [1.0, -6.0]);
1100 }
1101
1102 #[test]
1103 fn buffer_view_size_must_match_the_declared_layout() {
1104 let mut destination = [0u8; 7];
1105 let error = decode_buffer_view(
1106 &mut destination,
1107 &[],
1108 MeshoptMode::Attributes,
1109 MeshoptFilter::None,
1110 2,
1111 4,
1112 )
1113 .unwrap_err();
1114 assert!(matches!(error, GltfError::InvalidGltf(_)));
1115 }
1116}