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(String),
43
44 #[error("Unsupported feature: {0}")]
46 Unsupported(String),
47}
48
49pub type Result<T> = std::result::Result<T, GltfError>;
51
52pub(crate) const GLTF_MODE_POINTS: u32 = 0;
53pub(crate) const GLTF_MODE_TRIANGLES: u32 = 4;
54pub(crate) const GLTF_COMPONENT_BYTE: u32 = 5120;
55pub(crate) const GLTF_COMPONENT_UNSIGNED_BYTE: u32 = 5121;
56pub(crate) const GLTF_COMPONENT_SHORT: u32 = 5122;
57pub(crate) const GLTF_COMPONENT_UNSIGNED_SHORT: u32 = 5123;
58#[cfg(feature = "gltf-reader")]
60pub(crate) const GLTF_COMPONENT_UNSIGNED_INT: u32 = 5125;
61pub(crate) const GLTF_COMPONENT_FLOAT: u32 = 5126;
62
63pub struct DecodedAccessor {
70 count: usize,
71 num_components: u8,
72 data_type: DataType,
73 normalized: bool,
74 bytes: Vec<u8>,
75}
76
77impl DecodedAccessor {
78 pub fn new(
81 count: usize,
82 num_components: u8,
83 data_type: DataType,
84 normalized: bool,
85 bytes: Vec<u8>,
86 ) -> Self {
87 Self {
88 count,
89 num_components,
90 data_type,
91 normalized,
92 bytes,
93 }
94 }
95
96 fn gather(&self, indices: &[u32]) -> Result<Self> {
97 let stride = self.num_components as usize * self.data_type.byte_length();
98 let mut bytes = Vec::with_capacity(indices.len() * stride);
99
100 for &index in indices {
101 let index = index as usize;
102 if index >= self.count {
103 return Err(GltfError::InvalidGltf(format!(
104 "Accessor index {} out of bounds for {} values",
105 index, self.count
106 )));
107 }
108 let offset = index * stride;
109 bytes.extend_from_slice(&self.bytes[offset..offset + stride]);
110 }
111
112 Ok(Self {
113 count: indices.len(),
114 num_components: self.num_components,
115 data_type: self.data_type,
116 normalized: self.normalized,
117 bytes,
118 })
119 }
120}
121
122pub trait AccessorSource {
134 fn read_attribute(
138 &self,
139 accessor: usize,
140 expected_types: &[&str],
141 allowed_component_types: &[u32],
142 ) -> Result<DecodedAccessor>;
143
144 fn read_indices(&self, accessor: usize) -> Result<Vec<u32>>;
146}
147
148pub(crate) struct SemanticSpec {
149 pub(crate) attribute_type: GeometryAttributeType,
150 pub(crate) expected_accessor_types: &'static [&'static str],
151 pub(crate) allowed_component_types: &'static [u32],
152}
153
154const FLOAT_ONLY: &[u32] = &[GLTF_COMPONENT_FLOAT];
155const TEXCOORD_COMPONENT_TYPES: &[u32] = &[
156 GLTF_COMPONENT_FLOAT,
157 GLTF_COMPONENT_UNSIGNED_BYTE,
158 GLTF_COMPONENT_UNSIGNED_SHORT,
159];
160const COLOR_COMPONENT_TYPES: &[u32] = &[
161 GLTF_COMPONENT_FLOAT,
162 GLTF_COMPONENT_UNSIGNED_BYTE,
163 GLTF_COMPONENT_UNSIGNED_SHORT,
164];
165const GENERIC_COMPONENT_TYPES: &[u32] = &[
166 GLTF_COMPONENT_BYTE,
167 GLTF_COMPONENT_UNSIGNED_BYTE,
168 GLTF_COMPONENT_SHORT,
169 GLTF_COMPONENT_UNSIGNED_SHORT,
170 GLTF_COMPONENT_FLOAT,
171];
172
173pub fn decode_geometry<S: AccessorSource>(
188 src: &S,
189 mode: u32,
190 attributes: &[(String, usize)],
191 indices: Option<usize>,
192) -> Result<(Mesh, Vec<(String, u32)>)> {
193 use draco_core::geometry_indices::PointIndex;
194
195 if mode != GLTF_MODE_TRIANGLES && mode != GLTF_MODE_POINTS {
196 return Err(GltfError::Unsupported(format!(
197 "Primitive mode {} not supported (only POINTS=0 and TRIANGLES=4)",
198 mode
199 )));
200 }
201
202 let pos_accessor_idx = attributes
204 .iter()
205 .find(|(semantic, _)| semantic == "POSITION")
206 .map(|(_, accessor)| *accessor)
207 .ok_or_else(|| GltfError::InvalidGltf("primitive has no POSITION attribute".into()))?;
208
209 let positions = src.read_attribute(pos_accessor_idx, &["VEC3"], &[GLTF_COMPONENT_FLOAT])?;
210
211 let mut mesh = Mesh::new();
212 let point_indices = if mode == GLTF_MODE_POINTS {
213 indices.map(|idx| src.read_indices(idx)).transpose()?
214 } else {
215 None
216 };
217 let positions = if let Some(idx) = &point_indices {
218 positions.gather(idx)?
219 } else {
220 positions
221 };
222 mesh.set_num_points(positions.count);
223
224 let mut semantics: Vec<(String, u32)> = Vec::new();
225 let pos_id = add_decoded_attribute(&mut mesh, GeometryAttributeType::Position, positions)?;
226 semantics.push(("POSITION".to_string(), pos_id as u32));
227
228 if mode == GLTF_MODE_TRIANGLES {
229 if let Some(indices_accessor_idx) = indices {
230 let indices = src.read_indices(indices_accessor_idx)?;
231 if indices.len() % 3 != 0 {
232 return Err(GltfError::InvalidGltf(
233 "Index count not divisible by 3 for triangles".into(),
234 ));
235 }
236 for &index in &indices {
237 if index as usize >= mesh.num_points() {
238 return Err(GltfError::InvalidGltf(format!(
239 "Triangle index {} out of bounds for {} points",
240 index,
241 mesh.num_points()
242 )));
243 }
244 }
245 let num_faces = indices.len() / 3;
246 for i in 0..num_faces {
247 mesh.add_face([
248 PointIndex(indices[i * 3]),
249 PointIndex(indices[i * 3 + 1]),
250 PointIndex(indices[i * 3 + 2]),
251 ]);
252 }
253 } else {
254 if !mesh.num_points().is_multiple_of(3) {
256 return Err(GltfError::InvalidGltf(
257 "Non-indexed primitive point count not divisible by 3".into(),
258 ));
259 }
260 for i in 0..(mesh.num_points() / 3) {
261 let base = (i * 3) as u32;
262 mesh.add_face([PointIndex(base), PointIndex(base + 1), PointIndex(base + 2)]);
263 }
264 }
265 }
266
267 if let Some(normal_idx) = attributes
269 .iter()
270 .find(|(semantic, _)| semantic == "NORMAL")
271 .map(|(_, accessor)| *accessor)
272 {
273 let normal_id = read_and_add_standard_attribute(
274 &mut mesh,
275 src,
276 normal_idx,
277 GeometryAttributeType::Normal,
278 &["VEC3"],
279 &[GLTF_COMPONENT_FLOAT],
280 point_indices.as_deref(),
281 )?;
282 semantics.push(("NORMAL".to_string(), normal_id as u32));
283 }
284
285 let mut sorted: Vec<&(String, usize)> = attributes.iter().collect();
289 sorted.sort_by(|(left, _), (right, _)| left.cmp(right));
290 for (semantic, accessor_idx) in sorted {
291 if semantic == "POSITION" || semantic == "NORMAL" {
292 continue;
293 }
294 let attribute_spec = supported_semantic_spec(semantic)?;
295 let att_id = read_and_add_standard_attribute(
296 &mut mesh,
297 src,
298 *accessor_idx,
299 attribute_spec.attribute_type,
300 attribute_spec.expected_accessor_types,
301 attribute_spec.allowed_component_types,
302 point_indices.as_deref(),
303 )?;
304 semantics.push((semantic.clone(), att_id as u32));
305 }
306
307 mesh.deduplicate_point_ids();
311
312 Ok((mesh, semantics))
313}
314
315#[cfg(feature = "gltf-reader")]
319pub(crate) fn add_named_attribute<S: AccessorSource>(
320 mesh: &mut Mesh,
321 src: &S,
322 semantic: &str,
323 accessor_idx: usize,
324 point_indices: Option<&[u32]>,
325) -> Result<i32> {
326 let spec = supported_semantic_spec(semantic)?;
327 read_and_add_standard_attribute(
328 mesh,
329 src,
330 accessor_idx,
331 spec.attribute_type,
332 spec.expected_accessor_types,
333 spec.allowed_component_types,
334 point_indices,
335 )
336}
337
338fn read_and_add_standard_attribute<S: AccessorSource>(
339 mesh: &mut Mesh,
340 src: &S,
341 accessor_idx: usize,
342 attribute_type: GeometryAttributeType,
343 expected_types: &[&str],
344 allowed_component_types: &[u32],
345 point_indices: Option<&[u32]>,
346) -> Result<i32> {
347 let decoded = src.read_attribute(accessor_idx, expected_types, allowed_component_types)?;
348 let decoded = if let Some(indices) = point_indices {
349 decoded.gather(indices)?
350 } else {
351 decoded
352 };
353 add_decoded_attribute(mesh, attribute_type, decoded)
354}
355
356fn add_decoded_attribute(
359 mesh: &mut Mesh,
360 attribute_type: GeometryAttributeType,
361 decoded: DecodedAccessor,
362) -> Result<i32> {
363 if decoded.count != mesh.num_points() {
364 return Err(GltfError::InvalidGltf(format!(
365 "Attribute {:?} has {} values but primitive has {} points",
366 attribute_type,
367 decoded.count,
368 mesh.num_points()
369 )));
370 }
371
372 let mut attribute = PointAttribute::new();
373 attribute.init(
374 attribute_type,
375 decoded.num_components,
376 decoded.data_type,
377 decoded.normalized,
378 decoded.count,
379 );
380 attribute.buffer_mut().write(0, &decoded.bytes);
381 Ok(mesh.add_attribute(attribute))
382}
383
384pub(crate) fn supported_semantic_spec(semantic: &str) -> Result<SemanticSpec> {
385 let spec = if semantic == "POSITION" {
386 SemanticSpec {
387 attribute_type: GeometryAttributeType::Position,
388 expected_accessor_types: &["VEC3"],
389 allowed_component_types: FLOAT_ONLY,
390 }
391 } else if semantic == "NORMAL" {
392 SemanticSpec {
393 attribute_type: GeometryAttributeType::Normal,
394 expected_accessor_types: &["VEC3"],
395 allowed_component_types: FLOAT_ONLY,
396 }
397 } else if semantic.starts_with("TEXCOORD_") {
398 SemanticSpec {
399 attribute_type: GeometryAttributeType::TexCoord,
400 expected_accessor_types: &["VEC2"],
401 allowed_component_types: TEXCOORD_COMPONENT_TYPES,
402 }
403 } else if semantic.starts_with("COLOR_") {
404 SemanticSpec {
405 attribute_type: GeometryAttributeType::Color,
406 expected_accessor_types: &["VEC3", "VEC4"],
407 allowed_component_types: COLOR_COMPONENT_TYPES,
408 }
409 } else {
410 SemanticSpec {
415 attribute_type: GeometryAttributeType::Generic,
416 expected_accessor_types: &["SCALAR", "VEC2", "VEC3", "VEC4"],
417 allowed_component_types: GENERIC_COMPONENT_TYPES,
418 }
419 };
420
421 Ok(spec)
422}