1use crate::{Error, Import, Result};
2use draco_core::{
3 draco_types::DataType,
4 encoder_buffer::EncoderBuffer,
5 encoder_options::EncoderOptions,
6 mesh_encoder::{EncodedMeshInfo, MeshEncoder},
7};
8
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
11pub enum CompressionMode {
12 Fallback,
15 #[default]
18 DracoOnly,
19}
20
21fn add_extension_name(root: &mut crate::JsonValue, field: &str) -> Result<()> {
22 if root.get(field).is_none() {
23 root[field] = crate::JsonValue::Array(Vec::new());
24 }
25 let list = root[field]
26 .as_array_mut()
27 .ok_or_else(|| Error::Validation(vec![format!("{field} is not an array")]))?;
28 if !list
29 .iter()
30 .any(|value| value.as_str() == Some(crate::KHR_DRACO_MESH_COMPRESSION))
31 {
32 list.push(crate::JsonValue::from(crate::KHR_DRACO_MESH_COMPRESSION));
33 }
34 Ok(())
35}
36
37fn detach_draco_only_accessors(
38 root: &mut crate::JsonValue,
39 mesh_index: usize,
40 primitive_index: usize,
41 mapping: &[(String, u32)],
42 layout: &DracoGeometryLayout,
43) -> Result<()> {
44 for (semantic, unique_id) in mapping {
45 let source = root["meshes"][mesh_index]["primitives"][primitive_index]["attributes"]
46 [semantic.as_str()]
47 .as_u64()
48 .and_then(|value| usize::try_from(value).ok())
49 .ok_or_else(|| Error::Extension(format!("Draco attribute {semantic} has no accessor")))?;
50 let accessor = clone_accessor(root, source)?;
51 root["meshes"][mesh_index]["primitives"][primitive_index]["attributes"]
52 [semantic.as_str()] = crate::JsonValue::from(accessor as u64);
53 let attribute = layout
54 .attributes
55 .iter()
56 .find(|attribute| attribute.unique_id == *unique_id)
57 .ok_or_else(|| {
58 Error::Extension(format!("encoded Draco attribute {unique_id} is missing"))
59 })?;
60 set_draco_accessor_layout(
61 root,
62 accessor,
63 layout.points,
64 attribute.components,
65 attribute.data_type,
66 attribute.position_bounds.as_ref(),
67 )?;
68 }
69
70 let source = root["meshes"][mesh_index]["primitives"][primitive_index]
71 .get("indices")
72 .and_then(crate::JsonValue::as_u64)
73 .and_then(|value| usize::try_from(value).ok());
74 let accessor = match source {
75 Some(source) => clone_accessor(root, source)?,
76 None => {
77 let accessors = root["accessors"]
78 .as_array_mut()
79 .ok_or_else(|| Error::Validation(vec!["accessors is not an array".into()]))?;
80 let index = accessors.len();
81 accessors.push(crate::JsonValue::Object(Vec::new()));
82 index
83 }
84 };
85 root["meshes"][mesh_index]["primitives"][primitive_index]["indices"] =
86 crate::JsonValue::from(accessor as u64);
87 set_draco_accessor_layout(root, accessor, layout.faces * 3, 1, DataType::Uint32, None)?;
88 Ok(())
89}
90
91#[derive(Clone)]
92struct DracoAttributeLayout {
93 unique_id: u32,
94 components: u8,
95 data_type: DataType,
96 position_bounds: Option<(Vec<f64>, Vec<f64>)>,
97}
98
99struct DracoGeometryLayout {
100 points: usize,
101 faces: usize,
102 attributes: Vec<DracoAttributeLayout>,
103}
104
105impl DracoGeometryLayout {
106 fn from_encoded_info(info: &EncodedMeshInfo, mapping: &[(String, u32)]) -> Result<Self> {
107 let attributes = mapping
108 .iter()
109 .map(|(semantic, unique_id)| {
110 let attribute = info
111 .attributes
112 .iter()
113 .find(|attribute| attribute.unique_id == *unique_id)
114 .ok_or_else(|| {
115 Error::Extension(format!("encoded Draco attribute {unique_id} is missing"))
116 })?;
117 let position_bounds = if semantic == "POSITION" {
118 Some((
119 attribute.position_min.clone().ok_or_else(|| {
120 Error::Extension("encoded POSITION min bounds are missing".into())
121 })?,
122 attribute.position_max.clone().ok_or_else(|| {
123 Error::Extension("encoded POSITION max bounds are missing".into())
124 })?,
125 ))
126 } else {
127 None
128 };
129 Ok(DracoAttributeLayout {
130 unique_id: *unique_id,
131 components: attribute.num_components,
132 data_type: attribute.data_type,
133 position_bounds,
134 })
135 })
136 .collect::<Result<Vec<_>>>()?;
137 Ok(Self {
138 points: info.num_encoded_points,
139 faces: info.num_encoded_faces,
140 attributes,
141 })
142 }
143}
144
145fn clone_accessor(root: &mut crate::JsonValue, source: usize) -> Result<usize> {
146 let accessors = root["accessors"]
147 .as_array_mut()
148 .ok_or_else(|| Error::Validation(vec!["accessors is not an array".into()]))?;
149 let source = accessors
150 .get(source)
151 .cloned()
152 .ok_or_else(|| Error::Extension("Draco accessor out of range".into()))?;
153 let index = accessors.len();
154 accessors.push(source);
155 Ok(index)
156}
157
158fn set_draco_accessor_layout(
159 root: &mut crate::JsonValue,
160 index: usize,
161 count: usize,
162 components: u8,
163 data_type: DataType,
164 position_bounds: Option<&(Vec<f64>, Vec<f64>)>,
165) -> Result<()> {
166 let component_type = match data_type {
167 DataType::Int8 => 5120,
168 DataType::Uint8 => 5121,
169 DataType::Int16 => 5122,
170 DataType::Uint16 => 5123,
171 DataType::Uint32 => 5125,
172 DataType::Float32 => 5126,
173 _ => {
174 return Err(Error::Extension(format!(
175 "Draco attribute data type {data_type:?} cannot be represented by glTF 2.0"
176 )))
177 }
178 };
179 let accessor_type = match components {
180 1 => "SCALAR",
181 2 => "VEC2",
182 3 => "VEC3",
183 4 => "VEC4",
184 _ => {
185 return Err(Error::Extension(format!(
186 "Draco attribute component count {components} cannot be represented by glTF"
187 )))
188 }
189 };
190 let accessor = root["accessors"]
191 .as_array_mut()
192 .and_then(|values| values.get_mut(index))
193 .ok_or_else(|| Error::Extension("Draco accessor out of range".into()))?;
194 accessor["count"] = crate::JsonValue::from(count);
195 accessor["componentType"] = crate::JsonValue::from(component_type as u64);
196 accessor["type"] = crate::JsonValue::from(accessor_type);
197 if let Some(object) = accessor.as_object_mut() {
198 object.retain(|(name, _)| {
199 !matches!(
200 name.as_str(),
201 "bufferView" | "byteOffset" | "sparse" | "min" | "max"
202 )
203 });
204 }
205 if let Some((min, max)) = position_bounds {
206 accessor["min"] = crate::JsonValue::Array(
207 min.iter()
208 .map(|value| crate::JsonValue::Number(crate::writer::finite_float_lexeme(*value)))
209 .collect(),
210 );
211 accessor["max"] = crate::JsonValue::Array(
212 max.iter()
213 .map(|value| crate::JsonValue::Number(crate::writer::finite_float_lexeme(*value)))
214 .collect(),
215 );
216 }
217 Ok(())
218}
219
220fn compact_draco_only_resources(
221 document: &mut crate::Document,
222 buffers: &mut Vec<Vec<u8>>,
223 extensions: &crate::ExtensionRegistry,
224 max_output_bytes: Option<usize>,
225) -> Result<()> {
226 let used_accessors = collect_used_accessors(document.as_value(), extensions, document)?;
227 let accessor_map = {
228 let root = document.as_value_mut();
229 let accessor_map = prune_accessors(root, &used_accessors)?;
230 remap_accessor_references(root, &accessor_map)?;
231 accessor_map
232 };
233 let view_identity = (0..document.buffer_views().len())
234 .map(Some)
235 .collect::<Vec<_>>();
236 extensions.remap_binary_references(document, &accessor_map, &view_identity)?;
237
238 let used_views = collect_used_views(document.as_value(), extensions, document)?;
239 let old_views = document.as_value_mut()["bufferViews"]
240 .as_array_mut()
241 .ok_or_else(|| Error::Validation(vec!["bufferViews is not an array".into()]))
242 .map(std::mem::take)?;
243 if old_views.len() != used_views.len() {
244 return Err(Error::Validation(vec![
245 "bufferViews changed while compacting Draco resources".into(),
246 ]));
247 }
248 #[derive(Clone, Copy)]
249 struct Range {
250 buffer: usize,
251 start: usize,
252 end: usize,
253 output_offset: usize,
254 }
255
256 let mut ranges = Vec::new();
257 for (index, view) in old_views.iter().enumerate() {
258 if !used_views[index] {
259 continue;
260 }
261 let buffer_index = view
262 .get("buffer")
263 .and_then(crate::JsonValue::as_u64)
264 .and_then(|value| usize::try_from(value).ok())
265 .ok_or_else(|| Error::Validation(vec!["bufferView buffer is invalid".into()]))?;
266 let buffer = buffers
267 .get(buffer_index)
268 .ok_or_else(|| Error::Validation(vec!["bufferView buffer is invalid".into()]))?;
269 let start = view
270 .get("byteOffset")
271 .and_then(crate::JsonValue::as_u64)
272 .unwrap_or(0);
273 let start = usize::try_from(start)
274 .map_err(|_| Error::ResourceLimit("bufferView offset exceeds this platform".into()))?;
275 let length = view
276 .get("byteLength")
277 .and_then(crate::JsonValue::as_u64)
278 .and_then(|value| usize::try_from(value).ok())
279 .ok_or_else(|| Error::Validation(vec!["bufferView byteLength is invalid".into()]))?;
280 let end = start
281 .checked_add(length)
282 .filter(|end| *end <= buffer.len())
283 .ok_or_else(|| Error::Validation(vec!["bufferView range is invalid".into()]))?;
284 ranges.push((
285 index,
286 Range {
287 buffer: buffer_index,
288 start,
289 end,
290 output_offset: 0,
291 },
292 ));
293 }
294 ranges.sort_unstable_by_key(|(_, range)| (range.buffer, range.start, range.end));
295 let mut blocks = Vec::<Range>::new();
296 let mut block_for_view = vec![usize::MAX; old_views.len()];
297 for (view, range) in ranges {
298 let last_index = blocks.len().checked_sub(1);
299 let coalesces = last_index.is_some_and(|index| {
300 let last = blocks[index];
301 last.buffer == range.buffer && range.start <= last.end
302 });
303 if coalesces {
304 let index = last_index.expect("coalescing has a preceding range");
305 blocks[index].end = blocks[index].end.max(range.end);
306 block_for_view[view] = index;
307 } else {
308 block_for_view[view] = blocks.len();
309 blocks.push(range);
310 }
311 }
312 let mut compacted = Vec::new();
313 for block in &mut blocks {
314 let padding = (4 - compacted.len() % 4) % 4;
315 reserve_output(&mut compacted, padding, max_output_bytes)?;
316 compacted.resize(compacted.len() + padding, 0);
317 block.output_offset = compacted.len();
318 let length = block.end - block.start;
319 reserve_output(&mut compacted, length, max_output_bytes)?;
320 compacted.extend_from_slice(&buffers[block.buffer][block.start..block.end]);
321 }
322 let mut view_map = vec![None; old_views.len()];
323 let mut new_views = Vec::new();
324 for (index, mut view) in old_views.into_iter().enumerate() {
325 if !used_views[index] {
326 continue;
327 }
328 let start = view
329 .get("byteOffset")
330 .and_then(crate::JsonValue::as_u64)
331 .map(usize::try_from)
332 .transpose()
333 .map_err(|_| Error::ResourceLimit("bufferView offset exceeds this platform".into()))?
334 .unwrap_or(0);
335 let block = blocks
336 .get(block_for_view[index])
337 .ok_or_else(|| Error::Validation(vec!["bufferView range was not planned".into()]))?;
338 view["buffer"] = crate::JsonValue::from(0usize);
339 view["byteOffset"] = crate::JsonValue::from(block.output_offset + start - block.start);
340 view_map[index] = Some(new_views.len());
341 new_views.push(view);
342 }
343 {
344 let root = document.as_value_mut();
345 root["bufferViews"] = crate::JsonValue::Array(new_views);
346 remap_buffer_view_references(root, &view_map)?;
347 }
348 let accessor_identity = (0..document.accessors().len())
349 .map(Some)
350 .collect::<Vec<_>>();
351 extensions.remap_binary_references(document, &accessor_identity, &view_map)?;
352 document.as_value_mut()["buffers"] =
353 crate::JsonValue::Array(vec![crate::JsonValue::object([(
354 "byteLength",
355 crate::JsonValue::from(compacted.len()),
356 )])]);
357 *buffers = vec![compacted];
358 Ok(())
359}
360
361fn reserve_output(
362 output: &mut Vec<u8>,
363 additional: usize,
364 max_output_bytes: Option<usize>,
365) -> Result<()> {
366 let total = output
367 .len()
368 .checked_add(additional)
369 .ok_or_else(|| Error::ResourceLimit("compressed output size overflow".into()))?;
370 if max_output_bytes.is_some_and(|limit| total > limit) {
371 return Err(Error::ResourceLimit(format!(
372 "compressed output size {total} exceeds configured limit"
373 )));
374 }
375 output
376 .try_reserve(additional)
377 .map_err(|_| Error::ResourceLimit("unable to reserve compressed output".into()))?;
378 Ok(())
379}
380
381fn collect_used_accessors(
382 root: &crate::JsonValue,
383 extensions: &crate::ExtensionRegistry,
384 document: &crate::Document,
385) -> Result<Vec<bool>> {
386 let mut used = vec![
387 false;
388 root["accessors"]
389 .as_array()
390 .map_or(0, <[crate::JsonValue]>::len)
391 ];
392 visit_core_accessor_refs(root, &mut used)?;
393 let mut buffer_views = vec![
394 false;
395 root["bufferViews"]
396 .as_array()
397 .map_or(0, <[crate::JsonValue]>::len)
398 ];
399 extensions.collect_binary_references(document, &mut used, &mut buffer_views)?;
400 Ok(used)
401}
402
403fn prune_accessors(root: &mut crate::JsonValue, used: &[bool]) -> Result<Vec<Option<usize>>> {
404 let accessors = root["accessors"]
405 .as_array_mut()
406 .ok_or_else(|| Error::Validation(vec!["accessors is not an array".into()]))?;
407 if accessors.len() != used.len() {
408 return Err(Error::Validation(vec![
409 "accessor count changed while compacting".into(),
410 ]));
411 }
412 let old = std::mem::take(accessors);
413 let mut map = vec![None; old.len()];
414 for (index, accessor) in old.into_iter().enumerate() {
415 if used[index] {
416 map[index] = Some(accessors.len());
417 accessors.push(accessor);
418 }
419 }
420 Ok(map)
421}
422
423fn remap_accessor_references(root: &mut crate::JsonValue, map: &[Option<usize>]) -> Result<()> {
424 remap_core_accessor_refs(root, map)
425}
426
427fn collect_used_views(
428 root: &crate::JsonValue,
429 extensions: &crate::ExtensionRegistry,
430 document: &crate::Document,
431) -> Result<Vec<bool>> {
432 let mut used = vec![
433 false;
434 root["bufferViews"]
435 .as_array()
436 .map_or(0, <[crate::JsonValue]>::len)
437 ];
438 visit_core_buffer_view_refs(root, &mut used)?;
439 let mut accessors = vec![
440 false;
441 root["accessors"]
442 .as_array()
443 .map_or(0, <[crate::JsonValue]>::len)
444 ];
445 extensions.collect_binary_references(document, &mut accessors, &mut used)?;
446 Ok(used)
447}
448
449fn remap_buffer_view_references(root: &mut crate::JsonValue, map: &[Option<usize>]) -> Result<()> {
450 remap_core_buffer_view_refs(root, map)
451}
452
453fn visit_core_accessor_refs(root: &crate::JsonValue, used: &mut [bool]) -> Result<()> {
454 for mesh in root
455 .get("meshes")
456 .and_then(crate::JsonValue::as_array)
457 .unwrap_or(&[])
458 {
459 for primitive in mesh
460 .get("primitives")
461 .and_then(crate::JsonValue::as_array)
462 .unwrap_or(&[])
463 {
464 for (_, value) in primitive
465 .get("attributes")
466 .and_then(crate::JsonValue::as_object)
467 .unwrap_or(&[])
468 {
469 mark_used(value, used, "accessor")?;
470 }
471 if let Some(value) = primitive.get("indices") {
472 mark_used(value, used, "accessor")?;
473 }
474 for target in primitive
475 .get("targets")
476 .and_then(crate::JsonValue::as_array)
477 .unwrap_or(&[])
478 {
479 for (_, value) in target.as_object().unwrap_or(&[]) {
480 mark_used(value, used, "accessor")?;
481 }
482 }
483 }
484 }
485 for skin in root
486 .get("skins")
487 .and_then(crate::JsonValue::as_array)
488 .unwrap_or(&[])
489 {
490 if let Some(value) = skin.get("inverseBindMatrices") {
491 mark_used(value, used, "accessor")?;
492 }
493 }
494 for animation in root
495 .get("animations")
496 .and_then(crate::JsonValue::as_array)
497 .unwrap_or(&[])
498 {
499 for sampler in animation
500 .get("samplers")
501 .and_then(crate::JsonValue::as_array)
502 .unwrap_or(&[])
503 {
504 mark_used(&sampler["input"], used, "accessor")?;
505 mark_used(&sampler["output"], used, "accessor")?;
506 }
507 }
508 Ok(())
509}
510
511fn visit_core_buffer_view_refs(root: &crate::JsonValue, used: &mut [bool]) -> Result<()> {
512 for accessor in root
513 .get("accessors")
514 .and_then(crate::JsonValue::as_array)
515 .unwrap_or(&[])
516 {
517 if let Some(value) = accessor.get("bufferView") {
518 mark_used(value, used, "bufferView")?;
519 }
520 if let Some(sparse) = accessor.get("sparse") {
521 mark_used(&sparse["indices"]["bufferView"], used, "bufferView")?;
522 mark_used(&sparse["values"]["bufferView"], used, "bufferView")?;
523 }
524 }
525 for name in ["images", "files"] {
526 for object in root
527 .get(name)
528 .and_then(crate::JsonValue::as_array)
529 .unwrap_or(&[])
530 {
531 if let Some(value) = object.get("bufferView") {
532 mark_used(value, used, "bufferView")?;
533 }
534 }
535 }
536 Ok(())
537}
538
539fn remap_core_accessor_refs(root: &mut crate::JsonValue, map: &[Option<usize>]) -> Result<()> {
540 if let Some(meshes) = root
541 .get_mut("meshes")
542 .and_then(crate::JsonValue::as_array_mut)
543 {
544 for mesh in meshes {
545 if let Some(primitives) = mesh
546 .get_mut("primitives")
547 .and_then(crate::JsonValue::as_array_mut)
548 {
549 for primitive in primitives {
550 if let Some(values) = primitive
551 .get_mut("attributes")
552 .and_then(crate::JsonValue::as_object_mut)
553 {
554 for (_, value) in values {
555 remap_index(value, map, "accessor")?;
556 }
557 }
558 if let Some(value) = primitive.get_mut("indices") {
559 remap_index(value, map, "accessor")?;
560 }
561 if let Some(targets) = primitive
562 .get_mut("targets")
563 .and_then(crate::JsonValue::as_array_mut)
564 {
565 for target in targets {
566 if let Some(values) = target.as_object_mut() {
567 for (_, value) in values {
568 remap_index(value, map, "accessor")?;
569 }
570 }
571 }
572 }
573 }
574 }
575 }
576 }
577 if let Some(skins) = root
578 .get_mut("skins")
579 .and_then(crate::JsonValue::as_array_mut)
580 {
581 for skin in skins {
582 if let Some(value) = skin.get_mut("inverseBindMatrices") {
583 remap_index(value, map, "accessor")?;
584 }
585 }
586 }
587 if let Some(animations) = root
588 .get_mut("animations")
589 .and_then(crate::JsonValue::as_array_mut)
590 {
591 for animation in animations {
592 if let Some(samplers) = animation
593 .get_mut("samplers")
594 .and_then(crate::JsonValue::as_array_mut)
595 {
596 for sampler in samplers {
597 remap_index(&mut sampler["input"], map, "accessor")?;
598 remap_index(&mut sampler["output"], map, "accessor")?;
599 }
600 }
601 }
602 }
603 Ok(())
604}
605
606fn remap_core_buffer_view_refs(root: &mut crate::JsonValue, map: &[Option<usize>]) -> Result<()> {
607 if let Some(accessors) = root
608 .get_mut("accessors")
609 .and_then(crate::JsonValue::as_array_mut)
610 {
611 for accessor in accessors {
612 if let Some(value) = accessor.get_mut("bufferView") {
613 remap_index(value, map, "bufferView")?;
614 }
615 if let Some(sparse) = accessor.get_mut("sparse") {
616 remap_index(&mut sparse["indices"]["bufferView"], map, "bufferView")?;
617 remap_index(&mut sparse["values"]["bufferView"], map, "bufferView")?;
618 }
619 }
620 }
621 for name in ["images", "files"] {
622 if let Some(values) = root.get_mut(name).and_then(crate::JsonValue::as_array_mut) {
623 for value in values {
624 if let Some(view) = value.get_mut("bufferView") {
625 remap_index(view, map, "bufferView")?;
626 }
627 }
628 }
629 }
630 Ok(())
631}
632
633fn mark_used(value: &crate::JsonValue, used: &mut [bool], kind: &str) -> Result<()> {
634 let index = value
635 .as_u64()
636 .and_then(|value| usize::try_from(value).ok())
637 .filter(|index| *index < used.len())
638 .ok_or_else(|| Error::Validation(vec![format!("{kind} reference is invalid")]))?;
639 used[index] = true;
640 Ok(())
641}
642
643fn remap_index(value: &mut crate::JsonValue, map: &[Option<usize>], kind: &str) -> Result<()> {
644 let old = value
645 .as_u64()
646 .and_then(|value| usize::try_from(value).ok())
647 .ok_or_else(|| Error::Validation(vec![format!("{kind} reference is invalid")]))?;
648 let new = map
649 .get(old)
650 .and_then(|value| *value)
651 .ok_or_else(|| Error::Validation(vec![format!("{kind} reference was removed")]))?;
652 *value = crate::JsonValue::from(new);
653 Ok(())
654}
655
656#[derive(Clone, Copy, Debug)]
657pub struct CompressionOptions {
673 pub encoding_speed: u8,
675 pub decoding_speed: u8,
677 pub mode: CompressionMode,
679 pub max_output_bytes: Option<usize>,
682}
683impl Default for CompressionOptions {
684 fn default() -> Self {
685 Self {
686 encoding_speed: 5,
687 decoding_speed: 5,
688 mode: CompressionMode::DracoOnly,
689 max_output_bytes: None,
690 }
691 }
692}
693#[derive(Clone, Debug, Default)]
694pub struct CompressionReport {
696 pub mode: CompressionMode,
698 pub compressed_primitives: usize,
700 pub encoded_bytes: usize,
702 pub source_bytes: usize,
704 pub output_bytes: usize,
706 pub reclaimed_bytes: usize,
709}
710
711impl Import {
712 pub(crate) fn encode_draco_geometry(
714 &self,
715 mesh: draco_core::Mesh,
716 options: CompressionOptions,
717 ) -> Result<(Vec<u8>, EncodedMeshInfo)> {
718 let mut encoder = MeshEncoder::new();
719 encoder.set_mesh(mesh);
720 let mut settings = EncoderOptions::new();
721 settings.set_global_int("encoding_speed", options.encoding_speed as i32);
722 settings.set_global_int("decoding_speed", options.decoding_speed as i32);
723 let mut output = EncoderBuffer::new();
724 encoder
725 .encode(&settings, &mut output)
726 .map_err(|error| Error::Extension(error.to_string()))?;
727 let info = encoder
728 .encoded_mesh_info()
729 .cloned()
730 .ok_or_else(|| Error::Extension("Draco encoder did not return mesh info".into()))?;
731 Ok((output.data().to_vec(), info))
732 }
733
734 pub fn compress_primitive(
741 &mut self,
742 mesh: crate::MeshIndex,
743 primitive: usize,
744 options: CompressionOptions,
745 ) -> Result<CompressionReport> {
746 let mut candidate = self.clone();
747 let report = candidate.compress_primitive_inner(mesh, primitive, options)?;
748 *self = candidate;
749 Ok(report)
750 }
751
752 fn compress_primitive_inner(
753 &mut self,
754 mesh: crate::MeshIndex,
755 primitive: usize,
756 options: CompressionOptions,
757 ) -> Result<CompressionReport> {
758 let source_bytes = self
759 .resources
760 .buffers
761 .iter()
762 .try_fold(0usize, |total, buffer| {
763 total
764 .checked_add(buffer.len())
765 .ok_or_else(|| Error::ResourceLimit("total source buffer size overflow".into()))
766 })?;
767 let reference = self
768 .document
769 .primitive(mesh, primitive)
770 .ok_or_else(|| Error::Extension("primitive out of range".into()))?;
771 self.ensure_transform_safe(reference)?;
772 if options.mode == CompressionMode::DracoOnly {
773 self.ensure_document_binary_transform_safe()?;
774 }
775 if reference.mode() != 4 {
776 return Err(Error::Extension(
777 "KHR_draco_mesh_compression encoding currently supports only TRIANGLES (mode 4)"
778 .into(),
779 ));
780 }
781 let (geometry, mapping) = self.decode_geometry_primitive(reference)?;
782 let (bytes, encoded_info) = self.encode_draco_geometry(geometry, options)?;
783 let layout = DracoGeometryLayout::from_encoded_info(&encoded_info, &mapping)?;
784 let buffer = self.resources.buffers.len();
785 let view;
786 {
787 let root = self.document.as_value_mut();
788 let buffers = root["buffers"]
789 .as_array_mut()
790 .ok_or_else(|| Error::Extension("buffers is not an array".into()))?;
791 buffers.push(crate::JsonValue::object([(
792 "byteLength",
793 crate::JsonValue::from(bytes.len()),
794 )]));
795 let views = root["bufferViews"]
796 .as_array_mut()
797 .ok_or_else(|| Error::Extension("bufferViews is not an array".into()))?;
798 view = views.len();
799 views.push(crate::JsonValue::object([
800 ("buffer", crate::JsonValue::from(buffer)),
801 ("byteLength", crate::JsonValue::from(bytes.len())),
802 ]));
803 let attributes = crate::JsonValue::Object(
804 mapping
805 .iter()
806 .map(|(name, id)| (name.clone(), crate::JsonValue::from(*id as u64)))
807 .collect(),
808 );
809 root["meshes"][mesh.0]["primitives"][primitive]["extensions"]
810 [crate::KHR_DRACO_MESH_COMPRESSION] = crate::JsonValue::object([
811 ("bufferView", crate::JsonValue::from(view)),
812 ("attributes", attributes),
813 ]);
814 add_extension_name(root, "extensionsUsed")?;
815 if options.mode == CompressionMode::DracoOnly {
816 add_extension_name(root, "extensionsRequired")?;
817 }
818 if options.mode == CompressionMode::DracoOnly {
819 detach_draco_only_accessors(root, mesh.0, primitive, &mapping, &layout)?;
820 }
821 }
822 self.resources.buffers.push(bytes.clone());
823 if options.mode == CompressionMode::DracoOnly {
824 compact_draco_only_resources(
825 &mut self.document,
826 &mut self.resources.buffers,
827 &self.extensions,
828 options.max_output_bytes,
829 )?;
830 }
831 let output_bytes = self
832 .resources
833 .buffers
834 .iter()
835 .try_fold(0usize, |total, buffer| {
836 total
837 .checked_add(buffer.len())
838 .ok_or_else(|| Error::ResourceLimit("total output buffer size overflow".into()))
839 })?;
840 if let Some(limit) = options.max_output_bytes {
841 if output_bytes > limit {
842 return Err(Error::ResourceLimit(format!(
843 "compressed output size {output_bytes} exceeds limit {limit}"
844 )));
845 }
846 }
847 Ok(CompressionReport {
848 mode: options.mode,
849 compressed_primitives: 1,
850 encoded_bytes: bytes.len(),
851 source_bytes,
852 output_bytes,
853 reclaimed_bytes: source_bytes.saturating_sub(output_bytes),
854 })
855 }
856}