1use std::io;
15
16use draco_core::draco_types::DataType;
17use draco_core::geometry_attribute::{GeometryAttributeType, PointAttribute};
18use draco_core::mesh::Mesh;
19use thiserror::Error;
20
21#[derive(Error, Debug)]
23pub enum GltfError {
24 #[error("IO error: {0}")]
26 Io(#[from] io::Error),
27
28 #[error("JSON parse error: {0}")]
30 Json(#[from] serde_json::Error),
31
32 #[error("Invalid GLB: {0}")]
34 InvalidGlb(String),
35
36 #[error("Invalid glTF: {0}")]
38 InvalidGltf(String),
39
40 #[error("Draco decode error: {0}")]
42 DracoDecode(#[source] draco_core::DracoError),
43
44 #[error("Draco encode error: {0}")]
46 DracoEncode(#[source] draco_core::DracoError),
47
48 #[error("Unsupported feature: {0}")]
50 Unsupported(String),
51
52 #[error("External resource denied: {0}")]
54 ExternalResourceDenied(String),
55
56 #[error("Resource limit exceeded: {0}")]
58 ResourceLimitExceeded(String),
59
60 #[error("Opaque binary reference: {0}")]
62 OpaqueBinaryReference(String),
63
64 #[error("Invalid compression options: {0}")]
66 InvalidOptions(String),
67}
68
69pub type Result<T> = std::result::Result<T, GltfError>;
71
72pub(crate) const GLTF_MODE_POINTS: u32 = 0;
73pub(crate) const GLTF_MODE_TRIANGLES: u32 = 4;
74pub(crate) const GLTF_COMPONENT_BYTE: u32 = 5120;
75pub(crate) const GLTF_COMPONENT_UNSIGNED_BYTE: u32 = 5121;
76pub(crate) const GLTF_COMPONENT_SHORT: u32 = 5122;
77pub(crate) const GLTF_COMPONENT_UNSIGNED_SHORT: u32 = 5123;
78#[cfg(feature = "gltf-reader")]
80pub(crate) const GLTF_COMPONENT_UNSIGNED_INT: u32 = 5125;
81pub(crate) const GLTF_COMPONENT_FLOAT: u32 = 5126;
82
83pub struct DecodedAccessor {
90 count: usize,
91 num_components: u8,
92 data_type: DataType,
93 normalized: bool,
94 bytes: Vec<u8>,
95}
96
97impl DecodedAccessor {
98 pub fn new(
101 count: usize,
102 num_components: u8,
103 data_type: DataType,
104 normalized: bool,
105 bytes: Vec<u8>,
106 ) -> Result<Self> {
107 let expected = count
108 .checked_mul(num_components as usize)
109 .and_then(|count| count.checked_mul(data_type.byte_length()))
110 .ok_or_else(|| GltfError::InvalidGltf("accessor byte size overflow".into()))?;
111 if bytes.len() != expected {
112 return Err(GltfError::InvalidGltf(format!(
113 "accessor has {} bytes, expected {expected}",
114 bytes.len()
115 )));
116 }
117 Ok(Self {
118 count,
119 num_components,
120 data_type,
121 normalized,
122 bytes,
123 })
124 }
125
126 fn gather(&self, indices: &[u32]) -> Result<Self> {
127 let stride = (self.num_components as usize)
128 .checked_mul(self.data_type.byte_length())
129 .ok_or_else(|| GltfError::InvalidGltf("accessor stride overflow".into()))?;
130 let byte_len = indices
131 .len()
132 .checked_mul(stride)
133 .ok_or_else(|| GltfError::InvalidGltf("gathered accessor size overflow".into()))?;
134 let mut bytes = Vec::new();
135 bytes.try_reserve_exact(byte_len).map_err(|_| {
136 GltfError::ResourceLimitExceeded("gathered accessor allocation failed".into())
137 })?;
138
139 for &index in indices {
140 let index = index as usize;
141 if index >= self.count {
142 return Err(GltfError::InvalidGltf(format!(
143 "Accessor index {} out of bounds for {} values",
144 index, self.count
145 )));
146 }
147 let offset = index
148 .checked_mul(stride)
149 .ok_or_else(|| GltfError::InvalidGltf("accessor offset overflow".into()))?;
150 let end = offset
151 .checked_add(stride)
152 .filter(|end| *end <= self.bytes.len())
153 .ok_or_else(|| GltfError::InvalidGltf("accessor bytes are truncated".into()))?;
154 bytes.extend_from_slice(&self.bytes[offset..end]);
155 }
156
157 Ok(Self {
158 count: indices.len(),
159 num_components: self.num_components,
160 data_type: self.data_type,
161 normalized: self.normalized,
162 bytes,
163 })
164 }
165}
166
167pub trait AccessorSource {
179 fn read_attribute(
183 &self,
184 accessor: usize,
185 expected_types: &[&str],
186 allowed_component_types: &[u32],
187 ) -> Result<DecodedAccessor>;
188
189 fn read_indices(&self, accessor: usize) -> Result<Vec<u32>>;
191}
192
193#[derive(Clone, Copy)]
194pub(crate) struct SemanticSpec {
195 pub(crate) attribute_type: GeometryAttributeType,
196 pub(crate) expected_accessor_types: &'static [&'static str],
197 pub(crate) allowed_component_types: &'static [u32],
198 normalization: NormalizationPolicy,
199}
200
201#[derive(Clone, Copy)]
202enum NormalizationPolicy {
203 Forbidden,
204 RequiredForInteger,
205 Generic,
206}
207
208const FLOAT_ONLY: &[u32] = &[GLTF_COMPONENT_FLOAT];
209const TEXCOORD_COMPONENT_TYPES: &[u32] = &[
210 GLTF_COMPONENT_FLOAT,
211 GLTF_COMPONENT_UNSIGNED_BYTE,
212 GLTF_COMPONENT_UNSIGNED_SHORT,
213];
214const COLOR_COMPONENT_TYPES: &[u32] = &[
215 GLTF_COMPONENT_FLOAT,
216 GLTF_COMPONENT_UNSIGNED_BYTE,
217 GLTF_COMPONENT_UNSIGNED_SHORT,
218];
219const JOINT_COMPONENT_TYPES: &[u32] =
220 &[GLTF_COMPONENT_UNSIGNED_BYTE, GLTF_COMPONENT_UNSIGNED_SHORT];
221const WEIGHT_COMPONENT_TYPES: &[u32] = &[
222 GLTF_COMPONENT_FLOAT,
223 GLTF_COMPONENT_UNSIGNED_BYTE,
224 GLTF_COMPONENT_UNSIGNED_SHORT,
225];
226const GENERIC_COMPONENT_TYPES: &[u32] = &[
227 GLTF_COMPONENT_BYTE,
228 GLTF_COMPONENT_UNSIGNED_BYTE,
229 GLTF_COMPONENT_SHORT,
230 GLTF_COMPONENT_UNSIGNED_SHORT,
231 GLTF_COMPONENT_FLOAT,
232];
233
234pub fn decode_geometry<S: AccessorSource>(
249 src: &S,
250 mode: u32,
251 attributes: &[(String, usize)],
252 indices: Option<usize>,
253) -> Result<(Mesh, Vec<(String, u32)>)> {
254 if mode != GLTF_MODE_TRIANGLES && mode != GLTF_MODE_POINTS {
255 return Err(GltfError::Unsupported(format!(
256 "Primitive mode {} not supported (only POINTS=0 and TRIANGLES=4)",
257 mode
258 )));
259 }
260
261 let pos_accessor_idx = attributes
263 .iter()
264 .find(|(semantic, _)| semantic == "POSITION")
265 .map(|(_, accessor)| *accessor)
266 .ok_or_else(|| GltfError::InvalidGltf("primitive has no POSITION attribute".into()))?;
267
268 let positions = src.read_attribute(pos_accessor_idx, &["VEC3"], &[GLTF_COMPONENT_FLOAT])?;
269 validate_decoded_semantic("POSITION", &positions)?;
270
271 let mut mesh = Mesh::new();
272 let point_indices = if mode == GLTF_MODE_POINTS {
273 indices.map(|idx| src.read_indices(idx)).transpose()?
274 } else {
275 None
276 };
277 let positions = if let Some(idx) = &point_indices {
278 positions.gather(idx)?
279 } else {
280 positions
281 };
282 mesh.set_num_points(positions.count);
283
284 let mut semantics: Vec<(String, u32)> = Vec::new();
285 semantics.try_reserve_exact(attributes.len()).map_err(|_| {
286 GltfError::ResourceLimitExceeded("attribute semantic table allocation failed".into())
287 })?;
288 let pos_id = add_decoded_attribute(&mut mesh, GeometryAttributeType::Position, positions)?;
289 semantics.push(("POSITION".to_string(), pos_id as u32));
290
291 if mode == GLTF_MODE_TRIANGLES {
292 if let Some(indices_accessor_idx) = indices {
293 let indices = src.read_indices(indices_accessor_idx)?;
294 if indices.len() % 3 != 0 {
295 return Err(GltfError::InvalidGltf(
296 "Index count not divisible by 3 for triangles".into(),
297 ));
298 }
299 for &index in &indices {
300 if index as usize >= mesh.num_points() {
301 return Err(GltfError::InvalidGltf(format!(
302 "Triangle index {} out of bounds for {} points",
303 index,
304 mesh.num_points()
305 )));
306 }
307 }
308 let num_faces = indices.len() / 3;
309 mesh.try_set_num_faces(num_faces)
310 .map_err(GltfError::DracoEncode)?;
311 for (face_id, face) in indices.chunks_exact(3).enumerate() {
312 mesh.set_face_from_indices(face_id, [face[0], face[1], face[2]]);
313 }
314 } else {
315 if !mesh.num_points().is_multiple_of(3) {
317 return Err(GltfError::InvalidGltf(
318 "Non-indexed primitive point count not divisible by 3".into(),
319 ));
320 }
321 let num_faces = mesh.num_points() / 3;
322 mesh.try_set_num_faces(num_faces)
323 .map_err(GltfError::DracoEncode)?;
324 for face_id in 0..num_faces {
325 let base = face_id
326 .checked_mul(3)
327 .and_then(|base| u32::try_from(base).ok())
328 .ok_or_else(|| {
329 GltfError::InvalidGltf(
330 "Non-indexed primitive exceeds Draco's u32 point-id limit".into(),
331 )
332 })?;
333 let second = base
334 .checked_add(1)
335 .ok_or_else(|| GltfError::InvalidGltf("Triangle point-id overflow".into()))?;
336 let third = base
337 .checked_add(2)
338 .ok_or_else(|| GltfError::InvalidGltf("Triangle point-id overflow".into()))?;
339 mesh.set_face_from_indices(face_id, [base, second, third]);
340 }
341 }
342 }
343
344 if let Some(normal_idx) = attributes
346 .iter()
347 .find(|(semantic, _)| semantic == "NORMAL")
348 .map(|(_, accessor)| *accessor)
349 {
350 let spec = supported_semantic_spec("NORMAL")?;
351 let normal_id = read_and_add_standard_attribute(
352 &mut mesh,
353 src,
354 normal_idx,
355 "NORMAL",
356 spec,
357 point_indices.as_deref(),
358 )?;
359 semantics.push(("NORMAL".to_string(), normal_id as u32));
360 }
361
362 let mut sorted: Vec<&(String, usize)> = Vec::new();
366 sorted.try_reserve_exact(attributes.len()).map_err(|_| {
367 GltfError::ResourceLimitExceeded("attribute sort table allocation failed".into())
368 })?;
369 sorted.extend(attributes.iter());
370 sorted.sort_by(|(left, _), (right, _)| left.cmp(right));
371 for (semantic, accessor_idx) in sorted {
372 if semantic == "POSITION" || semantic == "NORMAL" {
373 continue;
374 }
375 let attribute_spec = supported_semantic_spec(semantic)?;
376 let att_id = read_and_add_standard_attribute(
377 &mut mesh,
378 src,
379 *accessor_idx,
380 semantic,
381 attribute_spec,
382 point_indices.as_deref(),
383 )?;
384 semantics.push((semantic.clone(), att_id as u32));
385 }
386
387 mesh.deduplicate_point_ids();
391
392 Ok((mesh, semantics))
393}
394
395#[cfg(feature = "gltf-reader")]
399pub(crate) fn add_named_attribute<S: AccessorSource>(
400 mesh: &mut Mesh,
401 src: &S,
402 semantic: &str,
403 accessor_idx: usize,
404 point_indices: Option<&[u32]>,
405) -> Result<i32> {
406 let spec = supported_semantic_spec(semantic)?;
407 read_and_add_standard_attribute(mesh, src, accessor_idx, semantic, spec, point_indices)
408}
409
410fn read_and_add_standard_attribute<S: AccessorSource>(
411 mesh: &mut Mesh,
412 src: &S,
413 accessor_idx: usize,
414 semantic: &str,
415 spec: SemanticSpec,
416 point_indices: Option<&[u32]>,
417) -> Result<i32> {
418 let decoded = src.read_attribute(
419 accessor_idx,
420 spec.expected_accessor_types,
421 spec.allowed_component_types,
422 )?;
423 validate_decoded_semantic(semantic, &decoded)?;
424 let decoded = if let Some(indices) = point_indices {
425 decoded.gather(indices)?
426 } else {
427 decoded
428 };
429 add_decoded_attribute(mesh, spec.attribute_type, decoded)
430}
431
432fn add_decoded_attribute(
435 mesh: &mut Mesh,
436 attribute_type: GeometryAttributeType,
437 decoded: DecodedAccessor,
438) -> Result<i32> {
439 if decoded.count != mesh.num_points() {
440 return Err(GltfError::InvalidGltf(format!(
441 "Attribute {:?} has {} values but primitive has {} points",
442 attribute_type,
443 decoded.count,
444 mesh.num_points()
445 )));
446 }
447
448 let mut attribute = PointAttribute::new();
449 attribute
450 .try_init(
451 attribute_type,
452 decoded.num_components,
453 decoded.data_type,
454 decoded.normalized,
455 decoded.count,
456 )
457 .map_err(GltfError::DracoEncode)?;
458 if !attribute.buffer_mut().try_write(0, &decoded.bytes) {
459 return Err(GltfError::DracoEncode(draco_core::DracoError::BufferError(
460 "Decoded glTF attribute does not fit its Draco buffer".into(),
461 )));
462 }
463 Ok(mesh.add_attribute(attribute))
464}
465
466pub(crate) fn supported_semantic_spec(semantic: &str) -> Result<SemanticSpec> {
467 let spec = if semantic == "POSITION" {
468 SemanticSpec {
469 attribute_type: GeometryAttributeType::Position,
470 expected_accessor_types: &["VEC3"],
471 allowed_component_types: FLOAT_ONLY,
472 normalization: NormalizationPolicy::Forbidden,
473 }
474 } else if semantic == "NORMAL" {
475 SemanticSpec {
476 attribute_type: GeometryAttributeType::Normal,
477 expected_accessor_types: &["VEC3"],
478 allowed_component_types: FLOAT_ONLY,
479 normalization: NormalizationPolicy::Forbidden,
480 }
481 } else if semantic == "TANGENT" {
482 SemanticSpec {
483 attribute_type: GeometryAttributeType::Generic,
484 expected_accessor_types: &["VEC4"],
485 allowed_component_types: FLOAT_ONLY,
486 normalization: NormalizationPolicy::Forbidden,
487 }
488 } else if indexed_semantic(semantic, "TEXCOORD_") {
489 SemanticSpec {
490 attribute_type: GeometryAttributeType::TexCoord,
491 expected_accessor_types: &["VEC2"],
492 allowed_component_types: TEXCOORD_COMPONENT_TYPES,
493 normalization: NormalizationPolicy::RequiredForInteger,
494 }
495 } else if indexed_semantic(semantic, "COLOR_") {
496 SemanticSpec {
497 attribute_type: GeometryAttributeType::Color,
498 expected_accessor_types: &["VEC3", "VEC4"],
499 allowed_component_types: COLOR_COMPONENT_TYPES,
500 normalization: NormalizationPolicy::RequiredForInteger,
501 }
502 } else if indexed_semantic(semantic, "JOINTS_") {
503 SemanticSpec {
504 attribute_type: GeometryAttributeType::Generic,
505 expected_accessor_types: &["VEC4"],
506 allowed_component_types: JOINT_COMPONENT_TYPES,
507 normalization: NormalizationPolicy::Forbidden,
508 }
509 } else if indexed_semantic(semantic, "WEIGHTS_") {
510 SemanticSpec {
511 attribute_type: GeometryAttributeType::Generic,
512 expected_accessor_types: &["VEC4"],
513 allowed_component_types: WEIGHT_COMPONENT_TYPES,
514 normalization: NormalizationPolicy::RequiredForInteger,
515 }
516 } else if semantic.starts_with('_') && semantic.len() > 1 {
517 SemanticSpec {
521 attribute_type: GeometryAttributeType::Generic,
522 expected_accessor_types: &["SCALAR", "VEC2", "VEC3", "VEC4"],
523 allowed_component_types: GENERIC_COMPONENT_TYPES,
524 normalization: NormalizationPolicy::Generic,
525 }
526 } else {
527 return Err(GltfError::InvalidGltf(format!(
528 "invalid glTF attribute semantic {semantic}"
529 )));
530 };
531
532 Ok(spec)
533}
534
535fn indexed_semantic(semantic: &str, prefix: &str) -> bool {
536 semantic
537 .strip_prefix(prefix)
538 .is_some_and(|index| !index.is_empty() && index.bytes().all(|byte| byte.is_ascii_digit()))
539}
540
541pub(crate) fn validate_semantic_accessor(
542 semantic: &str,
543 accessor_type: &str,
544 component_type: u32,
545 normalized: bool,
546) -> Result<SemanticSpec> {
547 let spec = supported_semantic_spec(semantic)?;
548 if !spec.expected_accessor_types.contains(&accessor_type) {
549 return Err(GltfError::InvalidGltf(format!(
550 "{semantic} accessor type {accessor_type} is invalid"
551 )));
552 }
553 if !spec.allowed_component_types.contains(&component_type) {
554 return Err(GltfError::InvalidGltf(format!(
555 "{semantic} accessor componentType {component_type} is invalid"
556 )));
557 }
558 let integer = component_type != GLTF_COMPONENT_FLOAT;
559 let valid_normalized = match spec.normalization {
560 NormalizationPolicy::Forbidden => !normalized,
561 NormalizationPolicy::RequiredForInteger => normalized == integer,
562 NormalizationPolicy::Generic => !normalized || integer,
563 };
564 if !valid_normalized {
565 return Err(GltfError::InvalidGltf(format!(
566 "{semantic} accessor normalized={normalized} is invalid for componentType {component_type}"
567 )));
568 }
569 Ok(spec)
570}
571
572fn validate_decoded_semantic(semantic: &str, accessor: &DecodedAccessor) -> Result<()> {
573 validate_semantic_accessor(
574 semantic,
575 gltf_type_for_num_components(accessor.num_components)?,
576 component_type_for_data_type(accessor.data_type)?,
577 accessor.normalized,
578 )?;
579 Ok(())
580}
581
582pub(crate) fn gltf_type_for_num_components(num_components: u8) -> Result<&'static str> {
583 match num_components {
584 1 => Ok("SCALAR"),
585 2 => Ok("VEC2"),
586 3 => Ok("VEC3"),
587 4 => Ok("VEC4"),
588 _ => Err(GltfError::InvalidGltf(format!(
589 "Invalid accessor component count: {num_components}"
590 ))),
591 }
592}
593
594pub(crate) fn component_type_for_data_type(data_type: DataType) -> Result<u32> {
595 match data_type {
596 DataType::Int8 => Ok(GLTF_COMPONENT_BYTE),
597 DataType::Uint8 => Ok(GLTF_COMPONENT_UNSIGNED_BYTE),
598 DataType::Int16 => Ok(GLTF_COMPONENT_SHORT),
599 DataType::Uint16 => Ok(GLTF_COMPONENT_UNSIGNED_SHORT),
600 #[cfg(feature = "gltf-reader")]
601 DataType::Uint32 => Ok(GLTF_COMPONENT_UNSIGNED_INT),
602 DataType::Float32 => Ok(GLTF_COMPONENT_FLOAT),
603 _ => Err(GltfError::Unsupported(format!(
604 "Unsupported Draco attribute data type for glTF: {data_type:?}"
605 ))),
606 }
607}