use std::fmt;
use std::str;
use bunny_mesh::Triangle32;
mod header;
use header::parse_header;
const VERTEX_STRIDE: usize = 12;
const TRIANGLE_FACE_STRIDE: usize = 13;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PlyError {
MissingHeaderEnd,
HeaderUtf8,
InvalidMagic,
UnsupportedFormat,
InvalidCount,
MissingVertexElement,
MissingFaceElement,
UnsupportedElement,
UnsupportedProperty,
PayloadTooShort,
TrailingData,
NonTriangularFace,
NegativeIndex,
IndexOutOfBounds,
NonFiniteVertex,
IntegerOverflow,
}
impl fmt::Display for PlyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self {
Self::MissingHeaderEnd => "PLY header terminator was not found",
Self::HeaderUtf8 => "PLY header is not valid UTF-8",
Self::InvalidMagic => "PLY header does not start with ply",
Self::UnsupportedFormat => "PLY format must be binary_little_endian 1.0",
Self::InvalidCount => "PLY element count is invalid",
Self::MissingVertexElement => "PLY vertex element is missing or incomplete",
Self::MissingFaceElement => "PLY face element is missing or incomplete",
Self::UnsupportedElement => "PLY declares an unsupported non-empty element",
Self::UnsupportedProperty => "PLY property layout is unsupported",
Self::PayloadTooShort => "PLY binary payload is shorter than declared",
Self::TrailingData => "PLY binary payload has trailing bytes",
Self::NonTriangularFace => "PLY face list entry is not a triangle",
Self::NegativeIndex => "PLY face index is negative",
Self::IndexOutOfBounds => "PLY face index is out of bounds",
Self::NonFiniteVertex => "PLY vertex coordinate is not finite",
Self::IntegerOverflow => "PLY count or offset overflowed usize",
};
f.write_str(message)
}
}
impl std::error::Error for PlyError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PlyBinaryMesh<'a> {
vertex_bytes: &'a [u8],
face_bytes: &'a [u8],
vertex_count: usize,
face_count: usize,
}
impl<'a> PlyBinaryMesh<'a> {
#[must_use]
pub const fn vertex_count(self) -> usize {
self.vertex_count
}
#[must_use]
pub const fn face_count(self) -> usize {
self.face_count
}
#[must_use]
pub const fn vertex_bytes(self) -> &'a [u8] {
self.vertex_bytes
}
#[must_use]
pub const fn face_bytes(self) -> &'a [u8] {
self.face_bytes
}
pub fn vertex(self, index: usize) -> Result<PlyVertex, PlyError> {
let start = checked_offset(index, VERTEX_STRIDE)?;
let bytes = take(self.vertex_bytes, start, VERTEX_STRIDE)?;
read_vertex(bytes)
}
pub fn triangle(self, index: usize) -> Result<Triangle32, PlyError> {
let start = checked_offset(index, TRIANGLE_FACE_STRIDE)?;
let bytes = take(self.face_bytes, start, TRIANGLE_FACE_STRIDE)?;
let triangle = read_triangle(bytes)?;
validate_triangle_bounds(triangle, self.vertex_count)?;
Ok(triangle)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PlyVertex {
pub x: f32,
pub y: f32,
pub z: f32,
}
struct PlyPayload<'a> {
vertex_bytes: &'a [u8],
face_bytes: &'a [u8],
payload_end: usize,
}
impl PlyVertex {
const fn is_finite(self) -> bool {
self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
}
}
pub fn parse_binary_ply(input: &[u8]) -> Result<PlyBinaryMesh<'_>, PlyError> {
let (header, payload_start) = split_header(input)?;
let spec = parse_header(header)?;
let payload = split_payload(input, payload_start, spec.vertex_count, spec.face_count)?;
validate_vertices(payload.vertex_bytes, spec.vertex_count)?;
validate_faces(payload.face_bytes, spec.face_count, spec.vertex_count)?;
if input.len() != payload.payload_end {
return Err(PlyError::TrailingData);
}
Ok(PlyBinaryMesh {
vertex_bytes: payload.vertex_bytes,
face_bytes: payload.face_bytes,
vertex_count: spec.vertex_count,
face_count: spec.face_count,
})
}
fn split_payload(
input: &[u8],
payload_start: usize,
vertex_count: usize,
face_count: usize,
) -> Result<PlyPayload<'_>, PlyError> {
let vertex_len = checked_offset(vertex_count, VERTEX_STRIDE)?;
let face_len = checked_offset(face_count, TRIANGLE_FACE_STRIDE)?;
let face_start = payload_start.checked_add(vertex_len).ok_or(PlyError::IntegerOverflow)?;
let payload_end = face_start.checked_add(face_len).ok_or(PlyError::IntegerOverflow)?;
if input.len() < payload_end {
return Err(PlyError::PayloadTooShort);
}
let vertex_bytes = take(input, payload_start, vertex_len)?;
let face_bytes = take(input, face_start, face_len)?;
Ok(PlyPayload { vertex_bytes, face_bytes, payload_end })
}
fn split_header(input: &[u8]) -> Result<(&str, usize), PlyError> {
let (header_end, payload_start) = header_bounds(input)?;
let header = input.get(..header_end).ok_or(PlyError::MissingHeaderEnd)?;
str::from_utf8(header).map(|header| (header, payload_start)).map_err(|_| PlyError::HeaderUtf8)
}
fn header_bounds(input: &[u8]) -> Result<(usize, usize), PlyError> {
let mut line_start = 0;
while line_start < input.len() {
let tail = input.get(line_start..).ok_or(PlyError::MissingHeaderEnd)?;
let Some(relative_newline) = tail.iter().position(|byte| *byte == b'\n') else {
return Err(PlyError::MissingHeaderEnd);
};
let line_end = line_start + relative_newline;
let previous = line_end.checked_sub(1).and_then(|index| input.get(index)).copied();
let content_end =
if line_end > line_start && previous == Some(b'\r') { line_end - 1 } else { line_end };
if input.get(line_start..content_end) == Some(b"end_header".as_slice()) {
return Ok((line_start, line_end + 1));
}
line_start = line_end + 1;
}
Err(PlyError::MissingHeaderEnd)
}
fn checked_offset(index: usize, stride: usize) -> Result<usize, PlyError> {
index.checked_mul(stride).ok_or(PlyError::IntegerOverflow)
}
fn take(input: &[u8], start: usize, len: usize) -> Result<&[u8], PlyError> {
let end = start.checked_add(len).ok_or(PlyError::IntegerOverflow)?;
input.get(start..end).ok_or(PlyError::PayloadTooShort)
}
fn take_array<const N: usize>(input: &[u8], start: usize) -> Result<[u8; N], PlyError> {
let slice = take(input, start, N)?;
let mut bytes = [0_u8; N];
bytes.copy_from_slice(slice);
Ok(bytes)
}
fn validate_vertices(vertex_bytes: &[u8], count: usize) -> Result<(), PlyError> {
for index in 0..count {
let start = checked_offset(index, VERTEX_STRIDE)?;
read_vertex(take(vertex_bytes, start, VERTEX_STRIDE)?)?;
}
Ok(())
}
fn validate_faces(face_bytes: &[u8], count: usize, vertex_count: usize) -> Result<(), PlyError> {
for index in 0..count {
let start = checked_offset(index, TRIANGLE_FACE_STRIDE)?;
let triangle = read_triangle(take(face_bytes, start, TRIANGLE_FACE_STRIDE)?)?;
validate_triangle_bounds(triangle, vertex_count)?;
}
Ok(())
}
fn read_vertex(bytes: &[u8]) -> Result<PlyVertex, PlyError> {
let vertex = PlyVertex {
x: f32::from_le_bytes(take_array(bytes, 0)?),
y: f32::from_le_bytes(take_array(bytes, 4)?),
z: f32::from_le_bytes(take_array(bytes, 8)?),
};
if vertex.is_finite() {
Ok(vertex)
} else {
Err(PlyError::NonFiniteVertex)
}
}
fn read_triangle(bytes: &[u8]) -> Result<Triangle32, PlyError> {
if take(bytes, 0, 1)?.first().copied() != Some(3) {
return Err(PlyError::NonTriangularFace);
}
Ok(Triangle32::new(read_index(bytes, 1)?, read_index(bytes, 5)?, read_index(bytes, 9)?))
}
fn read_index(bytes: &[u8], start: usize) -> Result<u32, PlyError> {
let value = i32::from_le_bytes(take_array(bytes, start)?);
u32::try_from(value).map_err(|_| PlyError::NegativeIndex)
}
fn validate_triangle_bounds(triangle: Triangle32, vertex_count: usize) -> Result<(), PlyError> {
if index_is_valid(triangle.v0, vertex_count)
&& index_is_valid(triangle.v1, vertex_count)
&& index_is_valid(triangle.v2, vertex_count)
{
Ok(())
} else {
Err(PlyError::IndexOutOfBounds)
}
}
fn index_is_valid(index: u32, vertex_count: usize) -> bool {
usize::try_from(index).is_ok_and(|index| index < vertex_count)
}