1use std::fmt;
2use std::str;
3
4use bunny_mesh::Triangle32;
5
6mod header;
7
8use header::parse_header;
9
10const VERTEX_STRIDE: usize = 12;
11const TRIANGLE_FACE_STRIDE: usize = 13;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum PlyError {
16 MissingHeaderEnd,
18 HeaderUtf8,
20 InvalidMagic,
22 UnsupportedFormat,
24 InvalidCount,
26 MissingVertexElement,
28 MissingFaceElement,
30 UnsupportedElement,
32 UnsupportedProperty,
34 PayloadTooShort,
36 TrailingData,
38 NonTriangularFace,
40 NegativeIndex,
42 IndexOutOfBounds,
44 NonFiniteVertex,
46 IntegerOverflow,
48}
49
50impl fmt::Display for PlyError {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 let message = match self {
53 Self::MissingHeaderEnd => "PLY header terminator was not found",
54 Self::HeaderUtf8 => "PLY header is not valid UTF-8",
55 Self::InvalidMagic => "PLY header does not start with ply",
56 Self::UnsupportedFormat => "PLY format must be binary_little_endian 1.0",
57 Self::InvalidCount => "PLY element count is invalid",
58 Self::MissingVertexElement => "PLY vertex element is missing or incomplete",
59 Self::MissingFaceElement => "PLY face element is missing or incomplete",
60 Self::UnsupportedElement => "PLY declares an unsupported non-empty element",
61 Self::UnsupportedProperty => "PLY property layout is unsupported",
62 Self::PayloadTooShort => "PLY binary payload is shorter than declared",
63 Self::TrailingData => "PLY binary payload has trailing bytes",
64 Self::NonTriangularFace => "PLY face list entry is not a triangle",
65 Self::NegativeIndex => "PLY face index is negative",
66 Self::IndexOutOfBounds => "PLY face index is out of bounds",
67 Self::NonFiniteVertex => "PLY vertex coordinate is not finite",
68 Self::IntegerOverflow => "PLY count or offset overflowed usize",
69 };
70 f.write_str(message)
71 }
72}
73
74impl std::error::Error for PlyError {}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub struct PlyBinaryMesh<'a> {
79 vertex_bytes: &'a [u8],
80 face_bytes: &'a [u8],
81 vertex_count: usize,
82 face_count: usize,
83}
84
85impl<'a> PlyBinaryMesh<'a> {
86 #[must_use]
88 pub const fn vertex_count(self) -> usize {
89 self.vertex_count
90 }
91
92 #[must_use]
94 pub const fn face_count(self) -> usize {
95 self.face_count
96 }
97
98 #[must_use]
100 pub const fn vertex_bytes(self) -> &'a [u8] {
101 self.vertex_bytes
102 }
103
104 #[must_use]
106 pub const fn face_bytes(self) -> &'a [u8] {
107 self.face_bytes
108 }
109
110 pub fn vertex(self, index: usize) -> Result<PlyVertex, PlyError> {
115 let start = checked_offset(index, VERTEX_STRIDE)?;
116 let bytes = take(self.vertex_bytes, start, VERTEX_STRIDE)?;
117 read_vertex(bytes)
118 }
119
120 pub fn triangle(self, index: usize) -> Result<Triangle32, PlyError> {
126 let start = checked_offset(index, TRIANGLE_FACE_STRIDE)?;
127 let bytes = take(self.face_bytes, start, TRIANGLE_FACE_STRIDE)?;
128 let triangle = read_triangle(bytes)?;
129 validate_triangle_bounds(triangle, self.vertex_count)?;
130 Ok(triangle)
131 }
132}
133
134#[derive(Clone, Copy, Debug, PartialEq)]
136pub struct PlyVertex {
137 pub x: f32,
139 pub y: f32,
141 pub z: f32,
143}
144
145impl PlyVertex {
146 const fn is_finite(self) -> bool {
147 self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
148 }
149}
150
151pub fn parse_binary_ply(input: &[u8]) -> Result<PlyBinaryMesh<'_>, PlyError> {
160 let (header, payload_start) = split_header(input)?;
161 let spec = parse_header(header)?;
162 let vertex_len = checked_offset(spec.vertex_count, VERTEX_STRIDE)?;
163 let face_len = checked_offset(spec.face_count, TRIANGLE_FACE_STRIDE)?;
164 let face_start = payload_start
165 .checked_add(vertex_len)
166 .ok_or(PlyError::IntegerOverflow)?;
167 let payload_end = face_start
168 .checked_add(face_len)
169 .ok_or(PlyError::IntegerOverflow)?;
170 if input.len() < payload_end {
171 return Err(PlyError::PayloadTooShort);
172 }
173
174 let vertex_bytes = take(input, payload_start, vertex_len)?;
175 let face_bytes = take(input, face_start, face_len)?;
176 validate_vertices(vertex_bytes, spec.vertex_count)?;
177 validate_faces(face_bytes, spec.face_count, spec.vertex_count)?;
178 if input.len() != payload_end {
179 return Err(PlyError::TrailingData);
180 }
181
182 Ok(PlyBinaryMesh {
183 vertex_bytes,
184 face_bytes,
185 vertex_count: spec.vertex_count,
186 face_count: spec.face_count,
187 })
188}
189
190fn split_header(input: &[u8]) -> Result<(&str, usize), PlyError> {
191 let (header_end, payload_start) = header_bounds(input)?;
192 let header = input.get(..header_end).ok_or(PlyError::MissingHeaderEnd)?;
193 str::from_utf8(header)
194 .map(|header| (header, payload_start))
195 .map_err(|_| PlyError::HeaderUtf8)
196}
197
198fn header_bounds(input: &[u8]) -> Result<(usize, usize), PlyError> {
199 let mut line_start = 0;
200 while line_start < input.len() {
201 let Some(relative_newline) = input[line_start..].iter().position(|byte| *byte == b'\n')
202 else {
203 return Err(PlyError::MissingHeaderEnd);
204 };
205 let line_end = line_start + relative_newline;
206 let content_end = if line_end > line_start && input[line_end - 1] == b'\r' {
207 line_end - 1
208 } else {
209 line_end
210 };
211 if input.get(line_start..content_end) == Some(b"end_header".as_slice()) {
212 return Ok((line_start, line_end + 1));
213 }
214 line_start = line_end + 1;
215 }
216
217 Err(PlyError::MissingHeaderEnd)
218}
219
220fn checked_offset(index: usize, stride: usize) -> Result<usize, PlyError> {
221 index.checked_mul(stride).ok_or(PlyError::IntegerOverflow)
222}
223
224fn take(input: &[u8], start: usize, len: usize) -> Result<&[u8], PlyError> {
225 let end = start.checked_add(len).ok_or(PlyError::IntegerOverflow)?;
226 input.get(start..end).ok_or(PlyError::PayloadTooShort)
227}
228
229fn take_array<const N: usize>(input: &[u8], start: usize) -> Result<[u8; N], PlyError> {
230 let slice = take(input, start, N)?;
231 let mut bytes = [0_u8; N];
232 bytes.copy_from_slice(slice);
233 Ok(bytes)
234}
235
236fn validate_vertices(vertex_bytes: &[u8], count: usize) -> Result<(), PlyError> {
237 for index in 0..count {
238 let start = checked_offset(index, VERTEX_STRIDE)?;
239 read_vertex(take(vertex_bytes, start, VERTEX_STRIDE)?)?;
240 }
241 Ok(())
242}
243
244fn validate_faces(face_bytes: &[u8], count: usize, vertex_count: usize) -> Result<(), PlyError> {
245 for index in 0..count {
246 let start = checked_offset(index, TRIANGLE_FACE_STRIDE)?;
247 let triangle = read_triangle(take(face_bytes, start, TRIANGLE_FACE_STRIDE)?)?;
248 validate_triangle_bounds(triangle, vertex_count)?;
249 }
250 Ok(())
251}
252
253fn read_vertex(bytes: &[u8]) -> Result<PlyVertex, PlyError> {
254 let vertex = PlyVertex {
255 x: f32::from_le_bytes(take_array(bytes, 0)?),
256 y: f32::from_le_bytes(take_array(bytes, 4)?),
257 z: f32::from_le_bytes(take_array(bytes, 8)?),
258 };
259 if vertex.is_finite() {
260 Ok(vertex)
261 } else {
262 Err(PlyError::NonFiniteVertex)
263 }
264}
265
266fn read_triangle(bytes: &[u8]) -> Result<Triangle32, PlyError> {
267 if take(bytes, 0, 1)?.first().copied() != Some(3) {
268 return Err(PlyError::NonTriangularFace);
269 }
270 Ok(Triangle32::new(
271 read_index(bytes, 1)?,
272 read_index(bytes, 5)?,
273 read_index(bytes, 9)?,
274 ))
275}
276
277fn read_index(bytes: &[u8], start: usize) -> Result<u32, PlyError> {
278 let value = i32::from_le_bytes(take_array(bytes, start)?);
279 u32::try_from(value).map_err(|_| PlyError::NegativeIndex)
280}
281
282fn validate_triangle_bounds(triangle: Triangle32, vertex_count: usize) -> Result<(), PlyError> {
283 if index_is_valid(triangle.v0, vertex_count)
284 && index_is_valid(triangle.v1, vertex_count)
285 && index_is_valid(triangle.v2, vertex_count)
286 {
287 Ok(())
288 } else {
289 Err(PlyError::IndexOutOfBounds)
290 }
291}
292
293fn index_is_valid(index: u32, vertex_count: usize) -> bool {
294 usize::try_from(index).is_ok_and(|index| index < vertex_count)
295}