bunny-codec 0.6.0

Zero-copy mesh format adapters for Bunny.
Documentation
use super::PlyError;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct HeaderSpec {
    pub(super) vertex_count: usize,
    pub(super) face_count: usize,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum HeaderSection {
    #[default]
    None,
    Vertex,
    Face,
    IgnoredEmptyElement,
}

#[derive(Default)]
struct HeaderParseState {
    format_seen: bool,
    section: HeaderSection,
    vertex_count: Option<usize>,
    face_count: Option<usize>,
    vertex_props: u8,
    face_property_seen: bool,
}

pub(super) fn parse_header(header: &str) -> Result<HeaderSpec, PlyError> {
    let mut lines = header.lines();
    if lines.next() != Some("ply") {
        return Err(PlyError::InvalidMagic);
    }

    let mut state = HeaderParseState::default();
    for line in lines {
        parse_header_line(line.trim_end_matches('\r'), &mut state)?;
    }
    state.finish()
}

impl HeaderParseState {
    fn finish(self) -> Result<HeaderSpec, PlyError> {
        if !self.format_seen {
            return Err(PlyError::UnsupportedFormat);
        }
        if self.vertex_props != 3 {
            return Err(PlyError::MissingVertexElement);
        }
        if !self.face_property_seen {
            return Err(PlyError::MissingFaceElement);
        }
        Ok(HeaderSpec {
            vertex_count: self.vertex_count.ok_or(PlyError::MissingVertexElement)?,
            face_count: self.face_count.ok_or(PlyError::MissingFaceElement)?,
        })
    }
}

fn parse_header_line(line: &str, state: &mut HeaderParseState) -> Result<(), PlyError> {
    if line == "comment"
        || line.starts_with("comment ")
        || line == "obj_info"
        || line.starts_with("obj_info ")
        || line.is_empty()
    {
        return Ok(());
    }
    let mut parts = line.split_whitespace();
    match parts.next() {
        Some("format") => parse_format(&mut parts, state),
        Some("element") => parse_element(&mut parts, state),
        Some("property") => parse_property(&mut parts, state),
        Some(_) | None => Err(PlyError::UnsupportedProperty),
    }
}

fn parse_format<'a>(
    parts: &mut impl Iterator<Item = &'a str>,
    state: &mut HeaderParseState,
) -> Result<(), PlyError> {
    if state.format_seen || state.section != HeaderSection::None {
        return Err(PlyError::UnsupportedFormat);
    }
    let valid = parts.next() == Some("binary_little_endian")
        && parts.next() == Some("1.0")
        && parts.next().is_none();
    if valid {
        state.format_seen = true;
        Ok(())
    } else {
        Err(PlyError::UnsupportedFormat)
    }
}

fn parse_element<'a>(
    parts: &mut impl Iterator<Item = &'a str>,
    state: &mut HeaderParseState,
) -> Result<(), PlyError> {
    if !state.format_seen {
        return Err(PlyError::UnsupportedFormat);
    }
    let name = parts.next().ok_or(PlyError::UnsupportedElement)?;
    let count = parts
        .next()
        .ok_or(PlyError::InvalidCount)?
        .parse::<usize>()
        .map_err(|_| PlyError::InvalidCount)?;
    if parts.next().is_some() {
        return Err(PlyError::UnsupportedElement);
    }
    match name {
        "vertex" => set_vertex_element(state, count),
        "face" => set_face_element(state, count),
        _ if count == 0 => {
            state.section = HeaderSection::IgnoredEmptyElement;
            Ok(())
        }
        _ => Err(PlyError::UnsupportedElement),
    }
}

const fn set_vertex_element(state: &mut HeaderParseState, count: usize) -> Result<(), PlyError> {
    if state.vertex_count.is_some() || state.face_count.is_some() {
        return Err(PlyError::UnsupportedElement);
    }
    state.vertex_count = Some(count);
    state.vertex_props = 0;
    state.section = HeaderSection::Vertex;
    Ok(())
}

const fn set_face_element(state: &mut HeaderParseState, count: usize) -> Result<(), PlyError> {
    if state.vertex_count.is_none() || state.vertex_props != 3 {
        return Err(PlyError::MissingVertexElement);
    }
    if state.face_count.is_some() {
        return Err(PlyError::UnsupportedElement);
    }
    state.face_count = Some(count);
    state.face_property_seen = false;
    state.section = HeaderSection::Face;
    Ok(())
}

fn parse_property<'a>(
    parts: &mut impl Iterator<Item = &'a str>,
    state: &mut HeaderParseState,
) -> Result<(), PlyError> {
    match state.section {
        HeaderSection::Vertex => parse_vertex_property(parts, state),
        HeaderSection::Face => parse_face_property(parts, state),
        HeaderSection::IgnoredEmptyElement => Ok(()),
        HeaderSection::None => Err(PlyError::UnsupportedProperty),
    }
}

fn parse_vertex_property<'a>(
    parts: &mut impl Iterator<Item = &'a str>,
    state: &mut HeaderParseState,
) -> Result<(), PlyError> {
    let expected = match state.vertex_props {
        0 => Some("x"),
        1 => Some("y"),
        2 => Some("z"),
        _ => None,
    };
    if parts.next() == Some("float") && parts.next() == expected && parts.next().is_none() {
        state.vertex_props += 1;
        Ok(())
    } else {
        Err(PlyError::UnsupportedProperty)
    }
}

fn parse_face_property<'a>(
    parts: &mut impl Iterator<Item = &'a str>,
    state: &mut HeaderParseState,
) -> Result<(), PlyError> {
    let valid = parts.next() == Some("list")
        && parts.next() == Some("uchar")
        && parts.next() == Some("int")
        && parts.next() == Some("vertex_indices")
        && parts.next().is_none();
    if valid && !state.face_property_seen {
        state.face_property_seen = true;
        Ok(())
    } else {
        Err(PlyError::UnsupportedProperty)
    }
}