pak 0.7.1

An easy-to-use data pak format for games.
Documentation
use {
    serde::{Deserialize, Serialize},
    std::mem::size_of,
};

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct IndexBuffer {
    #[serde(with = "serde_bytes")]
    buf: Vec<u8>,

    ty: IndexType,
}

impl IndexBuffer {
    pub fn new(indices: &[u32]) -> Self {
        debug_assert!(indices.len() >= 3);
        debug_assert_eq!(indices.len() % 3, 0);

        let max_vertex = indices.iter().copied().max().unwrap_or_default();

        debug_assert!(max_vertex <= u32::MAX as _);

        if max_vertex <= u8::MAX as _ {
            let mut buf = Vec::with_capacity(indices.len() << 1);
            for &idx in indices {
                buf.push(idx as u8);
            }

            Self {
                buf,
                ty: IndexType::U8,
            }
        } else if max_vertex <= u16::MAX as _ {
            let mut buf = Vec::with_capacity(indices.len() << 1);
            for &idx in indices {
                buf.extend_from_slice(&(idx as u16).to_ne_bytes());
            }

            Self {
                buf,
                ty: IndexType::U16,
            }
        } else {
            let mut buf = Vec::with_capacity(indices.len() << 2);
            for &idx in indices {
                buf.extend_from_slice(&idx.to_ne_bytes());
            }

            Self {
                buf,
                ty: IndexType::U32,
            }
        }
    }

    pub fn as_u8(&self) -> Option<Vec<u8>> {
        match self.ty {
            IndexType::U8 => Some(self.buf.iter().copied().map(|idx| idx as _).collect()),
            _ => None,
        }
    }

    pub fn as_u16(&self) -> Option<Vec<u16>> {
        match self.ty {
            IndexType::U8 => Some(self.buf.iter().copied().map(|idx| idx as u16).collect()),
            IndexType::U16 => {
                debug_assert_eq!(self.buf.len() % 2, 0);

                let count = self.buf.len() >> 1;
                let mut res = Vec::with_capacity(count);
                for idx in 0..count {
                    let idx = idx << 1;
                    let data = &self.buf[idx..idx + 2];
                    res.push(u16::from_ne_bytes([data[0], data[1]]));
                }

                Some(res)
            }
            _ => None,
        }
    }

    pub fn as_u32(&self) -> Vec<u32> {
        match self.ty {
            IndexType::U8 => self.buf.iter().copied().map(|idx| idx as _).collect(),
            IndexType::U16 => {
                debug_assert_eq!(self.buf.len() % 2, 0);

                let count = self.buf.len() >> 1;
                let mut res = Vec::with_capacity(count);
                for idx in 0..count {
                    let idx = idx << 1;
                    let data = &self.buf[idx..idx + 2];
                    res.push(u16::from_ne_bytes([data[0], data[1]]) as _);
                }

                res
            }
            IndexType::U32 => {
                debug_assert_eq!(self.buf.len() % 4, 0);

                let count = self.buf.len() >> 2;
                let mut res = Vec::with_capacity(count);
                for idx in 0..count {
                    let idx = idx << 2;
                    let data = &self.buf[idx..idx + 4];
                    res.push(u32::from_ne_bytes([data[0], data[1], data[2], data[3]]));
                }

                res
            }
        }
    }

    pub fn index_count(&self) -> usize {
        match self.ty {
            IndexType::U8 => self.buf.len(),
            IndexType::U16 => self.buf.len() >> 1,
            IndexType::U32 => self.buf.len() >> 2,
        }
    }

    pub fn index_type(&self) -> IndexType {
        self.ty
    }

    pub fn triangle_count(&self) -> usize {
        self.index_count() / 3
    }
}

#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum IndexType {
    U8,
    U16,
    U32,
}

impl IndexType {
    pub fn stride(self) -> usize {
        match self {
            Self::U8 => size_of::<u8>(),
            Self::U16 => size_of::<u16>(),
            Self::U32 => size_of::<u32>(),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::index::{IndexBuffer, IndexType};

    #[test]
    fn index_buffer_u8() {
        let indices = [0, 1, 2, 0, 1, 3];
        let index_buf = IndexBuffer::new(&indices);

        assert_eq!(index_buf.triangle_count(), 2);
        assert_eq!(index_buf.index_count(), 6);
        assert_eq!(index_buf.index_type(), IndexType::U8);

        let buf = index_buf.as_u8().unwrap();

        assert_eq!(buf.len(), 6);
        for (expected, actual) in indices.iter().copied().zip(buf) {
            assert_eq!(expected, actual as u32);
        }

        let buf = index_buf.as_u16().unwrap();

        assert_eq!(buf.len(), 6);
        for (expected, actual) in indices.iter().copied().zip(buf) {
            assert_eq!(expected, actual as u32);
        }

        let buf = index_buf.as_u32();

        assert_eq!(buf.len(), 6);
        for (expected, actual) in indices.iter().copied().zip(buf) {
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn index_buffer_u16() {
        let indices = [0, 1, 42_000, 0, 1, 2];
        let index_buf = IndexBuffer::new(&indices);

        assert_eq!(index_buf.triangle_count(), 2);
        assert_eq!(index_buf.index_count(), 6);
        assert_eq!(index_buf.index_type(), IndexType::U16);
        assert_eq!(index_buf.as_u8(), None);

        let buf = index_buf.as_u16().unwrap();

        assert_eq!(buf.len(), 6);
        for (expected, actual) in indices.iter().copied().zip(buf) {
            assert_eq!(expected, actual as u32);
        }

        let buf = index_buf.as_u32();

        assert_eq!(buf.len(), 6);
        for (expected, actual) in indices.iter().copied().zip(buf) {
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn index_buffer_u32() {
        let indices = [0, 1, 100_000, 0, 1, 2];
        let index_buf = IndexBuffer::new(&indices);

        assert_eq!(index_buf.triangle_count(), 2);
        assert_eq!(index_buf.index_count(), 6);
        assert_eq!(index_buf.index_type(), IndexType::U32);
        assert_eq!(index_buf.as_u8(), None);
        assert_eq!(index_buf.as_u16(), None);

        let buf = index_buf.as_u32();

        assert_eq!(buf.len(), 6);
        for (expected, actual) in indices.iter().copied().zip(buf) {
            assert_eq!(expected, actual);
        }
    }
}