1use std::collections::{BTreeSet, HashMap};
49#[cfg(feature = "gltf-reader")]
50use std::path::Path;
51
52use draco_core::decoder_buffer::DecoderBuffer;
53use draco_core::mesh::Mesh;
54use draco_core::mesh_decoder::MeshDecoder;
55use serde_json::{Map, Value};
56
57pub use crate::gltf_container::OutputFormat;
58#[cfg(feature = "gltf-reader")]
59use crate::gltf_container::{
60 parse_gltf_container, serialize_gltf_document, FileResourceResolver, ResourceLimits,
61 ResourceResolver,
62};
63use crate::gltf_geometry::{
64 component_type_for_data_type, gltf_type_for_num_components, validate_semantic_accessor,
65 GltfError,
66};
67use crate::gltf_khr_draco::{parse_khr_draco_mesh_compression, validate_khr_draco_document};
70#[cfg(feature = "gltf-reader")]
71use crate::gltf_reader::GltfReader;
72use crate::gltf_writer::encode_draco_mesh_with_info;
73
74type Result<T> = std::result::Result<T, GltfError>;
75
76const KHR_DRACO: &str = "KHR_draco_mesh_compression";
77const MODE_TRIANGLES: u64 = 4;
78const MODE_TRIANGLE_STRIP: u64 = 5;
79
80#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
82pub enum EncodingMethod {
83 #[default]
85 Auto,
86 Sequential,
88 Edgebreaker,
90}
91
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94pub struct QuantizationOptions {
95 pub position: Option<u8>,
96 pub normal: Option<u8>,
97 pub color: Option<u8>,
98 pub texcoord: Option<u8>,
99 pub generic: Option<u8>,
100}
101
102impl Default for QuantizationOptions {
103 fn default() -> Self {
104 Self {
105 position: Some(14),
106 normal: Some(10),
107 color: Some(8),
108 texcoord: Some(12),
109 generic: Some(8),
110 }
111 }
112}
113
114#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116pub struct GltfCompressionOptions {
117 pub quantization: QuantizationOptions,
118 pub encoding_speed: u8,
119 pub decoding_speed: u8,
120 pub encoding_method: EncodingMethod,
121 pub output_format: OutputFormat,
122}
123
124impl GltfCompressionOptions {
125 pub fn validate(&self) -> Result<()> {
127 if self.encoding_speed > 10 {
128 return Err(GltfError::InvalidOptions(format!(
129 "encoding_speed {} is outside 0..=10",
130 self.encoding_speed
131 )));
132 }
133 if self.decoding_speed > 10 {
134 return Err(GltfError::InvalidOptions(format!(
135 "decoding_speed {} is outside 0..=10",
136 self.decoding_speed
137 )));
138 }
139 validate_quantization("position", self.quantization.position, 1, 31)?;
140 validate_quantization("normal", self.quantization.normal, 2, 30)?;
141 validate_quantization("color", self.quantization.color, 1, 31)?;
142 validate_quantization("texcoord", self.quantization.texcoord, 1, 31)?;
143 validate_quantization("generic", self.quantization.generic, 1, 31)?;
144 Ok(())
145 }
146}
147
148impl Default for GltfCompressionOptions {
149 fn default() -> Self {
150 Self {
151 quantization: QuantizationOptions::default(),
152 encoding_speed: 5,
153 decoding_speed: 5,
154 encoding_method: EncodingMethod::Auto,
155 output_format: OutputFormat::SameAsInput,
156 }
157 }
158}
159
160fn validate_quantization(name: &str, bits: Option<u8>, min: u8, max: u8) -> Result<()> {
161 if bits.is_some_and(|bits| !(min..=max).contains(&bits)) {
162 return Err(GltfError::InvalidOptions(format!(
163 "{name} quantization must be None or {min}..={max}"
164 )));
165 }
166 Ok(())
167}
168
169#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
171pub struct PrimitiveLocation {
172 pub mesh: usize,
173 pub primitive: usize,
174}
175
176#[derive(Clone, Debug, PartialEq, Eq)]
178pub enum PreserveReason {
179 AlreadyDraco,
180 UnsupportedMode { mode: u32 },
181 UnsupportedLayout { detail: String },
182 SparseAccessor { accessor: usize },
183 MorphTargets,
184 SharedAccessor { accessor: usize },
185}
186
187#[derive(Clone, Debug, PartialEq, Eq)]
189pub struct PreservedPrimitive {
190 pub primitive: PrimitiveLocation,
191 pub reason: PreserveReason,
192}
193
194#[derive(Clone, Debug, Default, PartialEq, Eq)]
196pub struct CompressionReport {
197 pub compressed_primitives: Vec<PrimitiveLocation>,
198 pub preserved_primitives: Vec<PreservedPrimitive>,
199}
200
201#[derive(Clone, Debug, PartialEq, Eq)]
203pub struct CompressionOutput<T> {
204 pub data: T,
205 pub report: CompressionReport,
206}
207
208fn json_index(value: &Value, label: &str) -> Result<usize> {
209 value
210 .as_u64()
211 .and_then(|value| usize::try_from(value).ok())
212 .ok_or_else(|| GltfError::InvalidGltf(format!("{label} is not a valid index")))
213}
214
215#[cfg(feature = "gltf-reader")]
223pub fn compress_gltf_bytes(input: &[u8]) -> Result<CompressionOutput<Vec<u8>>> {
224 compress_gltf_bytes_with_options(input, &GltfCompressionOptions::default())
225}
226
227#[cfg(feature = "gltf-reader")]
229pub fn compress_gltf_bytes_with_options(
230 input: &[u8],
231 options: &GltfCompressionOptions,
232) -> Result<CompressionOutput<Vec<u8>>> {
233 compress_gltf_bytes_impl(input, None, &ResourceLimits::default(), options)
234}
235
236#[cfg(feature = "gltf-reader")]
239pub fn compress_gltf_bytes_with_base_path(
240 input: &[u8],
241 base_path: Option<&Path>,
242 options: &GltfCompressionOptions,
243) -> Result<CompressionOutput<Vec<u8>>> {
244 let resolver =
245 base_path.map(|base| FileResourceResolver::new(base, crate::ExternalFilePolicy::Allow));
246 compress_gltf_bytes_impl(
247 input,
248 resolver
249 .as_ref()
250 .map(|resolver| resolver as &dyn ResourceResolver),
251 &ResourceLimits::default(),
252 options,
253 )
254}
255
256#[cfg(feature = "gltf-reader")]
258pub fn compress_gltf_bytes_with_resolver(
259 input: &[u8],
260 resolver: &dyn ResourceResolver,
261 limits: &ResourceLimits,
262 options: &GltfCompressionOptions,
263) -> Result<CompressionOutput<Vec<u8>>> {
264 compress_gltf_bytes_impl(input, Some(resolver), limits, options)
265}
266
267#[cfg(feature = "gltf-reader")]
268fn compress_gltf_bytes_impl(
269 input: &[u8],
270 resolver: Option<&dyn ResourceResolver>,
271 limits: &ResourceLimits,
272 options: &GltfCompressionOptions,
273) -> Result<CompressionOutput<Vec<u8>>> {
274 options.validate()?;
275 let container = parse_gltf_container(input)?;
276 let doc = serde_json::from_slice::<Value>(container.json)?;
277
278 let reader = if let Some(resolver) = resolver {
281 GltfReader::from_bytes_lenient_with_resolver(input, resolver, limits)?
282 } else {
283 GltfReader::from_bytes_lenient(input)?
284 };
285 let compressed = compress_gltf_value(doc, reader.buffers(), options, |mesh, prim| {
286 reader.decode_primitive_with_semantics(mesh, prim)
287 })?;
288 let (doc, bin) = compressed.data;
289 let data = serialize_gltf_document(&doc, &bin, container.format, options.output_format)?;
290 Ok(CompressionOutput {
291 data,
292 report: compressed.report,
293 })
294}
295
296pub fn compress_gltf_value<F>(
311 mut doc: Value,
312 buffers: &[Vec<u8>],
313 options: &GltfCompressionOptions,
314 decode: F,
315) -> Result<CompressionOutput<(Value, Vec<u8>)>>
316where
317 F: Fn(usize, usize) -> Result<(draco_core::Mesh, Vec<(String, u32)>)>,
318{
319 if !doc.is_object() {
320 return Err(GltfError::InvalidGltf("glTF root is not an object".into()));
321 }
322 options.validate()?;
323 validate_gltf_document_for_repacking(&doc, buffers)?;
324
325 let accessor_users = count_accessor_users(&doc)?;
328
329 let (plans, mut report) = build_plans(&doc, buffers, &decode, &accessor_users, options)?;
330
331 apply_accessor_mutations(&mut doc, &plans)?;
335
336 add_generated_indices(&mut doc, &plans)?;
339
340 let repack = repack_buffers(&mut doc, buffers, &plans)?;
344
345 for (i, plan) in plans.iter().enumerate() {
348 let draco_bv = repack.draco_buffer_views[i];
349 set_primitive_draco_extension(&mut doc, plan, draco_bv)?;
350 }
351
352 if !plans.is_empty() {
353 ensure_extension_listed(&mut doc, "extensionsUsed")?;
354 ensure_extension_listed(&mut doc, "extensionsRequired")?;
355 }
356
357 set_single_buffer(&mut doc, repack.bin.len())?;
358
359 report.compressed_primitives = plans
360 .iter()
361 .map(|plan| PrimitiveLocation {
362 mesh: plan.mesh_idx,
363 primitive: plan.prim_idx,
364 })
365 .collect();
366 Ok(CompressionOutput {
367 data: (doc, repack.bin),
368 report,
369 })
370}
371
372pub fn consolidate_gltf_buffers(
375 mut document: Value,
376 buffers: &[Vec<u8>],
377) -> Result<(Value, Vec<u8>)> {
378 if !document.is_object() {
379 return Err(GltfError::InvalidGltf("glTF root is not an object".into()));
380 }
381 validate_gltf_document_for_repacking(&document, buffers)?;
382 let repack = repack_buffers(&mut document, buffers, &[])?;
383 set_single_buffer(&mut document, repack.bin.len())?;
384 Ok((document, repack.bin))
385}
386
387#[derive(Clone, Copy)]
388struct ValidatedBufferView {
389 buffer: usize,
390 byte_offset: usize,
391 byte_length: usize,
392 byte_stride: Option<usize>,
393}
394
395pub fn validate_gltf_document_for_repacking(document: &Value, buffers: &[Vec<u8>]) -> Result<()> {
404 validate_khr_draco_document(document)?;
405 reject_opaque_binary_references(document)?;
406 validate_gltf_document_binary_layout(document, buffers)
407}
408
409pub fn validate_gltf_document_binary_layout(document: &Value, buffers: &[Vec<u8>]) -> Result<()> {
411 let declared_buffers = optional_array(document, "buffers")?;
412 if declared_buffers.len() != buffers.len() {
413 return Err(GltfError::InvalidGltf(format!(
414 "document declares {} buffers but {} were resolved",
415 declared_buffers.len(),
416 buffers.len()
417 )));
418 }
419 for (index, declaration) in declared_buffers.iter().enumerate() {
420 let declaration = declaration
421 .as_object()
422 .ok_or_else(|| GltfError::InvalidGltf(format!("buffer {index} is not an object")))?;
423 let byte_length = required_usize(declaration, "byteLength", "buffer")?;
424 let actual = buffers[index].len();
425 if actual < byte_length {
426 return Err(GltfError::InvalidGltf(format!(
427 "buffer {index} byteLength {byte_length} exceeds resolved length {actual}"
428 )));
429 }
430 }
431
432 let view_values = optional_array(document, "bufferViews")?;
433 let mut views = Vec::new();
434 views.try_reserve_exact(view_values.len()).map_err(|_| {
435 GltfError::ResourceLimitExceeded("bufferView validation allocation failed".into())
436 })?;
437 for (index, value) in view_values.iter().enumerate() {
438 let view = value.as_object().ok_or_else(|| {
439 GltfError::InvalidGltf(format!("bufferView {index} is not an object"))
440 })?;
441 let buffer = required_usize(view, "buffer", "bufferView")?;
442 let byte_offset = optional_usize(view, "byteOffset", "bufferView")?.unwrap_or(0);
443 let byte_length = required_usize(view, "byteLength", "bufferView")?;
444 let byte_stride = optional_usize(view, "byteStride", "bufferView")?;
445 if let Some(stride) = byte_stride {
446 if !(4..=252).contains(&stride) || !stride.is_multiple_of(4) {
447 return Err(GltfError::InvalidGltf(format!(
448 "bufferView {index} byteStride must be a multiple of 4 in 4..=252"
449 )));
450 }
451 }
452 let buffer_data = buffers.get(buffer).ok_or_else(|| {
453 GltfError::InvalidGltf(format!(
454 "bufferView {index} references invalid buffer {buffer}"
455 ))
456 })?;
457 let end = byte_offset
458 .checked_add(byte_length)
459 .filter(|end| *end <= buffer_data.len())
460 .ok_or_else(|| GltfError::InvalidGltf(format!("bufferView {index} is out of range")))?;
461 let _ = end;
462 views.push(ValidatedBufferView {
463 buffer,
464 byte_offset,
465 byte_length,
466 byte_stride,
467 });
468 }
469
470 let accessor_values = optional_array(document, "accessors")?;
471 for (index, value) in accessor_values.iter().enumerate() {
472 validate_accessor(index, value, &views, buffers)?;
473 }
474 validate_primitive_accessor_contracts(document, accessor_values, &views, buffers)?;
475 validate_accessor_references(document, accessor_values.len())
476}
477
478fn optional_array<'a>(document: &'a Value, key: &str) -> Result<&'a [Value]> {
479 match document.get(key) {
480 Some(value) => value
481 .as_array()
482 .map(Vec::as_slice)
483 .ok_or_else(|| GltfError::InvalidGltf(format!("{key} is not an array"))),
484 None => Ok(&[]),
485 }
486}
487
488fn required_usize(object: &Map<String, Value>, key: &str, label: &str) -> Result<usize> {
489 let value = object
490 .get(key)
491 .ok_or_else(|| GltfError::InvalidGltf(format!("{label} is missing {key}")))?;
492 json_index(value, &format!("{label}.{key}"))
493}
494
495fn optional_usize(object: &Map<String, Value>, key: &str, label: &str) -> Result<Option<usize>> {
496 object
497 .get(key)
498 .map(|value| json_index(value, &format!("{label}.{key}")))
499 .transpose()
500}
501
502fn component_size(component_type: u64, label: &str) -> Result<usize> {
503 match component_type {
504 5120 | 5121 => Ok(1),
505 5122 | 5123 | 5131 => Ok(2),
506 5124..=5126 => Ok(4),
507 5130 | 5132 | 5133 => Ok(8),
508 _ => Err(GltfError::InvalidGltf(format!(
509 "{label} has invalid componentType {component_type}"
510 ))),
511 }
512}
513
514fn accessor_element_size(accessor_type: &str, component_size: usize) -> Result<usize> {
515 let (columns, rows) = match accessor_type {
516 "SCALAR" => (1usize, 1usize),
517 "VEC2" => (1, 2),
518 "VEC3" => (1, 3),
519 "VEC4" => (1, 4),
520 "MAT2" => (2, 2),
521 "MAT3" => (3, 3),
522 "MAT4" => (4, 4),
523 _ => {
524 return Err(GltfError::InvalidGltf(format!(
525 "accessor has invalid type {accessor_type}"
526 )));
527 }
528 };
529 let column_size = rows
530 .checked_mul(component_size)
531 .ok_or_else(|| GltfError::InvalidGltf("accessor element size overflow".into()))?;
532 let column_stride = if columns > 1 && component_size < 4 {
533 column_size
534 .checked_add(3)
535 .map(|size| size / 4 * 4)
536 .ok_or_else(|| GltfError::InvalidGltf("matrix element size overflow".into()))?
537 } else {
538 column_size
539 };
540 columns
541 .checked_mul(column_stride)
542 .ok_or_else(|| GltfError::InvalidGltf("accessor element size overflow".into()))
543}
544
545fn validate_range_in_view(
546 view: ValidatedBufferView,
547 byte_offset: usize,
548 count: usize,
549 element_size: usize,
550 stride: usize,
551 label: &str,
552) -> Result<()> {
553 if stride < element_size {
554 return Err(GltfError::InvalidGltf(format!(
555 "{label} stride {stride} is smaller than element size {element_size}"
556 )));
557 }
558 let byte_length = count
559 .checked_sub(1)
560 .and_then(|prefix| prefix.checked_mul(stride))
561 .and_then(|prefix| prefix.checked_add(element_size))
562 .ok_or_else(|| GltfError::InvalidGltf(format!("{label} byte range overflow")))?;
563 let end = byte_offset
564 .checked_add(byte_length)
565 .ok_or_else(|| GltfError::InvalidGltf(format!("{label} byte range overflow")))?;
566 if end > view.byte_length {
567 return Err(GltfError::InvalidGltf(format!(
568 "{label} does not fit its bufferView"
569 )));
570 }
571 Ok(())
572}
573
574fn validate_accessor(
575 index: usize,
576 value: &Value,
577 views: &[ValidatedBufferView],
578 buffers: &[Vec<u8>],
579) -> Result<()> {
580 let accessor = value
581 .as_object()
582 .ok_or_else(|| GltfError::InvalidGltf(format!("accessor {index} is not an object")))?;
583 let component_type = accessor
584 .get("componentType")
585 .and_then(Value::as_u64)
586 .ok_or_else(|| {
587 GltfError::InvalidGltf(format!("accessor {index} has invalid componentType"))
588 })?;
589 let component_size = component_size(component_type, &format!("accessor {index}"))?;
590 let accessor_type = accessor
591 .get("type")
592 .and_then(Value::as_str)
593 .ok_or_else(|| GltfError::InvalidGltf(format!("accessor {index} has invalid type")))?;
594 let element_size = accessor_element_size(accessor_type, component_size)?;
595 let count = required_usize(accessor, "count", &format!("accessor {index}"))?;
596 if count == 0 {
597 return Err(GltfError::InvalidGltf(format!(
598 "accessor {index} count must be greater than zero"
599 )));
600 }
601 if accessor
602 .get("normalized")
603 .is_some_and(|normalized| !normalized.is_boolean())
604 {
605 return Err(GltfError::InvalidGltf(format!(
606 "accessor {index}.normalized is not a boolean"
607 )));
608 }
609 let byte_offset = optional_usize(accessor, "byteOffset", "accessor")?.unwrap_or(0);
610 if !byte_offset.is_multiple_of(component_size) {
611 return Err(GltfError::InvalidGltf(format!(
612 "accessor {index} byteOffset is not component-aligned"
613 )));
614 }
615 if let Some(view_index) = optional_usize(accessor, "bufferView", "accessor")? {
616 let view = *views.get(view_index).ok_or_else(|| {
617 GltfError::InvalidGltf(format!(
618 "accessor {index} references invalid bufferView {view_index}"
619 ))
620 })?;
621 let stride = view.byte_stride.unwrap_or(element_size);
622 validate_range_in_view(
623 view,
624 byte_offset,
625 count,
626 element_size,
627 stride,
628 &format!("accessor {index}"),
629 )?;
630 } else if byte_offset != 0 {
631 return Err(GltfError::InvalidGltf(format!(
632 "accessor {index} has byteOffset without bufferView"
633 )));
634 }
635
636 if let Some(sparse) = accessor.get("sparse") {
637 validate_sparse_accessor(index, sparse, count, element_size, views, buffers)?;
638 }
639 Ok(())
640}
641
642fn validate_sparse_accessor(
643 accessor_index: usize,
644 sparse: &Value,
645 accessor_count: usize,
646 element_size: usize,
647 views: &[ValidatedBufferView],
648 buffers: &[Vec<u8>],
649) -> Result<()> {
650 let sparse = sparse.as_object().ok_or_else(|| {
651 GltfError::InvalidGltf(format!("accessor {accessor_index}.sparse is not an object"))
652 })?;
653 let count = required_usize(sparse, "count", "sparse accessor")?;
654 if count == 0 || count > accessor_count {
655 return Err(GltfError::InvalidGltf(format!(
656 "accessor {accessor_index} has invalid sparse count {count}"
657 )));
658 }
659
660 let indices = sparse
661 .get("indices")
662 .and_then(Value::as_object)
663 .ok_or_else(|| {
664 GltfError::InvalidGltf(format!(
665 "accessor {accessor_index}.sparse.indices is not an object"
666 ))
667 })?;
668 let indices_view_index = required_usize(indices, "bufferView", "sparse indices")?;
669 let indices_view = *views.get(indices_view_index).ok_or_else(|| {
670 GltfError::InvalidGltf(format!(
671 "sparse indices references invalid bufferView {indices_view_index}"
672 ))
673 })?;
674 if indices_view.byte_stride.is_some() {
675 return Err(GltfError::InvalidGltf(
676 "sparse indices bufferView must not define byteStride".into(),
677 ));
678 }
679 let index_component = indices
680 .get("componentType")
681 .and_then(Value::as_u64)
682 .ok_or_else(|| GltfError::InvalidGltf("sparse indices componentType is invalid".into()))?;
683 let index_size = match index_component {
684 5121 => 1,
685 5123 => 2,
686 5125 => 4,
687 _ => {
688 return Err(GltfError::InvalidGltf(format!(
689 "sparse indices has invalid componentType {index_component}"
690 )));
691 }
692 };
693 let indices_offset = optional_usize(indices, "byteOffset", "sparse indices")?.unwrap_or(0);
694 if !indices_offset.is_multiple_of(index_size) {
695 return Err(GltfError::InvalidGltf(
696 "sparse indices byteOffset is not component-aligned".into(),
697 ));
698 }
699 validate_range_in_view(
700 indices_view,
701 indices_offset,
702 count,
703 index_size,
704 index_size,
705 "sparse indices",
706 )?;
707 let indices_buffer = buffers
708 .get(indices_view.buffer)
709 .ok_or_else(|| GltfError::InvalidGltf("sparse indices buffer is out of range".into()))?;
710 let indices_start = indices_view
711 .byte_offset
712 .checked_add(indices_offset)
713 .ok_or_else(|| GltfError::InvalidGltf("sparse indices offset overflow".into()))?;
714 let mut previous = None;
715 for sparse_index in 0..count {
716 let offset = sparse_index
717 .checked_mul(index_size)
718 .and_then(|offset| indices_start.checked_add(offset))
719 .ok_or_else(|| GltfError::InvalidGltf("sparse indices offset overflow".into()))?;
720 let end = offset
721 .checked_add(index_size)
722 .ok_or_else(|| GltfError::InvalidGltf("sparse indices offset overflow".into()))?;
723 let bytes = indices_buffer
724 .get(offset..end)
725 .ok_or_else(|| GltfError::InvalidGltf("sparse indices are out of range".into()))?;
726 let value = match index_component {
727 5121 => bytes[0] as u32,
728 5123 => u16::from_le_bytes([bytes[0], bytes[1]]) as u32,
729 5125 => u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
730 _ => {
731 return Err(GltfError::InvalidGltf(
732 "sparse indices component type changed after validation".into(),
733 ));
734 }
735 } as usize;
736 if value >= accessor_count {
737 return Err(GltfError::InvalidGltf(format!(
738 "accessor {accessor_index} sparse index {value} is out of range"
739 )));
740 }
741 if previous.is_some_and(|previous| value <= previous) {
742 return Err(GltfError::InvalidGltf(format!(
743 "accessor {accessor_index} sparse indices are not strictly increasing"
744 )));
745 }
746 previous = Some(value);
747 }
748
749 let values = sparse
750 .get("values")
751 .and_then(Value::as_object)
752 .ok_or_else(|| {
753 GltfError::InvalidGltf(format!(
754 "accessor {accessor_index}.sparse.values is not an object"
755 ))
756 })?;
757 let values_view_index = required_usize(values, "bufferView", "sparse values")?;
758 let values_view = *views.get(values_view_index).ok_or_else(|| {
759 GltfError::InvalidGltf(format!(
760 "sparse values references invalid bufferView {values_view_index}"
761 ))
762 })?;
763 if values_view.byte_stride.is_some() {
764 return Err(GltfError::InvalidGltf(
765 "sparse values bufferView must not define byteStride".into(),
766 ));
767 }
768 let values_offset = optional_usize(values, "byteOffset", "sparse values")?.unwrap_or(0);
769 validate_range_in_view(
770 values_view,
771 values_offset,
772 count,
773 element_size,
774 element_size,
775 "sparse values",
776 )
777}
778
779fn validate_primitive_accessor_contracts(
780 document: &Value,
781 accessors: &[Value],
782 views: &[ValidatedBufferView],
783 buffers: &[Vec<u8>],
784) -> Result<()> {
785 for (mesh_index, mesh) in optional_array(document, "meshes")?.iter().enumerate() {
786 let primitives = mesh
787 .get("primitives")
788 .and_then(Value::as_array)
789 .ok_or_else(|| {
790 GltfError::InvalidGltf(format!("mesh {mesh_index}.primitives is not an array"))
791 })?;
792 for (primitive_index, primitive) in primitives.iter().enumerate() {
793 let primitive = primitive.as_object().ok_or_else(|| {
794 GltfError::InvalidGltf(format!(
795 "primitive {mesh_index}:{primitive_index} is not an object"
796 ))
797 })?;
798 let attributes = primitive
799 .get("attributes")
800 .and_then(Value::as_object)
801 .ok_or_else(|| {
802 GltfError::InvalidGltf(format!(
803 "primitive {mesh_index}:{primitive_index}.attributes is not an object"
804 ))
805 })?;
806 let mut vertex_count = None;
807 for (semantic, accessor_index) in attributes {
808 let accessor_index = json_index(accessor_index, "primitive attribute accessor")?;
809 let accessor = accessors
810 .get(accessor_index)
811 .and_then(Value::as_object)
812 .ok_or_else(|| {
813 GltfError::InvalidGltf(format!(
814 "primitive {mesh_index}:{primitive_index} accessor {accessor_index} is out of range"
815 ))
816 })?;
817 let count = required_usize(accessor, "count", "primitive attribute accessor")?;
818 if let Some(expected) = vertex_count {
819 if expected != count {
820 return Err(GltfError::InvalidGltf(format!(
821 "primitive {mesh_index}:{primitive_index} attribute counts do not match"
822 )));
823 }
824 } else {
825 vertex_count = Some(count);
826 }
827 let component_type = accessor
828 .get("componentType")
829 .and_then(Value::as_u64)
830 .and_then(|value| u32::try_from(value).ok())
831 .ok_or_else(|| {
832 GltfError::InvalidGltf(format!(
833 "accessor {accessor_index} has invalid componentType"
834 ))
835 })?;
836 if matches!(component_type, 5120 | 5121 | 5122 | 5123 | 5126) {
840 let accessor_type =
841 accessor
842 .get("type")
843 .and_then(Value::as_str)
844 .ok_or_else(|| {
845 GltfError::InvalidGltf(format!(
846 "accessor {accessor_index} has invalid type"
847 ))
848 })?;
849 let normalized = match accessor.get("normalized") {
850 None => false,
851 Some(Value::Bool(normalized)) => *normalized,
852 Some(_) => {
853 return Err(GltfError::InvalidGltf(format!(
854 "accessor {accessor_index}.normalized is not a boolean"
855 )));
856 }
857 };
858 validate_semantic_accessor(
859 semantic,
860 accessor_type,
861 component_type,
862 normalized,
863 )?;
864 }
865 }
866 let vertex_count = vertex_count.unwrap_or(0);
867 let mode = primitive
868 .get("mode")
869 .and_then(Value::as_u64)
870 .unwrap_or(MODE_TRIANGLES);
871 let element_count = if let Some(indices) = primitive.get("indices") {
872 let accessor_index = json_index(indices, "primitive indices accessor")?;
873 let values =
874 read_unsigned_scalar_accessor(accessor_index, accessors, views, buffers)?;
875 if values.iter().any(|&index| index as usize >= vertex_count) {
876 return Err(GltfError::InvalidGltf(format!(
877 "primitive {mesh_index}:{primitive_index} index is out of range for {vertex_count} vertices"
878 )));
879 }
880 values.len()
881 } else {
882 vertex_count
883 };
884 validate_primitive_element_count(mode, element_count, mesh_index, primitive_index)?;
885
886 if let Some(targets) = primitive.get("targets") {
887 let targets = targets.as_array().ok_or_else(|| {
888 GltfError::InvalidGltf("primitive.targets is not an array".into())
889 })?;
890 for (target_index, target) in targets.iter().enumerate() {
891 let target = target.as_object().ok_or_else(|| {
892 GltfError::InvalidGltf("morph target is not an object".into())
893 })?;
894 if target.is_empty() {
895 return Err(GltfError::InvalidGltf("morph target is empty".into()));
896 }
897 for (semantic, accessor_index) in target {
898 let accessor_index = json_index(accessor_index, "morph target accessor")?;
899 let accessor = accessors
900 .get(accessor_index)
901 .and_then(Value::as_object)
902 .ok_or_else(|| {
903 GltfError::InvalidGltf(format!(
904 "morph target accessor {accessor_index} is out of range"
905 ))
906 })?;
907 let count = required_usize(accessor, "count", "morph target accessor")?;
908 if count != vertex_count
909 || !matches!(semantic.as_str(), "POSITION" | "NORMAL" | "TANGENT")
910 || accessor.get("type").and_then(Value::as_str) != Some("VEC3")
911 || accessor.get("componentType").and_then(Value::as_u64) != Some(5126)
912 || accessor
913 .get("normalized")
914 .is_some_and(|normalized| normalized != &Value::Bool(false))
915 {
916 return Err(GltfError::InvalidGltf(format!(
917 "primitive {mesh_index}:{primitive_index} morph target {target_index} {semantic} accessor has an invalid contract"
918 )));
919 }
920 }
921 }
922 }
923 }
924 }
925 Ok(())
926}
927
928fn validate_primitive_element_count(
929 mode: u64,
930 count: usize,
931 mesh: usize,
932 primitive: usize,
933) -> Result<()> {
934 let valid = match mode {
935 0 => count >= 1,
936 1 => count >= 2 && count.is_multiple_of(2),
937 2 | 3 => count >= 2,
938 4 => count >= 3 && count.is_multiple_of(3),
939 5 | 6 => count >= 3,
940 _ => false,
941 };
942 if !valid {
943 return Err(GltfError::InvalidGltf(format!(
944 "primitive {mesh}:{primitive} has invalid element count {count} for mode {mode}"
945 )));
946 }
947 Ok(())
948}
949
950fn read_unsigned_scalar_accessor(
951 accessor_index: usize,
952 accessors: &[Value],
953 views: &[ValidatedBufferView],
954 buffers: &[Vec<u8>],
955) -> Result<Vec<u32>> {
956 let accessor = accessors
957 .get(accessor_index)
958 .and_then(Value::as_object)
959 .ok_or_else(|| {
960 GltfError::InvalidGltf(format!("accessor {accessor_index} is out of range"))
961 })?;
962 if accessor.get("type").and_then(Value::as_str) != Some("SCALAR")
963 || accessor
964 .get("normalized")
965 .is_some_and(|normalized| normalized != &Value::Bool(false))
966 {
967 return Err(GltfError::InvalidGltf(format!(
968 "indices accessor {accessor_index} has an invalid contract"
969 )));
970 }
971 let component_type = accessor
972 .get("componentType")
973 .and_then(Value::as_u64)
974 .ok_or_else(|| GltfError::InvalidGltf("indices componentType is invalid".into()))?;
975 let value_size = match component_type {
976 5121 => 1,
977 5123 => 2,
978 5125 => 4,
979 _ => {
980 return Err(GltfError::InvalidGltf(format!(
981 "indices accessor {accessor_index} has invalid componentType {component_type}"
982 )));
983 }
984 };
985 let count = required_usize(accessor, "count", "indices accessor")?;
986 let mut values = Vec::new();
987 values.try_reserve_exact(count).map_err(|_| {
988 GltfError::ResourceLimitExceeded("indices validation allocation failed".into())
989 })?;
990 values.resize(count, 0);
991 if let Some(view_index) = optional_usize(accessor, "bufferView", "indices accessor")? {
992 let view = *views.get(view_index).ok_or_else(|| {
993 GltfError::InvalidGltf(format!("indices bufferView {view_index} is out of range"))
994 })?;
995 if view.byte_stride.is_some() {
996 return Err(GltfError::InvalidGltf(
997 "indices bufferView must not define byteStride".into(),
998 ));
999 }
1000 let byte_offset = optional_usize(accessor, "byteOffset", "indices accessor")?.unwrap_or(0);
1001 for (index, value) in values.iter_mut().enumerate() {
1002 *value = read_unsigned_from_view(
1003 view,
1004 byte_offset,
1005 index,
1006 value_size,
1007 component_type,
1008 buffers,
1009 "indices accessor",
1010 )?;
1011 }
1012 }
1013 if let Some(sparse) = accessor.get("sparse").and_then(Value::as_object) {
1014 let sparse_count = required_usize(sparse, "count", "sparse accessor")?;
1015 let sparse_indices = sparse
1016 .get("indices")
1017 .and_then(Value::as_object)
1018 .ok_or_else(|| GltfError::InvalidGltf("sparse indices are invalid".into()))?;
1019 let sparse_values = sparse
1020 .get("values")
1021 .and_then(Value::as_object)
1022 .ok_or_else(|| GltfError::InvalidGltf("sparse values are invalid".into()))?;
1023 let sparse_index_component = sparse_indices
1024 .get("componentType")
1025 .and_then(Value::as_u64)
1026 .ok_or_else(|| GltfError::InvalidGltf("sparse index type is invalid".into()))?;
1027 let sparse_index_size = component_size(sparse_index_component, "sparse indices")?;
1028 let index_view = *views
1029 .get(required_usize(
1030 sparse_indices,
1031 "bufferView",
1032 "sparse indices",
1033 )?)
1034 .ok_or_else(|| GltfError::InvalidGltf("sparse indices view is invalid".into()))?;
1035 let value_view = *views
1036 .get(required_usize(
1037 sparse_values,
1038 "bufferView",
1039 "sparse values",
1040 )?)
1041 .ok_or_else(|| GltfError::InvalidGltf("sparse values view is invalid".into()))?;
1042 let index_offset =
1043 optional_usize(sparse_indices, "byteOffset", "sparse indices")?.unwrap_or(0);
1044 let value_offset =
1045 optional_usize(sparse_values, "byteOffset", "sparse values")?.unwrap_or(0);
1046 for sparse_index in 0..sparse_count {
1047 let destination = read_unsigned_from_view(
1048 index_view,
1049 index_offset,
1050 sparse_index,
1051 sparse_index_size,
1052 sparse_index_component,
1053 buffers,
1054 "sparse indices",
1055 )? as usize;
1056 let value = read_unsigned_from_view(
1057 value_view,
1058 value_offset,
1059 sparse_index,
1060 value_size,
1061 component_type,
1062 buffers,
1063 "sparse values",
1064 )?;
1065 *values
1066 .get_mut(destination)
1067 .ok_or_else(|| GltfError::InvalidGltf("sparse index is out of range".into()))? =
1068 value;
1069 }
1070 }
1071 Ok(values)
1072}
1073
1074fn read_unsigned_from_view(
1075 view: ValidatedBufferView,
1076 byte_offset: usize,
1077 index: usize,
1078 component_size: usize,
1079 component_type: u64,
1080 buffers: &[Vec<u8>],
1081 label: &str,
1082) -> Result<u32> {
1083 let start = index
1084 .checked_mul(component_size)
1085 .and_then(|offset| byte_offset.checked_add(offset))
1086 .and_then(|offset| view.byte_offset.checked_add(offset))
1087 .ok_or_else(|| GltfError::InvalidGltf(format!("{label} offset overflow")))?;
1088 let end = start
1089 .checked_add(component_size)
1090 .ok_or_else(|| GltfError::InvalidGltf(format!("{label} offset overflow")))?;
1091 let bytes = buffers
1092 .get(view.buffer)
1093 .and_then(|buffer| buffer.get(start..end))
1094 .ok_or_else(|| GltfError::InvalidGltf(format!("{label} is out of range")))?;
1095 match component_type {
1096 5121 => Ok(bytes[0] as u32),
1097 5123 => Ok(u16::from_le_bytes([bytes[0], bytes[1]]) as u32),
1098 5125 => Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])),
1099 _ => Err(GltfError::InvalidGltf(format!(
1100 "{label} has invalid componentType {component_type}"
1101 ))),
1102 }
1103}
1104
1105fn validate_accessor_reference(value: &Value, count: usize, label: &str) -> Result<()> {
1106 let index = json_index(value, label)?;
1107 if index >= count {
1108 return Err(GltfError::InvalidGltf(format!(
1109 "{label} {index} is out of range"
1110 )));
1111 }
1112 Ok(())
1113}
1114
1115fn validate_accessor_references(document: &Value, accessor_count: usize) -> Result<()> {
1116 for (mesh_index, mesh) in optional_array(document, "meshes")?.iter().enumerate() {
1117 let mesh = mesh
1118 .as_object()
1119 .ok_or_else(|| GltfError::InvalidGltf(format!("mesh {mesh_index} is not an object")))?;
1120 let primitives = mesh
1121 .get("primitives")
1122 .and_then(Value::as_array)
1123 .ok_or_else(|| {
1124 GltfError::InvalidGltf(format!("mesh {mesh_index}.primitives is not an array"))
1125 })?;
1126 if primitives.is_empty() {
1127 return Err(GltfError::InvalidGltf(format!(
1128 "mesh {mesh_index} has no primitives"
1129 )));
1130 }
1131 for (primitive_index, primitive) in primitives.iter().enumerate() {
1132 let primitive = primitive.as_object().ok_or_else(|| {
1133 GltfError::InvalidGltf(format!(
1134 "primitive {mesh_index}:{primitive_index} is not an object"
1135 ))
1136 })?;
1137 let attributes = primitive
1138 .get("attributes")
1139 .and_then(Value::as_object)
1140 .ok_or_else(|| {
1141 GltfError::InvalidGltf(format!(
1142 "primitive {mesh_index}:{primitive_index}.attributes is not an object"
1143 ))
1144 })?;
1145 if attributes.is_empty() {
1146 return Err(GltfError::InvalidGltf(format!(
1147 "primitive {mesh_index}:{primitive_index}.attributes is empty"
1148 )));
1149 }
1150 for accessor in attributes.values() {
1151 validate_accessor_reference(accessor, accessor_count, "primitive attribute")?;
1152 }
1153 if let Some(indices) = primitive.get("indices") {
1154 validate_accessor_reference(indices, accessor_count, "primitive indices")?;
1155 }
1156 if let Some(mode) = primitive.get("mode") {
1157 let mode = mode.as_u64().ok_or_else(|| {
1158 GltfError::InvalidGltf("primitive.mode is not an integer".into())
1159 })?;
1160 if mode > 6 {
1161 return Err(GltfError::InvalidGltf(format!(
1162 "primitive mode {mode} is outside the glTF enum"
1163 )));
1164 }
1165 }
1166 if let Some(targets) = primitive.get("targets") {
1167 let targets = targets.as_array().ok_or_else(|| {
1168 GltfError::InvalidGltf("primitive.targets is not an array".into())
1169 })?;
1170 for target in targets {
1171 let target = target.as_object().ok_or_else(|| {
1172 GltfError::InvalidGltf("morph target is not an object".into())
1173 })?;
1174 if target.is_empty() {
1175 return Err(GltfError::InvalidGltf("morph target is empty".into()));
1176 }
1177 for accessor in target.values() {
1178 validate_accessor_reference(
1179 accessor,
1180 accessor_count,
1181 "morph target accessor",
1182 )?;
1183 }
1184 }
1185 }
1186 }
1187 }
1188
1189 for animation in optional_array(document, "animations")? {
1190 if let Some(samplers) = animation.get("samplers") {
1191 let samplers = samplers.as_array().ok_or_else(|| {
1192 GltfError::InvalidGltf("animation.samplers is not an array".into())
1193 })?;
1194 for sampler in samplers {
1195 let sampler = sampler.as_object().ok_or_else(|| {
1196 GltfError::InvalidGltf("animation sampler is not an object".into())
1197 })?;
1198 for key in ["input", "output"] {
1199 let accessor = sampler.get(key).ok_or_else(|| {
1200 GltfError::InvalidGltf(format!("animation sampler is missing {key}"))
1201 })?;
1202 validate_accessor_reference(accessor, accessor_count, "animation accessor")?;
1203 }
1204 }
1205 }
1206 }
1207 for skin in optional_array(document, "skins")? {
1208 if let Some(accessor) = skin.get("inverseBindMatrices") {
1209 validate_accessor_reference(accessor, accessor_count, "inverseBindMatrices")?;
1210 }
1211 }
1212 for node in optional_array(document, "nodes")? {
1213 if let Some(attributes) = node
1214 .get("extensions")
1215 .and_then(|extensions| extensions.get("EXT_mesh_gpu_instancing"))
1216 .and_then(|extension| extension.get("attributes"))
1217 {
1218 let attributes = attributes.as_object().ok_or_else(|| {
1219 GltfError::InvalidGltf("EXT_mesh_gpu_instancing.attributes is not an object".into())
1220 })?;
1221 for accessor in attributes.values() {
1222 validate_accessor_reference(accessor, accessor_count, "instancing accessor")?;
1223 }
1224 }
1225 }
1226 Ok(())
1227}
1228
1229struct CompressPlan {
1231 mesh_idx: usize,
1232 prim_idx: usize,
1233 draco_bytes: Vec<u8>,
1234 semantic_to_id: Vec<(String, u32)>,
1236 attribute_accessors: Vec<usize>,
1238 indices_accessor: Option<usize>,
1242 num_points: usize,
1243 num_indices: usize,
1244}
1245
1246fn build_plans<F>(
1247 doc: &Value,
1248 buffers: &[Vec<u8>],
1249 decode: &F,
1250 accessor_users: &HashMap<usize, usize>,
1251 options: &GltfCompressionOptions,
1252) -> Result<(Vec<CompressPlan>, CompressionReport)>
1253where
1254 F: Fn(usize, usize) -> Result<(draco_core::Mesh, Vec<(String, u32)>)>,
1255{
1256 let mut plans = Vec::new();
1257 let mut report = CompressionReport::default();
1258 let Some(meshes) = doc.get("meshes").and_then(Value::as_array) else {
1259 return Ok((plans, report));
1260 };
1261
1262 for (mesh_idx, mesh) in meshes.iter().enumerate() {
1263 let Some(primitives) = mesh.get("primitives").and_then(Value::as_array) else {
1264 continue;
1265 };
1266 for (prim_idx, prim) in primitives.iter().enumerate() {
1267 let location = PrimitiveLocation {
1268 mesh: mesh_idx,
1269 primitive: prim_idx,
1270 };
1271 match plan_for_primitive(
1272 doc,
1273 prim,
1274 buffers,
1275 location,
1276 decode,
1277 accessor_users,
1278 options,
1279 )? {
1280 PlanDecision::Compress(plan) => plans.push(plan),
1281 PlanDecision::Preserve(reason) => {
1282 report.preserved_primitives.push(PreservedPrimitive {
1283 primitive: location,
1284 reason,
1285 })
1286 }
1287 }
1288 }
1289 }
1290 Ok((plans, report))
1291}
1292
1293enum PlanDecision {
1294 Compress(CompressPlan),
1295 Preserve(PreserveReason),
1296}
1297
1298fn validate_existing_draco_primitive(
1299 document: &Value,
1300 primitive: &Value,
1301 buffers: &[Vec<u8>],
1302 mode: u32,
1303) -> Result<()> {
1304 let extension = parse_khr_draco_mesh_compression(document, primitive)?.ok_or_else(|| {
1305 GltfError::InvalidGltf("primitive has no KHR_draco_mesh_compression payload".into())
1306 })?;
1307 let views = document
1308 .get("bufferViews")
1309 .and_then(Value::as_array)
1310 .ok_or_else(|| GltfError::InvalidGltf("missing bufferViews array".into()))?;
1311 let view = views
1312 .get(extension.buffer_view)
1313 .and_then(Value::as_object)
1314 .ok_or_else(|| {
1315 GltfError::InvalidGltf(format!(
1316 "Draco bufferView {} is out of range",
1317 extension.buffer_view
1318 ))
1319 })?;
1320 let data = buffer_view_bytes(view, buffers)?;
1321 let mut decoder_buffer = DecoderBuffer::new(data);
1322 let mut mesh = Mesh::new();
1323 MeshDecoder::new()
1324 .decode(&mut decoder_buffer, &mut mesh)
1325 .map_err(GltfError::DracoDecode)?;
1326
1327 let primitive_attributes = primitive
1328 .get("attributes")
1329 .and_then(Value::as_object)
1330 .ok_or_else(|| GltfError::InvalidGltf("primitive.attributes is not an object".into()))?;
1331 let accessors = document
1332 .get("accessors")
1333 .and_then(Value::as_array)
1334 .ok_or_else(|| GltfError::InvalidGltf("missing accessors array".into()))?;
1335
1336 for (semantic, &unique_id) in &extension.attributes {
1337 let accessor_index = primitive_attributes
1338 .get(semantic)
1339 .ok_or_else(|| {
1340 GltfError::InvalidGltf(format!(
1341 "Draco semantic {semantic} is absent from primitive.attributes"
1342 ))
1343 })
1344 .and_then(|value| json_index(value, "Draco attribute accessor"))?;
1345 let accessor = accessors
1346 .get(accessor_index)
1347 .and_then(Value::as_object)
1348 .ok_or_else(|| {
1349 GltfError::InvalidGltf(format!("accessor {accessor_index} is out of range"))
1350 })?;
1351 let accessor_type = accessor
1352 .get("type")
1353 .and_then(Value::as_str)
1354 .ok_or_else(|| {
1355 GltfError::InvalidGltf(format!("accessor {accessor_index} has invalid type"))
1356 })?;
1357 let component_type = accessor
1358 .get("componentType")
1359 .and_then(Value::as_u64)
1360 .and_then(|value| u32::try_from(value).ok())
1361 .ok_or_else(|| {
1362 GltfError::InvalidGltf(format!(
1363 "accessor {accessor_index} has invalid componentType"
1364 ))
1365 })?;
1366 let normalized = match accessor.get("normalized") {
1367 None => false,
1368 Some(Value::Bool(normalized)) => *normalized,
1369 Some(_) => {
1370 return Err(GltfError::InvalidGltf("normalized is not a boolean".into()));
1371 }
1372 };
1373 let semantic_spec =
1374 validate_semantic_accessor(semantic, accessor_type, component_type, normalized)?;
1375 let attribute = mesh.attribute_by_unique_id(unique_id).ok_or_else(|| {
1376 GltfError::InvalidGltf(format!(
1377 "Draco unique attribute id {unique_id} for {semantic} is absent"
1378 ))
1379 })?;
1380 if attribute.attribute_type() != semantic_spec.attribute_type
1381 || gltf_type_for_num_components(attribute.num_components())? != accessor_type
1382 || component_type_for_data_type(attribute.data_type())? != component_type
1383 || attribute.normalized() != normalized
1384 {
1385 return Err(GltfError::InvalidGltf(format!(
1386 "decoded Draco attribute {semantic} does not match accessor {accessor_index}"
1387 )));
1388 }
1389 let count = required_usize(accessor, "count", "Draco attribute accessor")?;
1390 if attribute.size() != count || count != mesh.num_points() {
1391 return Err(GltfError::InvalidGltf(format!(
1392 "decoded Draco attribute {semantic} count {} does not match accessor count {count}",
1393 attribute.size()
1394 )));
1395 }
1396 }
1397
1398 if let Some(indices) = primitive.get("indices") {
1399 let accessor_index = json_index(indices, "Draco indices accessor")?;
1400 let accessor = accessors
1401 .get(accessor_index)
1402 .and_then(Value::as_object)
1403 .ok_or_else(|| {
1404 GltfError::InvalidGltf(format!("accessor {accessor_index} is out of range"))
1405 })?;
1406 if accessor.get("type").and_then(Value::as_str) != Some("SCALAR")
1407 || accessor
1408 .get("componentType")
1409 .and_then(Value::as_u64)
1410 .is_none_or(|component| !matches!(component, 5121 | 5123 | 5125))
1411 || accessor
1412 .get("normalized")
1413 .is_some_and(|normalized| normalized != &Value::Bool(false))
1414 {
1415 return Err(GltfError::InvalidGltf(
1416 "Draco indices accessor has an invalid contract".into(),
1417 ));
1418 }
1419 let expected_count = if mode == MODE_TRIANGLES as u32 {
1420 mesh.num_faces().checked_mul(3)
1421 } else {
1422 mesh.num_faces().checked_add(2)
1423 }
1424 .ok_or_else(|| GltfError::InvalidGltf("decoded Draco index count overflow".into()))?;
1425 let count = required_usize(accessor, "count", "Draco indices accessor")?;
1426 if count != expected_count {
1427 return Err(GltfError::InvalidGltf(format!(
1428 "Draco indices accessor count {count} does not match decoded count {expected_count}"
1429 )));
1430 }
1431 }
1432 Ok(())
1433}
1434
1435fn plan_for_primitive<F>(
1436 doc: &Value,
1437 prim: &Value,
1438 buffers: &[Vec<u8>],
1439 location: PrimitiveLocation,
1440 decode: &F,
1441 accessor_users: &HashMap<usize, usize>,
1442 options: &GltfCompressionOptions,
1443) -> Result<PlanDecision>
1444where
1445 F: Fn(usize, usize) -> Result<(draco_core::Mesh, Vec<(String, u32)>)>,
1446{
1447 let mesh_idx = location.mesh;
1448 let prim_idx = location.primitive;
1449 let mode = prim
1454 .get("mode")
1455 .map(|value| {
1456 value
1457 .as_u64()
1458 .ok_or_else(|| GltfError::InvalidGltf("primitive.mode is not an integer".into()))
1459 })
1460 .transpose()?
1461 .unwrap_or(MODE_TRIANGLES);
1462 let mode_u32 = u32::try_from(mode)
1463 .map_err(|_| GltfError::InvalidGltf("primitive.mode exceeds u32".into()))?;
1464 if prim
1469 .get("extensions")
1470 .and_then(|extensions| extensions.get(KHR_DRACO))
1471 .is_some()
1472 {
1473 validate_existing_draco_primitive(doc, prim, buffers, mode_u32)?;
1474 return Ok(PlanDecision::Preserve(PreserveReason::AlreadyDraco));
1475 }
1476 if mode != MODE_TRIANGLES && mode != MODE_TRIANGLE_STRIP {
1477 return Ok(PlanDecision::Preserve(PreserveReason::UnsupportedMode {
1478 mode: mode_u32,
1479 }));
1480 }
1481 if mode == MODE_TRIANGLE_STRIP {
1482 return Ok(PlanDecision::Preserve(PreserveReason::UnsupportedLayout {
1483 detail: "TRIANGLE_STRIP compression is not implemented".into(),
1484 }));
1485 }
1486 if let Some(targets) = prim.get("targets") {
1487 let targets = targets
1488 .as_array()
1489 .ok_or_else(|| GltfError::InvalidGltf("primitive.targets is not an array".into()))?;
1490 if !targets.is_empty() {
1491 return Ok(PlanDecision::Preserve(PreserveReason::MorphTargets));
1492 }
1493 }
1494 let indices_accessor = prim
1498 .get("indices")
1499 .map(|value| json_index(value, "indices accessor"))
1500 .transpose()?;
1501
1502 let Some(attributes) = prim.get("attributes").and_then(Value::as_object) else {
1504 return Err(GltfError::InvalidGltf(
1505 "primitive.attributes must be a non-empty object".into(),
1506 ));
1507 };
1508 if attributes.is_empty() || !attributes.contains_key("POSITION") {
1509 return Ok(PlanDecision::Preserve(PreserveReason::UnsupportedLayout {
1510 detail: "primitive has no POSITION attribute".into(),
1511 }));
1512 }
1513 let mut attribute_accessors = Vec::new();
1514 for accessor in attributes.values() {
1515 attribute_accessors.push(json_index(accessor, "attribute accessor")?);
1516 }
1517
1518 let accessors = doc
1519 .get("accessors")
1520 .and_then(Value::as_array)
1521 .ok_or_else(|| GltfError::InvalidGltf("missing accessors array".into()))?;
1522 for &accessor in attribute_accessors.iter().chain(indices_accessor.iter()) {
1523 let value = accessors
1524 .get(accessor)
1525 .and_then(Value::as_object)
1526 .ok_or_else(|| {
1527 GltfError::InvalidGltf(format!("accessor {accessor} is out of range"))
1528 })?;
1529 if value.contains_key("sparse") {
1530 return Ok(PlanDecision::Preserve(PreserveReason::SparseAccessor {
1531 accessor,
1532 }));
1533 }
1534 }
1535
1536 let exclusive = |acc: usize| accessor_users.get(&acc).copied().unwrap_or(0) == 1;
1540 if let Some(accessor) = indices_accessor.filter(|accessor| !exclusive(*accessor)) {
1541 return Ok(PlanDecision::Preserve(PreserveReason::SharedAccessor {
1542 accessor,
1543 }));
1544 }
1545 if let Some(accessor) = attribute_accessors
1546 .iter()
1547 .copied()
1548 .find(|accessor| !exclusive(*accessor))
1549 {
1550 return Ok(PlanDecision::Preserve(PreserveReason::SharedAccessor {
1551 accessor,
1552 }));
1553 }
1554
1555 let (mesh, semantic_to_uid) = match decode(mesh_idx, prim_idx) {
1558 Ok(out) => out,
1559 Err(GltfError::Unsupported(detail)) => {
1560 return Ok(PlanDecision::Preserve(PreserveReason::UnsupportedLayout {
1561 detail,
1562 }))
1563 }
1564 Err(error) => return Err(error),
1565 };
1566 let (draco_bytes, info) = match encode_draco_mesh_with_info(&mesh, options) {
1567 Ok(out) => out,
1568 Err(crate::gltf_writer::GltfWriteError::Unsupported(detail)) => {
1569 return Ok(PlanDecision::Preserve(PreserveReason::UnsupportedLayout {
1570 detail,
1571 }))
1572 }
1573 Err(crate::gltf_writer::GltfWriteError::InvalidMesh(detail)) => {
1574 return Err(GltfError::InvalidGltf(detail))
1575 }
1576 Err(crate::gltf_writer::GltfWriteError::InvalidOptions(detail)) => {
1577 return Err(GltfError::InvalidOptions(detail))
1578 }
1579 Err(crate::gltf_writer::GltfWriteError::ResourceLimit(detail)) => {
1580 return Err(GltfError::ResourceLimitExceeded(detail))
1581 }
1582 Err(crate::gltf_writer::GltfWriteError::DracoEncode(source)) => {
1583 return Err(GltfError::DracoEncode(source))
1584 }
1585 Err(error) => {
1586 return Err(GltfError::InvalidGltf(format!(
1587 "Draco writer failed: {error}"
1588 )))
1589 }
1590 };
1591
1592 let produced: BTreeSet<&str> = semantic_to_uid.iter().map(|(s, _)| s.as_str()).collect();
1595 let original: BTreeSet<&str> = attributes.keys().map(String::as_str).collect();
1596 if produced != original {
1597 return Err(GltfError::InvalidGltf(
1598 "decoded attribute set does not match primitive.attributes".into(),
1599 ));
1600 }
1601 let semantic_to_id = semantic_to_uid;
1602
1603 let num_indices = info
1604 .num_encoded_faces
1605 .checked_mul(3)
1606 .ok_or_else(|| GltfError::ResourceLimitExceeded("index count overflow".into()))?;
1607
1608 Ok(PlanDecision::Compress(CompressPlan {
1609 mesh_idx,
1610 prim_idx,
1611 draco_bytes,
1612 semantic_to_id,
1613 attribute_accessors,
1614 indices_accessor,
1615 num_points: info.num_encoded_points,
1616 num_indices,
1617 }))
1618}
1619
1620fn count_accessor_users(doc: &Value) -> Result<HashMap<usize, usize>> {
1623 let mut users: HashMap<usize, usize> = HashMap::new();
1624 let mut add = |value: &Value, label: &str| -> Result<()> {
1625 let accessor = json_index(value, label)?;
1626 let count = users.entry(accessor).or_default();
1627 *count = count
1628 .checked_add(1)
1629 .ok_or_else(|| GltfError::InvalidGltf("accessor use count overflow".into()))?;
1630 Ok(())
1631 };
1632
1633 if let Some(meshes) = doc.get("meshes") {
1634 let meshes = meshes
1635 .as_array()
1636 .ok_or_else(|| GltfError::InvalidGltf("meshes is not an array".into()))?;
1637 for mesh in meshes {
1638 let primitives = mesh
1639 .get("primitives")
1640 .and_then(Value::as_array)
1641 .ok_or_else(|| GltfError::InvalidGltf("mesh.primitives is not an array".into()))?;
1642 for prim in primitives {
1643 let attrs = prim
1644 .get("attributes")
1645 .and_then(Value::as_object)
1646 .ok_or_else(|| {
1647 GltfError::InvalidGltf("primitive.attributes is not an object".into())
1648 })?;
1649 for accessor in attrs.values() {
1650 add(accessor, "primitive attribute accessor")?;
1651 }
1652 if let Some(accessor) = prim.get("indices") {
1653 add(accessor, "primitive indices accessor")?;
1654 }
1655 if let Some(targets) = prim.get("targets") {
1656 let targets = targets.as_array().ok_or_else(|| {
1657 GltfError::InvalidGltf("primitive.targets is not an array".into())
1658 })?;
1659 for target in targets {
1660 let target = target.as_object().ok_or_else(|| {
1661 GltfError::InvalidGltf("morph target is not an object".into())
1662 })?;
1663 for accessor in target.values() {
1664 add(accessor, "morph target accessor")?;
1665 }
1666 }
1667 }
1668 }
1669 }
1670 }
1671
1672 if let Some(animations) = doc.get("animations") {
1673 for animation in animations
1674 .as_array()
1675 .ok_or_else(|| GltfError::InvalidGltf("animations is not an array".into()))?
1676 {
1677 if let Some(samplers) = animation.get("samplers") {
1678 for sampler in samplers.as_array().ok_or_else(|| {
1679 GltfError::InvalidGltf("animation.samplers is not an array".into())
1680 })? {
1681 let sampler = sampler.as_object().ok_or_else(|| {
1682 GltfError::InvalidGltf("animation sampler is not an object".into())
1683 })?;
1684 for key in ["input", "output"] {
1685 let accessor = sampler.get(key).ok_or_else(|| {
1686 GltfError::InvalidGltf(format!("animation sampler is missing {key}"))
1687 })?;
1688 add(accessor, "animation sampler accessor")?;
1689 }
1690 }
1691 }
1692 }
1693 }
1694
1695 if let Some(skins) = doc.get("skins") {
1696 for skin in skins
1697 .as_array()
1698 .ok_or_else(|| GltfError::InvalidGltf("skins is not an array".into()))?
1699 {
1700 if let Some(accessor) = skin.get("inverseBindMatrices") {
1701 add(accessor, "skin inverseBindMatrices accessor")?;
1702 }
1703 }
1704 }
1705
1706 if let Some(nodes) = doc.get("nodes") {
1707 for node in nodes
1708 .as_array()
1709 .ok_or_else(|| GltfError::InvalidGltf("nodes is not an array".into()))?
1710 {
1711 if let Some(attributes) = node
1712 .get("extensions")
1713 .and_then(|extensions| extensions.get("EXT_mesh_gpu_instancing"))
1714 .and_then(|extension| extension.get("attributes"))
1715 {
1716 let attributes = attributes.as_object().ok_or_else(|| {
1717 GltfError::InvalidGltf(
1718 "EXT_mesh_gpu_instancing.attributes is not an object".into(),
1719 )
1720 })?;
1721 for accessor in attributes.values() {
1722 add(accessor, "EXT_mesh_gpu_instancing accessor")?;
1723 }
1724 }
1725 }
1726 }
1727
1728 Ok(users)
1729}
1730
1731fn apply_accessor_mutations(doc: &mut Value, plans: &[CompressPlan]) -> Result<()> {
1732 let accessors = doc
1733 .get_mut("accessors")
1734 .and_then(Value::as_array_mut)
1735 .ok_or_else(|| GltfError::InvalidGltf("missing accessors array".into()))?;
1736
1737 for plan in plans {
1738 for &acc in &plan.attribute_accessors {
1739 strip_geometry_accessor(accessors, acc, plan.num_points)?;
1740 }
1741 if let Some(indices) = plan.indices_accessor {
1742 strip_geometry_accessor(accessors, indices, plan.num_indices)?;
1743 }
1744 }
1745 Ok(())
1746}
1747
1748fn add_generated_indices(doc: &mut Value, plans: &[CompressPlan]) -> Result<()> {
1753 for plan in plans {
1754 if plan.indices_accessor.is_some() {
1755 continue;
1756 }
1757 let accessors = doc
1758 .get_mut("accessors")
1759 .and_then(Value::as_array_mut)
1760 .ok_or_else(|| GltfError::InvalidGltf("missing accessors array".into()))?;
1761 let new_idx = accessors.len();
1762 accessors.push(serde_json::json!({
1763 "componentType": 5125u64, "count": plan.num_indices as u64,
1765 "type": "SCALAR",
1766 }));
1767 let prim = primitive_mut(doc, plan)?;
1768 prim.insert("indices".into(), Value::from(new_idx as u64));
1769 }
1770 Ok(())
1771}
1772
1773fn primitive_mut<'a>(
1775 doc: &'a mut Value,
1776 plan: &CompressPlan,
1777) -> Result<&'a mut Map<String, Value>> {
1778 doc.get_mut("meshes")
1779 .and_then(Value::as_array_mut)
1780 .and_then(|m| m.get_mut(plan.mesh_idx))
1781 .and_then(|m| m.get_mut("primitives"))
1782 .and_then(Value::as_array_mut)
1783 .and_then(|p| p.get_mut(plan.prim_idx))
1784 .and_then(Value::as_object_mut)
1785 .ok_or_else(|| GltfError::InvalidGltf("primitive vanished during rewrite".into()))
1786}
1787
1788fn strip_geometry_accessor(accessors: &mut [Value], idx: usize, count: usize) -> Result<()> {
1792 let accessor = accessors
1793 .get_mut(idx)
1794 .and_then(Value::as_object_mut)
1795 .ok_or_else(|| GltfError::InvalidGltf(format!("accessor {} out of range", idx)))?;
1796 accessor.remove("bufferView");
1797 accessor.remove("byteOffset");
1798 accessor.insert("count".into(), Value::from(count));
1799 Ok(())
1800}
1801
1802struct Repack {
1803 bin: Vec<u8>,
1804 draco_buffer_views: Vec<usize>,
1806}
1807
1808fn repack_buffers(
1809 doc: &mut Value,
1810 source_buffers: &[Vec<u8>],
1811 plans: &[CompressPlan],
1812) -> Result<Repack> {
1813 let mut referenced = BTreeSet::new();
1817 collect_buffer_view_refs(doc, &mut referenced)?;
1818
1819 let old_views = doc
1822 .as_object_mut()
1823 .ok_or_else(|| GltfError::InvalidGltf("glTF root is not an object".into()))?
1824 .remove("bufferViews");
1825 let mut old_views = match old_views {
1826 Some(Value::Array(views)) => views,
1827 Some(_) => {
1828 return Err(GltfError::InvalidGltf("bufferViews is not an array".into()));
1829 }
1830 None => Vec::new(),
1831 };
1832
1833 let mut bin: Vec<u8> = Vec::new();
1835 let mut new_views: Vec<Value> = Vec::new();
1836 let new_view_count = referenced
1837 .len()
1838 .checked_add(plans.len())
1839 .ok_or_else(|| GltfError::ResourceLimitExceeded("bufferView table size overflow".into()))?;
1840 new_views.try_reserve_exact(new_view_count).map_err(|_| {
1841 GltfError::ResourceLimitExceeded("bufferView table allocation failed".into())
1842 })?;
1843 let mut remap: HashMap<usize, usize> = HashMap::new();
1844 remap.try_reserve(referenced.len()).map_err(|_| {
1845 GltfError::ResourceLimitExceeded("bufferView remap allocation failed".into())
1846 })?;
1847
1848 for &old_idx in &referenced {
1849 let view = old_views
1850 .get_mut(old_idx)
1851 .ok_or_else(|| GltfError::InvalidGltf(format!("buffer view {old_idx} invalid")))?;
1852 let mut new_view = match std::mem::take(view) {
1853 Value::Object(view) => view,
1854 _ => {
1855 return Err(GltfError::InvalidGltf(format!(
1856 "buffer view {old_idx} is not an object"
1857 )));
1858 }
1859 };
1860 let bytes = buffer_view_bytes(&new_view, source_buffers)?;
1861 let byte_length = bytes.len();
1862 align_to_4(&mut bin)?;
1863 let offset = bin.len();
1864 append_bytes(&mut bin, bytes, "buffer view")?;
1865
1866 new_view.insert("buffer".into(), Value::from(0u64));
1867 new_view.insert("byteOffset".into(), Value::from(offset as u64));
1868 new_view.insert("byteLength".into(), Value::from(byte_length as u64));
1869 let new_idx = new_views.len();
1870 new_views.push(Value::Object(new_view));
1871 remap.insert(old_idx, new_idx);
1872 }
1873
1874 remap_buffer_view_refs(doc, &remap)?;
1876
1877 let mut draco_buffer_views = Vec::new();
1880 draco_buffer_views
1881 .try_reserve_exact(plans.len())
1882 .map_err(|_| {
1883 GltfError::ResourceLimitExceeded("Draco bufferView table allocation failed".into())
1884 })?;
1885 for plan in plans {
1886 align_to_4(&mut bin)?;
1887 let offset = bin.len();
1888 append_bytes(&mut bin, &plan.draco_bytes, "Draco bitstream")?;
1889 let new_idx = new_views.len();
1890 new_views.push(serde_json::json!({
1891 "buffer": 0,
1892 "byteOffset": offset as u64,
1893 "byteLength": plan.draco_bytes.len() as u64,
1894 }));
1895 draco_buffer_views.push(new_idx);
1896 }
1897
1898 let root = doc
1899 .as_object_mut()
1900 .ok_or_else(|| GltfError::InvalidGltf("glTF root is not an object".into()))?;
1901 if new_views.is_empty() {
1902 root.remove("bufferViews");
1903 } else {
1904 root.insert("bufferViews".into(), Value::Array(new_views));
1905 }
1906
1907 Ok(Repack {
1908 bin,
1909 draco_buffer_views,
1910 })
1911}
1912
1913fn buffer_view_bytes<'a>(view: &Map<String, Value>, buffers: &'a [Vec<u8>]) -> Result<&'a [u8]> {
1914 let buffer_idx: usize = view
1915 .get("buffer")
1916 .and_then(Value::as_u64)
1917 .ok_or_else(|| GltfError::InvalidGltf("buffer view missing buffer index".into()))?
1918 .try_into()
1919 .map_err(|_| GltfError::InvalidGltf("buffer index cannot fit usize".into()))?;
1920 let offset_u64 = view.get("byteOffset").and_then(Value::as_u64).unwrap_or(0);
1921 let offset = usize::try_from(offset_u64)
1922 .map_err(|_| GltfError::InvalidGltf("buffer view offset cannot fit usize".into()))?;
1923 let length: usize = view
1924 .get("byteLength")
1925 .and_then(Value::as_u64)
1926 .ok_or_else(|| GltfError::InvalidGltf("buffer view missing byteLength".into()))?
1927 .try_into()
1928 .map_err(|_| GltfError::InvalidGltf("buffer view length cannot fit usize".into()))?;
1929 let buffer = buffers
1930 .get(buffer_idx)
1931 .ok_or_else(|| GltfError::InvalidGltf(format!("buffer {} not resolved", buffer_idx)))?;
1932 let end = offset
1933 .checked_add(length)
1934 .filter(|&e| e <= buffer.len())
1935 .ok_or_else(|| GltfError::InvalidGltf("buffer view out of range".into()))?;
1936 Ok(&buffer[offset..end])
1937}
1938
1939fn collect_buffer_view_refs(value: &Value, out: &mut BTreeSet<usize>) -> Result<()> {
1941 match value {
1942 Value::Object(map) => {
1943 for (key, child) in map {
1944 if key == "extras" {
1945 continue;
1946 }
1947 if key == "extensions" {
1948 if let Some(extensions) = child.as_object() {
1949 if let Some(draco) = extensions.get(KHR_DRACO) {
1950 collect_buffer_view_refs(draco, out)?;
1951 }
1952 if let Some(metadata) = extensions.get("EXT_structural_metadata") {
1953 collect_structural_metadata_refs(metadata, out)?;
1954 }
1955 }
1956 continue;
1957 }
1958 if key == "bufferView" {
1959 out.insert(json_index(child, "bufferView")?);
1960 }
1961 collect_buffer_view_refs(child, out)?;
1962 }
1963 }
1964 Value::Array(items) => {
1965 for item in items {
1966 collect_buffer_view_refs(item, out)?;
1967 }
1968 }
1969 _ => {}
1970 }
1971 Ok(())
1972}
1973
1974fn remap_buffer_view_refs(value: &mut Value, remap: &HashMap<usize, usize>) -> Result<()> {
1976 match value {
1977 Value::Object(map) => {
1978 for (key, child) in map.iter_mut() {
1979 if key == "extras" {
1980 continue;
1981 }
1982 if key == "extensions" {
1983 if let Some(extensions) = child.as_object_mut() {
1984 if let Some(draco) = extensions.get_mut(KHR_DRACO) {
1985 remap_buffer_view_refs(draco, remap)?;
1986 }
1987 if let Some(metadata) = extensions.get_mut("EXT_structural_metadata") {
1988 remap_structural_metadata_refs(metadata, remap)?;
1989 }
1990 }
1991 continue;
1992 }
1993 if key == "bufferView" {
1994 let old = json_index(child, "bufferView")?;
1995 let new = remap.get(&old).ok_or_else(|| {
1996 GltfError::InvalidGltf(format!("bufferView {old} has no repack mapping"))
1997 })?;
1998 *child = Value::from(*new as u64);
1999 }
2000 remap_buffer_view_refs(child, remap)?;
2001 }
2002 }
2003 Value::Array(items) => {
2004 for item in items {
2005 remap_buffer_view_refs(item, remap)?;
2006 }
2007 }
2008 _ => {}
2009 }
2010 Ok(())
2011}
2012
2013const STRUCTURAL_METADATA_BUFFER_VIEW_KEYS: &[&str] = &["values", "arrayOffsets", "stringOffsets"];
2014
2015fn structural_metadata_properties(value: &Value) -> Result<Vec<&Map<String, Value>>> {
2016 let Some(tables) = value.get("propertyTables") else {
2017 return Ok(Vec::new());
2018 };
2019 let tables = tables.as_array().ok_or_else(|| {
2020 GltfError::InvalidGltf("EXT_structural_metadata.propertyTables is not an array".into())
2021 })?;
2022 let mut properties = Vec::new();
2023 for table in tables {
2024 let Some(table_properties) = table.get("properties") else {
2025 continue;
2026 };
2027 let table_properties = table_properties.as_object().ok_or_else(|| {
2028 GltfError::InvalidGltf(
2029 "EXT_structural_metadata property table properties is not an object".into(),
2030 )
2031 })?;
2032 for property in table_properties.values() {
2033 properties.push(property.as_object().ok_or_else(|| {
2034 GltfError::InvalidGltf("EXT_structural_metadata property is not an object".into())
2035 })?);
2036 }
2037 }
2038 Ok(properties)
2039}
2040
2041fn collect_structural_metadata_refs(metadata: &Value, out: &mut BTreeSet<usize>) -> Result<()> {
2042 for property in structural_metadata_properties(metadata)? {
2043 for key in STRUCTURAL_METADATA_BUFFER_VIEW_KEYS {
2044 if let Some(value) = property.get(*key) {
2045 out.insert(json_index(value, "EXT_structural_metadata bufferView")?);
2046 }
2047 }
2048 }
2049 Ok(())
2050}
2051
2052fn remap_structural_metadata_refs(
2053 metadata: &mut Value,
2054 remap: &HashMap<usize, usize>,
2055) -> Result<()> {
2056 let Some(tables) = metadata.get_mut("propertyTables") else {
2057 return Ok(());
2058 };
2059 let tables = tables.as_array_mut().ok_or_else(|| {
2060 GltfError::InvalidGltf("EXT_structural_metadata.propertyTables is not an array".into())
2061 })?;
2062 for table in tables {
2063 let Some(properties) = table.get_mut("properties") else {
2064 continue;
2065 };
2066 let properties = properties.as_object_mut().ok_or_else(|| {
2067 GltfError::InvalidGltf(
2068 "EXT_structural_metadata property table properties is not an object".into(),
2069 )
2070 })?;
2071 for property in properties.values_mut() {
2072 let property = property.as_object_mut().ok_or_else(|| {
2073 GltfError::InvalidGltf("EXT_structural_metadata property is not an object".into())
2074 })?;
2075 for key in STRUCTURAL_METADATA_BUFFER_VIEW_KEYS {
2076 if let Some(value) = property.get_mut(*key) {
2077 let old = json_index(value, "EXT_structural_metadata bufferView")?;
2078 let new = remap.get(&old).ok_or_else(|| {
2079 GltfError::InvalidGltf(format!(
2080 "EXT_structural_metadata bufferView {old} has no repack mapping"
2081 ))
2082 })?;
2083 *value = Value::from(*new as u64);
2084 }
2085 }
2086 }
2087 }
2088 Ok(())
2089}
2090
2091fn known_non_binary_extension(name: &str) -> bool {
2092 name == "KHR_texture_transform"
2093 || name == "EXT_mesh_features"
2094 || name == "EXT_mesh_gpu_instancing"
2095 || name == "KHR_lights_punctual"
2096 || name == "KHR_animation_pointer"
2097 || matches!(
2098 name,
2099 "KHR_materials_anisotropy"
2100 | "KHR_materials_clearcoat"
2101 | "KHR_materials_diffuse_transmission"
2102 | "KHR_materials_dispersion"
2103 | "KHR_materials_emissive_strength"
2104 | "KHR_materials_ior"
2105 | "KHR_materials_iridescence"
2106 | "KHR_materials_pbrSpecularGlossiness"
2107 | "KHR_materials_sheen"
2108 | "KHR_materials_specular"
2109 | "KHR_materials_transmission"
2110 | "KHR_materials_unlit"
2111 | "KHR_materials_variants"
2112 | "KHR_materials_volume"
2113 | "KHR_texture_basisu"
2114 | "EXT_texture_webp"
2115 | "EXT_texture_avif"
2116 | "MSFT_lod"
2117 )
2118}
2119
2120fn reject_opaque_binary_references(document: &Value) -> Result<()> {
2121 fn scan(value: &Value, path: &str) -> Result<()> {
2122 match value {
2123 Value::Object(object) => {
2124 for (key, child) in object {
2125 let normalized: String = key
2126 .chars()
2127 .filter(|character| character.is_ascii_alphanumeric())
2128 .flat_map(char::to_lowercase)
2129 .collect();
2130 let looks_binary = normalized.contains("buffer")
2131 || normalized.contains("offset")
2132 || normalized.contains("stride")
2133 || (normalized.contains("byte") && normalized.contains("length"));
2134 if looks_binary {
2135 return Err(GltfError::OpaqueBinaryReference(format!("{path}.{key}")));
2136 }
2137 scan(child, &format!("{path}.{key}"))?;
2138 }
2139 }
2140 Value::Array(values) => {
2141 for (index, child) in values.iter().enumerate() {
2142 scan(child, &format!("{path}[{index}]"))?;
2143 }
2144 }
2145 _ => {}
2146 }
2147 Ok(())
2148 }
2149
2150 fn walk(value: &Value, path: &str) -> Result<()> {
2151 match value {
2152 Value::Object(object) => {
2153 for (key, child) in object {
2154 if key == "extras" {
2155 continue;
2156 }
2157 if key == "extensions" {
2158 let extensions = child.as_object().ok_or_else(|| {
2159 GltfError::InvalidGltf(format!("{path}.extensions is not an object"))
2160 })?;
2161 for (name, extension) in extensions {
2162 let extension_path = format!("{path}.extensions.{name}");
2163 if name == KHR_DRACO
2164 || name == "EXT_structural_metadata"
2165 || known_non_binary_extension(name)
2166 {
2167 walk(extension, &extension_path)?;
2172 } else {
2173 scan(extension, &extension_path)?;
2174 }
2175 }
2176 continue;
2177 }
2178 walk(child, &format!("{path}.{key}"))?;
2179 }
2180 }
2181 Value::Array(values) => {
2182 for (index, child) in values.iter().enumerate() {
2183 walk(child, &format!("{path}[{index}]"))?;
2184 }
2185 }
2186 _ => {}
2187 }
2188 Ok(())
2189 }
2190
2191 walk(document, "$")
2192}
2193
2194fn set_primitive_draco_extension(
2195 doc: &mut Value,
2196 plan: &CompressPlan,
2197 draco_buffer_view: usize,
2198) -> Result<()> {
2199 let prim = primitive_mut(doc, plan)?;
2200
2201 let mut attributes = Map::new();
2202 for (semantic, id) in &plan.semantic_to_id {
2203 attributes.insert(semantic.clone(), Value::from(*id as u64));
2204 }
2205 let draco = serde_json::json!({
2206 "bufferView": draco_buffer_view as u64,
2207 "attributes": Value::Object(attributes),
2208 });
2209
2210 let extensions = prim
2211 .entry("extensions")
2212 .or_insert_with(|| Value::Object(Map::new()));
2213 if !extensions.is_object() {
2214 return Err(GltfError::InvalidGltf(
2215 "primitive.extensions is not an object".into(),
2216 ));
2217 }
2218 let extensions = extensions
2219 .as_object_mut()
2220 .ok_or_else(|| GltfError::InvalidGltf("primitive.extensions is not an object".into()))?;
2221 extensions.insert(KHR_DRACO.into(), draco);
2222 Ok(())
2223}
2224
2225fn ensure_extension_listed(doc: &mut Value, key: &str) -> Result<()> {
2228 let root = doc
2229 .as_object_mut()
2230 .ok_or_else(|| GltfError::InvalidGltf("glTF root is not an object".into()))?;
2231 let list = root.entry(key).or_insert_with(|| Value::Array(Vec::new()));
2232 let arr = list
2233 .as_array_mut()
2234 .ok_or_else(|| GltfError::InvalidGltf(format!("{key} is not an array")))?;
2235 if !arr.iter().any(|v| v.as_str() == Some(KHR_DRACO)) {
2236 arr.push(Value::from(KHR_DRACO));
2237 }
2238 Ok(())
2239}
2240
2241fn set_single_buffer(doc: &mut Value, bin_len: usize) -> Result<()> {
2245 let root = doc
2246 .as_object_mut()
2247 .ok_or_else(|| GltfError::InvalidGltf("glTF root is not an object".into()))?;
2248 if bin_len == 0 {
2249 root.remove("buffers");
2250 return Ok(());
2251 }
2252 let mut buffer = Map::new();
2253 let bin_len = u64::try_from(bin_len)
2254 .map_err(|_| GltfError::ResourceLimitExceeded("buffer exceeds u64".into()))?;
2255 buffer.insert("byteLength".into(), Value::from(bin_len));
2256 root.insert("buffers".into(), Value::Array(vec![Value::Object(buffer)]));
2257 Ok(())
2258}
2259
2260fn align_to_4(buf: &mut Vec<u8>) -> Result<()> {
2261 let padding = (4 - buf.len() % 4) % 4;
2262 let new_len = buf
2263 .len()
2264 .checked_add(padding)
2265 .ok_or_else(|| GltfError::ResourceLimitExceeded("alignment size overflow".into()))?;
2266 buf.try_reserve(padding)
2267 .map_err(|_| GltfError::ResourceLimitExceeded("alignment allocation failed".into()))?;
2268 buf.resize(new_len, 0);
2269 Ok(())
2270}
2271
2272fn append_bytes(buf: &mut Vec<u8>, bytes: &[u8], label: &str) -> Result<()> {
2273 buf.len()
2274 .checked_add(bytes.len())
2275 .ok_or_else(|| GltfError::ResourceLimitExceeded(format!("{label} size overflow")))?;
2276 buf.try_reserve(bytes.len())
2277 .map_err(|_| GltfError::ResourceLimitExceeded(format!("{label} allocation failed")))?;
2278 buf.extend_from_slice(bytes);
2279 Ok(())
2280}