Skip to main content

draco_gltf/
accessor.rs

1use crate::{Document, Error, ResourceStore, Result};
2use draco_core::draco_types::DataType;
3use draco_io::{AccessorSource, DecodedAccessor, GltfError};
4
5/// Accessor source backed by a [`Document`] and its resolved resources.
6pub struct DocumentAccessorSource<'a> {
7    document: &'a Document,
8    resources: &'a ResourceStore,
9}
10
11/// Tightly packed accessor payload for geometry consumers.
12#[derive(Clone, Debug)]
13pub struct AccessorData {
14    /// Number of elements in the accessor.
15    pub count: usize,
16    /// Original glTF accessor shape (`SCALAR`, `VEC*`, or `MAT*`).
17    pub accessor_type: String,
18    /// Number of scalar components per element.
19    pub components: u8,
20    /// Original glTF component type code.
21    pub component_type: u32,
22    /// Draco storage type used for materialization.
23    pub data_type: DataType,
24    /// Whether integer values use normalized interpretation.
25    pub normalized: bool,
26    /// Tightly packed accessor bytes in glTF component order.
27    ///
28    /// Matrix columns retain glTF's column-major order, with on-disk alignment
29    /// padding removed.
30    pub bytes: Vec<u8>,
31}
32
33#[derive(Clone, Copy)]
34struct AccessorLayout {
35    tight_width: usize,
36    source_width: usize,
37    columns: usize,
38    column_width: usize,
39    column_stride: usize,
40}
41
42impl<'a> DocumentAccessorSource<'a> {
43    /// Creates an accessor source over a document and resolved buffers.
44    pub fn new(document: &'a Document, resources: &'a ResourceStore) -> Self {
45        Self {
46            document,
47            resources,
48        }
49    }
50
51    /// Copies one complete buffer view from the resolved resource store.
52    ///
53    /// The returned bytes retain the buffer view's original layout, including
54    /// accessor stride or padding. Use [`Self::read_accessor`] when a tightly
55    /// packed, sparse-materialized accessor payload is needed.
56    pub fn read_buffer_view(&self, index: usize) -> Result<Vec<u8>> {
57        let view = self
58            .document
59            .as_value()
60            .get("bufferViews")
61            .and_then(|value| value.as_array())
62            .and_then(|values| values.get(index))
63            .ok_or_else(|| Error::Extension("bufferView out of range".into()))?;
64        let buffer = view
65            .get("buffer")
66            .and_then(|value| value.as_u64())
67            .and_then(|value| usize::try_from(value).ok())
68            .and_then(|index| self.resources.buffers.get(index))
69            .ok_or_else(|| Error::Extension("buffer is not resolved".into()))?;
70        let start = view
71            .get("byteOffset")
72            .and_then(|value| value.as_u64())
73            .unwrap_or(0);
74        let length = view
75            .get("byteLength")
76            .and_then(|value| value.as_u64())
77            .ok_or_else(|| Error::Extension("bufferView byteLength is invalid".into()))?;
78        let end = start
79            .checked_add(length)
80            .ok_or_else(|| Error::ResourceLimit("bufferView range overflow".into()))?;
81        let start = usize::try_from(start).map_err(|_| {
82            Error::ResourceLimit("bufferView offset exceeds platform limits".into())
83        })?;
84        let end = usize::try_from(end)
85            .map_err(|_| Error::ResourceLimit("bufferView end exceeds platform limits".into()))?;
86        let bytes = buffer
87            .get(start..end)
88            .ok_or_else(|| Error::Extension("bufferView range is out of bounds".into()))?;
89        let mut output = Vec::new();
90        output.try_reserve_exact(bytes.len()).map_err(|_| {
91            Error::ResourceLimit("bufferView materialization allocation failed".into())
92        })?;
93        output.extend_from_slice(bytes);
94        Ok(output)
95    }
96
97    fn read<const MATRICES: bool>(
98        &self,
99        index: usize,
100    ) -> Result<(usize, u8, u32, DataType, bool, Vec<u8>)> {
101        let accessor = self
102            .document
103            .as_value()
104            .get("accessors")
105            .and_then(|v| v.as_array())
106            .and_then(|v| v.get(index))
107            .ok_or_else(|| Error::Extension("accessor out of range".into()))?;
108        let count = accessor
109            .get("count")
110            .and_then(|v| v.as_u64())
111            .and_then(|v| usize::try_from(v).ok())
112            .ok_or_else(|| Error::Extension("accessor count is invalid".into()))?;
113        let accessor_type = accessor
114            .get("type")
115            .and_then(|value| value.as_str())
116            .ok_or_else(|| Error::Extension("accessor type is invalid".into()))?;
117        let components = match accessor_type {
118            "SCALAR" => 1,
119            "VEC2" => 2,
120            "VEC3" => 3,
121            "VEC4" => 4,
122            "MAT2" if MATRICES => 4,
123            "MAT3" if MATRICES => 9,
124            "MAT4" if MATRICES => 16,
125            _ => return Err(Error::Extension("accessor type is invalid".into())),
126        };
127        let component = accessor
128            .get("componentType")
129            .and_then(|v| v.as_u64())
130            .ok_or_else(|| Error::Extension("accessor componentType is invalid".into()))?;
131        let data_type = match component {
132            5120 => DataType::Int8,
133            5121 => DataType::Uint8,
134            5122 => DataType::Int16,
135            5123 => DataType::Uint16,
136            5125 => DataType::Uint32,
137            5126 => DataType::Float32,
138            5124 => DataType::Int32,
139            // `DataType` has no f16 variant. Its on-disk layout is identical
140            // to u16; `component` is preserved separately for packed output.
141            5131 => DataType::Uint16,
142            5130 => DataType::Float64,
143            5134 => DataType::Int64,
144            5135 => DataType::Uint64,
145            _ => {
146                return Err(Error::Extension(
147                    "unsupported accessor component type".into(),
148                ))
149            }
150        };
151        let layout = accessor_layout::<MATRICES>(accessor_type, data_type.byte_length())?;
152        let width = layout.tight_width;
153        let byte_len = count
154            .checked_mul(width)
155            .ok_or_else(|| Error::ResourceLimit("accessor byte size overflow".into()))?;
156        let mut bytes = if let Some(view) = accessor
157            .get("bufferView")
158            .and_then(|value| value.as_u64())
159            .and_then(|value| usize::try_from(value).ok())
160        {
161            let accessor_offset = accessor
162                .get("byteOffset")
163                .and_then(|value| value.as_u64())
164                .unwrap_or(0);
165            let (buffer, offset, stride) =
166                self.buffer_view_layout(view, accessor_offset, layout.source_width)?;
167            let mut dense = Vec::new();
168            dense.try_reserve_exact(byte_len).map_err(|_| {
169                Error::ResourceLimit("accessor materialization allocation failed".into())
170            })?;
171            for row in 0..count {
172                let start =
173                    offset
174                        .checked_add(row.checked_mul(stride).ok_or_else(|| {
175                            Error::ResourceLimit("accessor stride overflow".into())
176                        })?)
177                        .ok_or_else(|| Error::ResourceLimit("accessor offset overflow".into()))?;
178                copy_accessor_element(buffer, start, layout, &mut dense)?;
179            }
180            dense
181        } else if accessor.get("sparse").is_some() {
182            vec![0; byte_len]
183        } else {
184            return Err(Error::Extension(
185                "accessor has neither bufferView nor sparse values".into(),
186            ));
187        };
188        if let Some(sparse) = accessor.get("sparse") {
189            self.apply_sparse(sparse, count, layout, &mut bytes)?;
190        }
191        Ok((
192            count,
193            components,
194            component as u32,
195            data_type,
196            accessor
197                .get("normalized")
198                .and_then(|v| {
199                    if let crate::JsonValue::Bool(v) = v {
200                        Some(*v)
201                    } else {
202                        None
203                    }
204                })
205                .unwrap_or(false),
206            bytes,
207        ))
208    }
209
210    fn buffer_view_layout(
211        &self,
212        view_index: usize,
213        additional_offset: u64,
214        default_stride: usize,
215    ) -> Result<(&[u8], usize, usize)> {
216        let view = self
217            .document
218            .as_value()
219            .get("bufferViews")
220            .and_then(|value| value.as_array())
221            .and_then(|values| values.get(view_index))
222            .ok_or_else(|| Error::Extension("bufferView out of range".into()))?;
223        let buffer = view
224            .get("buffer")
225            .and_then(|value| value.as_u64())
226            .and_then(|value| usize::try_from(value).ok())
227            .and_then(|index| self.resources.buffers.get(index))
228            .ok_or_else(|| Error::Extension("buffer is not resolved".into()))?;
229        let offset = view
230            .get("byteOffset")
231            .and_then(|value| value.as_u64())
232            .unwrap_or(0)
233            .checked_add(additional_offset)
234            .and_then(|value| usize::try_from(value).ok())
235            .ok_or_else(|| Error::ResourceLimit("bufferView offset is invalid".into()))?;
236        let stride = view
237            .get("byteStride")
238            .and_then(|value| value.as_u64())
239            .and_then(|value| usize::try_from(value).ok())
240            .unwrap_or(default_stride);
241        if stride < default_stride {
242            return Err(Error::Extension(
243                "bufferView byteStride is too small".into(),
244            ));
245        }
246        Ok((buffer, offset, stride))
247    }
248
249    fn apply_sparse(
250        &self,
251        sparse: &crate::JsonValue,
252        count: usize,
253        layout: AccessorLayout,
254        bytes: &mut [u8],
255    ) -> Result<()> {
256        let sparse_count = sparse
257            .get("count")
258            .and_then(|value| value.as_u64())
259            .and_then(|value| usize::try_from(value).ok())
260            .filter(|value| *value <= count)
261            .ok_or_else(|| Error::Extension("sparse accessor count is invalid".into()))?;
262        let indices = sparse
263            .get("indices")
264            .ok_or_else(|| Error::Extension("sparse accessor indices are missing".into()))?;
265        let index_view = indices
266            .get("bufferView")
267            .and_then(|value| value.as_u64())
268            .and_then(|value| usize::try_from(value).ok())
269            .ok_or_else(|| Error::Extension("sparse indices bufferView is invalid".into()))?;
270        let index_type = indices
271            .get("componentType")
272            .and_then(|value| value.as_u64())
273            .ok_or_else(|| Error::Extension("sparse indices componentType is invalid".into()))?;
274        let index_width = match index_type {
275            5121 => 1,
276            5123 => 2,
277            5125 => 4,
278            _ => {
279                return Err(Error::Extension(
280                    "sparse indices componentType is invalid".into(),
281                ))
282            }
283        };
284        let index_offset = indices
285            .get("byteOffset")
286            .and_then(|value| value.as_u64())
287            .unwrap_or(0);
288        let (index_buffer, index_start, _) =
289            self.buffer_view_layout(index_view, index_offset, index_width)?;
290        let values = sparse
291            .get("values")
292            .ok_or_else(|| Error::Extension("sparse accessor values are missing".into()))?;
293        let value_view = values
294            .get("bufferView")
295            .and_then(|value| value.as_u64())
296            .and_then(|value| usize::try_from(value).ok())
297            .ok_or_else(|| Error::Extension("sparse values bufferView is invalid".into()))?;
298        let value_offset = values
299            .get("byteOffset")
300            .and_then(|value| value.as_u64())
301            .unwrap_or(0);
302        let (value_buffer, value_start, value_stride) =
303            self.buffer_view_layout(value_view, value_offset, layout.source_width)?;
304        let mut previous = None;
305        for entry in 0..sparse_count {
306            let index_start =
307                index_start
308                    .checked_add(entry.checked_mul(index_width).ok_or_else(|| {
309                        Error::ResourceLimit("sparse index offset overflow".into())
310                    })?)
311                    .ok_or_else(|| Error::ResourceLimit("sparse index offset overflow".into()))?;
312            let index_end = index_start
313                .checked_add(index_width)
314                .filter(|end| *end <= index_buffer.len())
315                .ok_or_else(|| Error::Extension("sparse indices are out of bounds".into()))?;
316            let index = match index_type {
317                5121 => index_buffer[index_start] as usize,
318                5123 => {
319                    u16::from_le_bytes(index_buffer[index_start..index_end].try_into().unwrap())
320                        as usize
321                }
322                5125 => {
323                    u32::from_le_bytes(index_buffer[index_start..index_end].try_into().unwrap())
324                        as usize
325                }
326                _ => unreachable!(),
327            };
328            if index >= count || previous.is_some_and(|previous| index <= previous) {
329                return Err(Error::Extension(
330                    "sparse indices must be strictly increasing".into(),
331                ));
332            }
333            previous = Some(index);
334            let value_start =
335                value_start
336                    .checked_add(entry.checked_mul(value_stride).ok_or_else(|| {
337                        Error::ResourceLimit("sparse value offset overflow".into())
338                    })?)
339                    .ok_or_else(|| Error::ResourceLimit("sparse value offset overflow".into()))?;
340            let destination_start = index
341                .checked_mul(layout.tight_width)
342                .ok_or_else(|| Error::ResourceLimit("sparse value offset overflow".into()))?;
343            let destination_end = destination_start
344                .checked_add(layout.tight_width)
345                .ok_or_else(|| Error::ResourceLimit("sparse value offset overflow".into()))?;
346            copy_accessor_element_into(
347                value_buffer,
348                value_start,
349                layout,
350                &mut bytes[destination_start..destination_end],
351            )?;
352        }
353        Ok(())
354    }
355
356    /// Reads and materializes one accessor by zero-based index.
357    #[cfg(feature = "accessors")]
358    pub fn read_accessor(&self, index: usize) -> Result<AccessorData> {
359        self.read_accessor_inner::<true>(index)
360    }
361
362    pub(crate) fn read_geometry_accessor(&self, index: usize) -> Result<AccessorData> {
363        self.read_accessor_inner::<false>(index)
364    }
365
366    fn read_accessor_inner<const MATRICES: bool>(&self, index: usize) -> Result<AccessorData> {
367        let (count, components, component_type, data_type, normalized, bytes) =
368            self.read::<MATRICES>(index)?;
369        let accessor_type = self
370            .document
371            .as_value()
372            .get("accessors")
373            .and_then(|value| value.as_array())
374            .and_then(|values| values.get(index))
375            .and_then(|value| value.get("type"))
376            .and_then(|value| value.as_str())
377            .ok_or_else(|| Error::Extension("accessor type is invalid".into()))?
378            .to_owned();
379        Ok(AccessorData {
380            count,
381            accessor_type,
382            components,
383            component_type,
384            data_type,
385            normalized,
386            bytes,
387        })
388    }
389}
390
391fn accessor_layout<const MATRICES: bool>(
392    accessor_type: &str,
393    component_width: usize,
394) -> Result<AccessorLayout> {
395    let (columns, rows) = match accessor_type {
396        "SCALAR" => (1, 1),
397        "VEC2" => (1, 2),
398        "VEC3" => (1, 3),
399        "VEC4" => (1, 4),
400        "MAT2" if MATRICES => (2, 2),
401        "MAT3" if MATRICES => (3, 3),
402        "MAT4" if MATRICES => (4, 4),
403        _ => return Err(Error::Extension("accessor type is invalid".into())),
404    };
405    let column_width = rows * component_width;
406    let column_stride = if columns > 1 && component_width < 4 {
407        column_width
408            .checked_add(3)
409            .map(|width| width & !3)
410            .ok_or_else(|| Error::ResourceLimit("accessor column size overflow".into()))?
411    } else {
412        column_width
413    };
414    Ok(AccessorLayout {
415        tight_width: columns * column_width,
416        source_width: columns * column_stride,
417        columns,
418        column_width,
419        column_stride,
420    })
421}
422
423fn copy_accessor_element(
424    source: &[u8],
425    start: usize,
426    layout: AccessorLayout,
427    destination: &mut Vec<u8>,
428) -> Result<()> {
429    let destination_start = destination.len();
430    destination
431        .try_reserve_exact(layout.tight_width)
432        .map_err(|_| Error::ResourceLimit("accessor materialization allocation failed".into()))?;
433    let destination_end = destination_start
434        .checked_add(layout.tight_width)
435        .ok_or_else(|| Error::ResourceLimit("accessor materialization size overflow".into()))?;
436    destination.resize(destination_end, 0);
437    copy_accessor_element_into(source, start, layout, &mut destination[destination_start..])
438}
439
440fn copy_accessor_element_into(
441    source: &[u8],
442    start: usize,
443    layout: AccessorLayout,
444    destination: &mut [u8],
445) -> Result<()> {
446    if destination.len() != layout.tight_width {
447        return Err(Error::ResourceLimit(
448            "accessor materialization size mismatch".into(),
449        ));
450    }
451    for column in 0..layout.columns {
452        let column_start =
453            start
454                .checked_add(column.checked_mul(layout.column_stride).ok_or_else(|| {
455                    Error::ResourceLimit("accessor column offset overflow".into())
456                })?)
457                .ok_or_else(|| Error::ResourceLimit("accessor column offset overflow".into()))?;
458        let column_end = column_start
459            .checked_add(layout.column_width)
460            .filter(|end| *end <= source.len())
461            .ok_or_else(|| Error::Extension("accessor range is out of bounds".into()))?;
462        let destination_start = column * layout.column_width;
463        let destination_end = destination_start + layout.column_width;
464        destination[destination_start..destination_end]
465            .copy_from_slice(&source[column_start..column_end]);
466    }
467    Ok(())
468}
469impl AccessorSource for DocumentAccessorSource<'_> {
470    fn read_attribute(
471        &self,
472        index: usize,
473        expected: &[&str],
474        allowed: &[u32],
475    ) -> std::result::Result<DecodedAccessor, GltfError> {
476        let (count, c, _, t, n, b) = self
477            .read::<false>(index)
478            .map_err(|e| GltfError::InvalidGltf(e.to_string()))?;
479        let a = &self.document.as_value()["accessors"][index];
480        if !expected.contains(&a.get("type").and_then(|v| v.as_str()).unwrap_or(""))
481            || !allowed
482                .contains(&(a.get("componentType").and_then(|v| v.as_u64()).unwrap_or(0) as u32))
483        {
484            return Err(GltfError::Unsupported(
485                "accessor layout is not permitted".into(),
486            ));
487        };
488        DecodedAccessor::new(count, c, t, n, b)
489    }
490    fn read_indices(&self, index: usize) -> std::result::Result<Vec<u32>, GltfError> {
491        let (count, c, component_type, t, _, b) = self
492            .read::<false>(index)
493            .map_err(|e| GltfError::InvalidGltf(e.to_string()))?;
494        if c != 1 {
495            return Err(GltfError::InvalidGltf("indices must be SCALAR".into()));
496        }
497        if !matches!(component_type, 5121 | 5123 | 5125) {
498            return Err(GltfError::Unsupported("index component type".into()));
499        }
500        let w = t.byte_length();
501        (0..count)
502            .map(|i| {
503                let s = &b[i * w..(i + 1) * w];
504                Ok(match t {
505                    DataType::Uint8 => s[0] as u32,
506                    DataType::Uint16 => u16::from_le_bytes([s[0], s[1]]) as u32,
507                    DataType::Uint32 => u32::from_le_bytes([s[0], s[1], s[2], s[3]]),
508                    _ => return Err(GltfError::Unsupported("index component type".into())),
509                })
510            })
511            .collect()
512    }
513}