1use crate::gltf_error::{GltfError, Result};
13use draco_core::draco_types::DataType;
14use draco_core::geometry_attribute::{GeometryAttributeType, PointAttribute};
15use draco_core::mesh::Mesh;
16
17pub(crate) const GLTF_MODE_POINTS: u32 = 0;
18pub(crate) const GLTF_MODE_TRIANGLES: u32 = 4;
19pub(crate) const GLTF_COMPONENT_BYTE: u32 = 5120;
20pub(crate) const GLTF_COMPONENT_UNSIGNED_BYTE: u32 = 5121;
21pub(crate) const GLTF_COMPONENT_SHORT: u32 = 5122;
22pub(crate) const GLTF_COMPONENT_UNSIGNED_SHORT: u32 = 5123;
23pub(crate) const GLTF_COMPONENT_UNSIGNED_INT: u32 = 5125;
25pub(crate) const GLTF_COMPONENT_FLOAT: u32 = 5126;
26
27pub struct DecodedAccessor {
34 count: usize,
35 num_components: u8,
36 data_type: DataType,
37 normalized: bool,
38 bytes: Vec<u8>,
39}
40
41impl DecodedAccessor {
42 pub fn new(
45 count: usize,
46 num_components: u8,
47 data_type: DataType,
48 normalized: bool,
49 bytes: Vec<u8>,
50 ) -> Result<Self> {
51 let expected = count
52 .checked_mul(num_components as usize)
53 .and_then(|count| count.checked_mul(data_type.byte_length()))
54 .ok_or_else(|| GltfError::InvalidGltf("accessor byte size overflow".into()))?;
55 if bytes.len() != expected {
56 return Err(GltfError::InvalidGltf(format!(
57 "accessor has {} bytes, expected {expected}",
58 bytes.len()
59 )));
60 }
61 Ok(Self {
62 count,
63 num_components,
64 data_type,
65 normalized,
66 bytes,
67 })
68 }
69
70 fn gather(&self, indices: &[u32]) -> Result<Self> {
71 let stride = (self.num_components as usize)
72 .checked_mul(self.data_type.byte_length())
73 .ok_or_else(|| GltfError::InvalidGltf("accessor stride overflow".into()))?;
74 let byte_len = indices
75 .len()
76 .checked_mul(stride)
77 .ok_or_else(|| GltfError::InvalidGltf("gathered accessor size overflow".into()))?;
78 let mut bytes = Vec::new();
79 bytes.try_reserve_exact(byte_len).map_err(|_| {
80 GltfError::ResourceLimitExceeded("gathered accessor allocation failed".into())
81 })?;
82
83 for &index in indices {
84 let index = index as usize;
85 if index >= self.count {
86 return Err(GltfError::InvalidGltf(format!(
87 "Accessor index {} out of bounds for {} values",
88 index, self.count
89 )));
90 }
91 let offset = index
92 .checked_mul(stride)
93 .ok_or_else(|| GltfError::InvalidGltf("accessor offset overflow".into()))?;
94 let end = offset
95 .checked_add(stride)
96 .filter(|end| *end <= self.bytes.len())
97 .ok_or_else(|| GltfError::InvalidGltf("accessor bytes are truncated".into()))?;
98 bytes.extend_from_slice(&self.bytes[offset..end]);
99 }
100
101 Ok(Self {
102 count: indices.len(),
103 num_components: self.num_components,
104 data_type: self.data_type,
105 normalized: self.normalized,
106 bytes,
107 })
108 }
109}
110
111pub trait AccessorSource {
123 fn read_attribute(
127 &self,
128 accessor: usize,
129 expected_types: &[&str],
130 allowed_component_types: &[u32],
131 ) -> Result<DecodedAccessor>;
132
133 fn read_indices(&self, accessor: usize) -> Result<Vec<u32>>;
135}
136
137#[derive(Clone, Copy)]
138pub(crate) struct SemanticSpec {
139 pub(crate) attribute_type: GeometryAttributeType,
140 pub(crate) expected_accessor_types: &'static [&'static str],
141 pub(crate) allowed_component_types: &'static [u32],
142 normalization: NormalizationPolicy,
143}
144
145#[derive(Clone, Copy)]
146enum NormalizationPolicy {
147 Forbidden,
148 RequiredForInteger,
149 Generic,
150}
151
152const FLOAT_ONLY: &[u32] = &[GLTF_COMPONENT_FLOAT];
153const TEXCOORD_COMPONENT_TYPES: &[u32] = &[
154 GLTF_COMPONENT_FLOAT,
155 GLTF_COMPONENT_UNSIGNED_BYTE,
156 GLTF_COMPONENT_UNSIGNED_SHORT,
157];
158const COLOR_COMPONENT_TYPES: &[u32] = &[
159 GLTF_COMPONENT_FLOAT,
160 GLTF_COMPONENT_UNSIGNED_BYTE,
161 GLTF_COMPONENT_UNSIGNED_SHORT,
162];
163const JOINT_COMPONENT_TYPES: &[u32] =
164 &[GLTF_COMPONENT_UNSIGNED_BYTE, GLTF_COMPONENT_UNSIGNED_SHORT];
165const WEIGHT_COMPONENT_TYPES: &[u32] = &[
166 GLTF_COMPONENT_FLOAT,
167 GLTF_COMPONENT_UNSIGNED_BYTE,
168 GLTF_COMPONENT_UNSIGNED_SHORT,
169];
170const GENERIC_COMPONENT_TYPES: &[u32] = &[
171 GLTF_COMPONENT_BYTE,
172 GLTF_COMPONENT_UNSIGNED_BYTE,
173 GLTF_COMPONENT_SHORT,
174 GLTF_COMPONENT_UNSIGNED_SHORT,
175 GLTF_COMPONENT_FLOAT,
176];
177
178pub fn decode_geometry<S: AccessorSource>(
193 src: &S,
194 mode: u32,
195 attributes: &[(String, usize)],
196 indices: Option<usize>,
197) -> Result<(Mesh, Vec<(String, u32)>)> {
198 if mode != GLTF_MODE_TRIANGLES && mode != GLTF_MODE_POINTS {
199 return Err(GltfError::Unsupported(format!(
200 "Primitive mode {} not supported (only POINTS=0 and TRIANGLES=4)",
201 mode
202 )));
203 }
204
205 let pos_accessor_idx = attributes
207 .iter()
208 .find(|(semantic, _)| semantic == "POSITION")
209 .map(|(_, accessor)| *accessor)
210 .ok_or_else(|| GltfError::InvalidGltf("primitive has no POSITION attribute".into()))?;
211
212 let positions = src.read_attribute(pos_accessor_idx, &["VEC3"], &[GLTF_COMPONENT_FLOAT])?;
213 validate_decoded_semantic("POSITION", &positions)?;
214
215 let mut mesh = Mesh::new();
216 let point_indices = if mode == GLTF_MODE_POINTS {
217 indices.map(|idx| src.read_indices(idx)).transpose()?
218 } else {
219 None
220 };
221 let positions = if let Some(idx) = &point_indices {
222 positions.gather(idx)?
223 } else {
224 positions
225 };
226 mesh.set_num_points(positions.count);
227
228 let mut semantics: Vec<(String, u32)> = Vec::new();
229 semantics.try_reserve_exact(attributes.len()).map_err(|_| {
230 GltfError::ResourceLimitExceeded("attribute semantic table allocation failed".into())
231 })?;
232 let pos_id = add_decoded_attribute(&mut mesh, GeometryAttributeType::Position, positions)?;
233 semantics.push(("POSITION".to_string(), pos_id as u32));
234
235 if mode == GLTF_MODE_TRIANGLES {
236 if let Some(indices_accessor_idx) = indices {
237 let indices = src.read_indices(indices_accessor_idx)?;
238 if indices.len() % 3 != 0 {
239 return Err(GltfError::InvalidGltf(
240 "Index count not divisible by 3 for triangles".into(),
241 ));
242 }
243 for &index in &indices {
244 if index as usize >= mesh.num_points() {
245 return Err(GltfError::InvalidGltf(format!(
246 "Triangle index {} out of bounds for {} points",
247 index,
248 mesh.num_points()
249 )));
250 }
251 }
252 let num_faces = indices.len() / 3;
253 mesh.try_set_num_faces(num_faces)
254 .map_err(GltfError::DracoEncode)?;
255 for (face_id, face) in indices.chunks_exact(3).enumerate() {
256 mesh.set_face_from_indices(face_id, [face[0], face[1], face[2]]);
257 }
258 } else {
259 if !mesh.num_points().is_multiple_of(3) {
261 return Err(GltfError::InvalidGltf(
262 "Non-indexed primitive point count not divisible by 3".into(),
263 ));
264 }
265 let num_faces = mesh.num_points() / 3;
266 mesh.try_set_num_faces(num_faces)
267 .map_err(GltfError::DracoEncode)?;
268 for face_id in 0..num_faces {
269 let base = face_id
270 .checked_mul(3)
271 .and_then(|base| u32::try_from(base).ok())
272 .ok_or_else(|| {
273 GltfError::InvalidGltf(
274 "Non-indexed primitive exceeds Draco's u32 point-id limit".into(),
275 )
276 })?;
277 let second = base
278 .checked_add(1)
279 .ok_or_else(|| GltfError::InvalidGltf("Triangle point-id overflow".into()))?;
280 let third = base
281 .checked_add(2)
282 .ok_or_else(|| GltfError::InvalidGltf("Triangle point-id overflow".into()))?;
283 mesh.set_face_from_indices(face_id, [base, second, third]);
284 }
285 }
286 }
287
288 if let Some(normal_idx) = attributes
290 .iter()
291 .find(|(semantic, _)| semantic == "NORMAL")
292 .map(|(_, accessor)| *accessor)
293 {
294 let spec = supported_semantic_spec("NORMAL")?;
295 let normal_id = read_and_add_standard_attribute(
296 &mut mesh,
297 src,
298 normal_idx,
299 "NORMAL",
300 spec,
301 point_indices.as_deref(),
302 )?;
303 semantics.push(("NORMAL".to_string(), normal_id as u32));
304 }
305
306 let mut sorted: Vec<&(String, usize)> = Vec::new();
310 sorted.try_reserve_exact(attributes.len()).map_err(|_| {
311 GltfError::ResourceLimitExceeded("attribute sort table allocation failed".into())
312 })?;
313 sorted.extend(attributes.iter());
314 sorted.sort_by(|(left, _), (right, _)| left.cmp(right));
315 for (semantic, accessor_idx) in sorted {
316 if semantic == "POSITION" || semantic == "NORMAL" {
317 continue;
318 }
319 let attribute_spec = supported_semantic_spec(semantic)?;
320 let att_id = read_and_add_standard_attribute(
321 &mut mesh,
322 src,
323 *accessor_idx,
324 semantic,
325 attribute_spec,
326 point_indices.as_deref(),
327 )?;
328 semantics.push((semantic.clone(), att_id as u32));
329 }
330
331 mesh.deduplicate_point_ids();
335
336 Ok((mesh, semantics))
337}
338
339fn read_and_add_standard_attribute<S: AccessorSource>(
340 mesh: &mut Mesh,
341 src: &S,
342 accessor_idx: usize,
343 semantic: &str,
344 spec: SemanticSpec,
345 point_indices: Option<&[u32]>,
346) -> Result<i32> {
347 let decoded = src.read_attribute(
348 accessor_idx,
349 spec.expected_accessor_types,
350 spec.allowed_component_types,
351 )?;
352 validate_decoded_semantic(semantic, &decoded)?;
353 let decoded = if let Some(indices) = point_indices {
354 decoded.gather(indices)?
355 } else {
356 decoded
357 };
358 add_decoded_attribute(mesh, spec.attribute_type, decoded)
359}
360
361fn add_decoded_attribute(
364 mesh: &mut Mesh,
365 attribute_type: GeometryAttributeType,
366 decoded: DecodedAccessor,
367) -> Result<i32> {
368 if decoded.count != mesh.num_points() {
369 return Err(GltfError::InvalidGltf(format!(
370 "Attribute {:?} has {} values but primitive has {} points",
371 attribute_type,
372 decoded.count,
373 mesh.num_points()
374 )));
375 }
376
377 let mut attribute = PointAttribute::new();
378 attribute
379 .try_init(
380 attribute_type,
381 decoded.num_components,
382 decoded.data_type,
383 decoded.normalized,
384 decoded.count,
385 )
386 .map_err(GltfError::DracoEncode)?;
387 if !attribute.buffer_mut().try_write(0, &decoded.bytes) {
388 return Err(GltfError::DracoEncode(draco_core::DracoError::BufferError(
389 "Decoded glTF attribute does not fit its Draco buffer".into(),
390 )));
391 }
392 Ok(mesh.add_attribute(attribute))
393}
394
395pub(crate) fn supported_semantic_spec(semantic: &str) -> Result<SemanticSpec> {
396 let spec = if semantic == "POSITION" {
397 SemanticSpec {
398 attribute_type: GeometryAttributeType::Position,
399 expected_accessor_types: &["VEC3"],
400 allowed_component_types: FLOAT_ONLY,
401 normalization: NormalizationPolicy::Forbidden,
402 }
403 } else if semantic == "NORMAL" {
404 SemanticSpec {
405 attribute_type: GeometryAttributeType::Normal,
406 expected_accessor_types: &["VEC3"],
407 allowed_component_types: FLOAT_ONLY,
408 normalization: NormalizationPolicy::Forbidden,
409 }
410 } else if semantic == "TANGENT" {
411 SemanticSpec {
412 attribute_type: GeometryAttributeType::Generic,
413 expected_accessor_types: &["VEC4"],
414 allowed_component_types: FLOAT_ONLY,
415 normalization: NormalizationPolicy::Forbidden,
416 }
417 } else if indexed_semantic(semantic, "TEXCOORD_") {
418 SemanticSpec {
419 attribute_type: GeometryAttributeType::TexCoord,
420 expected_accessor_types: &["VEC2"],
421 allowed_component_types: TEXCOORD_COMPONENT_TYPES,
422 normalization: NormalizationPolicy::RequiredForInteger,
423 }
424 } else if indexed_semantic(semantic, "COLOR_") {
425 SemanticSpec {
426 attribute_type: GeometryAttributeType::Color,
427 expected_accessor_types: &["VEC3", "VEC4"],
428 allowed_component_types: COLOR_COMPONENT_TYPES,
429 normalization: NormalizationPolicy::RequiredForInteger,
430 }
431 } else if indexed_semantic(semantic, "JOINTS_") {
432 SemanticSpec {
433 attribute_type: GeometryAttributeType::Generic,
434 expected_accessor_types: &["VEC4"],
435 allowed_component_types: JOINT_COMPONENT_TYPES,
436 normalization: NormalizationPolicy::Forbidden,
437 }
438 } else if indexed_semantic(semantic, "WEIGHTS_") {
439 SemanticSpec {
440 attribute_type: GeometryAttributeType::Generic,
441 expected_accessor_types: &["VEC4"],
442 allowed_component_types: WEIGHT_COMPONENT_TYPES,
443 normalization: NormalizationPolicy::RequiredForInteger,
444 }
445 } else if semantic.starts_with('_') && semantic.len() > 1 {
446 SemanticSpec {
450 attribute_type: GeometryAttributeType::Generic,
451 expected_accessor_types: &["SCALAR", "VEC2", "VEC3", "VEC4"],
452 allowed_component_types: GENERIC_COMPONENT_TYPES,
453 normalization: NormalizationPolicy::Generic,
454 }
455 } else {
456 return Err(GltfError::InvalidGltf(format!(
457 "invalid glTF attribute semantic {semantic}"
458 )));
459 };
460
461 Ok(spec)
462}
463
464fn indexed_semantic(semantic: &str, prefix: &str) -> bool {
465 semantic
466 .strip_prefix(prefix)
467 .is_some_and(|index| !index.is_empty() && index.bytes().all(|byte| byte.is_ascii_digit()))
468}
469
470pub(crate) fn validate_semantic_accessor(
471 semantic: &str,
472 accessor_type: &str,
473 component_type: u32,
474 normalized: bool,
475) -> Result<SemanticSpec> {
476 let spec = supported_semantic_spec(semantic)?;
477 if !spec.expected_accessor_types.contains(&accessor_type) {
478 return Err(GltfError::InvalidGltf(format!(
479 "{semantic} accessor type {accessor_type} is invalid"
480 )));
481 }
482 if !spec.allowed_component_types.contains(&component_type) {
483 return Err(GltfError::InvalidGltf(format!(
484 "{semantic} accessor componentType {component_type} is invalid"
485 )));
486 }
487 let integer = component_type != GLTF_COMPONENT_FLOAT;
488 let valid_normalized = match spec.normalization {
489 NormalizationPolicy::Forbidden => !normalized,
490 NormalizationPolicy::RequiredForInteger => normalized == integer,
491 NormalizationPolicy::Generic => !normalized || integer,
492 };
493 if !valid_normalized {
494 return Err(GltfError::InvalidGltf(format!(
495 "{semantic} accessor normalized={normalized} is invalid for componentType {component_type}"
496 )));
497 }
498 Ok(spec)
499}
500
501fn validate_decoded_semantic(semantic: &str, accessor: &DecodedAccessor) -> Result<()> {
502 validate_semantic_accessor(
503 semantic,
504 gltf_type_for_num_components(accessor.num_components)?,
505 component_type_for_data_type(accessor.data_type)?,
506 accessor.normalized,
507 )?;
508 Ok(())
509}
510
511pub(crate) fn gltf_type_for_num_components(num_components: u8) -> Result<&'static str> {
512 match num_components {
513 1 => Ok("SCALAR"),
514 2 => Ok("VEC2"),
515 3 => Ok("VEC3"),
516 4 => Ok("VEC4"),
517 _ => Err(GltfError::InvalidGltf(format!(
518 "Invalid accessor component count: {num_components}"
519 ))),
520 }
521}
522
523pub(crate) fn component_type_for_data_type(data_type: DataType) -> Result<u32> {
524 match data_type {
525 DataType::Int8 => Ok(GLTF_COMPONENT_BYTE),
526 DataType::Uint8 => Ok(GLTF_COMPONENT_UNSIGNED_BYTE),
527 DataType::Int16 => Ok(GLTF_COMPONENT_SHORT),
528 DataType::Uint16 => Ok(GLTF_COMPONENT_UNSIGNED_SHORT),
529 DataType::Uint32 => Ok(GLTF_COMPONENT_UNSIGNED_INT),
530 DataType::Float32 => Ok(GLTF_COMPONENT_FLOAT),
531 _ => Err(GltfError::Unsupported(format!(
532 "Unsupported Draco attribute data type for glTF: {data_type:?}"
533 ))),
534 }
535}