scena 1.1.0

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
use std::collections::BTreeMap;

use ::gltf::buffer::View;

#[cfg(feature = "meshopt")]
use crate::diagnostics::AssetError;

#[cfg(feature = "meshopt")]
use super::AssetPath;

#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct ResolvedGltfBuffers {
    raw_buffers: Vec<Vec<u8>>,
    accessor_buffers: Vec<Vec<u8>>,
    decompressed_views: BTreeMap<usize, Vec<u8>>,
}

impl ResolvedGltfBuffers {
    pub(super) fn new(raw_buffers: Vec<Vec<u8>>) -> Self {
        Self {
            accessor_buffers: raw_buffers.clone(),
            raw_buffers,
            decompressed_views: BTreeMap::new(),
        }
    }

    pub(super) fn reader_buffer(&self, buffer_index: usize) -> Option<&[u8]> {
        self.accessor_buffers.get(buffer_index).map(Vec::as_slice)
    }

    pub(super) fn raw_buffer(&self, buffer_index: usize) -> Option<&[u8]> {
        self.raw_buffers.get(buffer_index).map(Vec::as_slice)
    }

    pub(super) fn view_bytes(&self, view: &View<'_>) -> Option<&[u8]> {
        if let Some(decoded) = self.decompressed_views.get(&view.index()) {
            return Some(decoded.as_slice());
        }
        let buffer = self.raw_buffer(view.buffer().index())?;
        let start = view.offset();
        let end = start.checked_add(view.length())?;
        buffer.get(start..end)
    }

    #[cfg(feature = "meshopt")]
    pub(super) fn store_decompressed_view(
        &mut self,
        path: &AssetPath,
        view: &View<'_>,
        decoded: Vec<u8>,
    ) -> Result<(), AssetError> {
        if decoded.len() != view.length() {
            return Err(AssetError::Parse {
                path: path.as_str().to_string(),
                reason: format!(
                    "decompressed bufferView {} has {} byte(s), expected {}",
                    view.index(),
                    decoded.len(),
                    view.length()
                ),
            });
        }
        let destination_buffer_index = view.buffer().index();
        let destination_start = view.offset();
        let destination_end = destination_start
            .checked_add(view.length())
            .ok_or_else(|| AssetError::Parse {
                path: path.as_str().to_string(),
                reason: format!("decoded bufferView {} byte range overflowed", view.index()),
            })?;
        let Some(destination) = self.accessor_buffers.get_mut(destination_buffer_index) else {
            return Err(AssetError::Parse {
                path: path.as_str().to_string(),
                reason: format!(
                    "decoded bufferView {} targets missing buffer {destination_buffer_index}",
                    view.index()
                ),
            });
        };
        if destination.len() < destination_end {
            destination.resize(destination_end, 0);
        }
        destination[destination_start..destination_end].copy_from_slice(&decoded);
        self.decompressed_views.insert(view.index(), decoded);
        Ok(())
    }
}