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
145struct PlyPayload<'a> {
146 vertex_bytes: &'a [u8],
147 face_bytes: &'a [u8],
148 payload_end: usize,
149}
150
151impl PlyVertex {
152 const fn is_finite(self) -> bool {
153 self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
154 }
155}
156
157pub fn parse_binary_ply(input: &[u8]) -> Result<PlyBinaryMesh<'_>, PlyError> {
166 let (header, payload_start) = split_header(input)?;
167 let spec = parse_header(header)?;
168 let payload = split_payload(input, payload_start, spec.vertex_count, spec.face_count)?;
169 validate_vertices(payload.vertex_bytes, spec.vertex_count)?;
170 validate_faces(payload.face_bytes, spec.face_count, spec.vertex_count)?;
171 if input.len() != payload.payload_end {
172 return Err(PlyError::TrailingData);
173 }
174
175 Ok(PlyBinaryMesh {
176 vertex_bytes: payload.vertex_bytes,
177 face_bytes: payload.face_bytes,
178 vertex_count: spec.vertex_count,
179 face_count: spec.face_count,
180 })
181}
182
183fn split_payload(
184 input: &[u8],
185 payload_start: usize,
186 vertex_count: usize,
187 face_count: usize,
188) -> Result<PlyPayload<'_>, PlyError> {
189 let vertex_len = checked_offset(vertex_count, VERTEX_STRIDE)?;
190 let face_len = checked_offset(face_count, TRIANGLE_FACE_STRIDE)?;
191 let face_start = payload_start.checked_add(vertex_len).ok_or(PlyError::IntegerOverflow)?;
192 let payload_end = face_start.checked_add(face_len).ok_or(PlyError::IntegerOverflow)?;
193 if input.len() < payload_end {
194 return Err(PlyError::PayloadTooShort);
195 }
196
197 let vertex_bytes = take(input, payload_start, vertex_len)?;
198 let face_bytes = take(input, face_start, face_len)?;
199 Ok(PlyPayload { vertex_bytes, face_bytes, payload_end })
200}
201
202fn split_header(input: &[u8]) -> Result<(&str, usize), PlyError> {
203 let (header_end, payload_start) = header_bounds(input)?;
204 let header = input.get(..header_end).ok_or(PlyError::MissingHeaderEnd)?;
205 str::from_utf8(header).map(|header| (header, payload_start)).map_err(|_| PlyError::HeaderUtf8)
206}
207
208fn header_bounds(input: &[u8]) -> Result<(usize, usize), PlyError> {
209 let mut line_start = 0;
210 while line_start < input.len() {
211 let tail = input.get(line_start..).ok_or(PlyError::MissingHeaderEnd)?;
212 let Some(relative_newline) = tail.iter().position(|byte| *byte == b'\n') else {
213 return Err(PlyError::MissingHeaderEnd);
214 };
215 let line_end = line_start + relative_newline;
216 let previous = line_end.checked_sub(1).and_then(|index| input.get(index)).copied();
217 let content_end =
218 if line_end > line_start && previous == Some(b'\r') { line_end - 1 } else { line_end };
219 if input.get(line_start..content_end) == Some(b"end_header".as_slice()) {
220 return Ok((line_start, line_end + 1));
221 }
222 line_start = line_end + 1;
223 }
224
225 Err(PlyError::MissingHeaderEnd)
226}
227
228fn checked_offset(index: usize, stride: usize) -> Result<usize, PlyError> {
229 index.checked_mul(stride).ok_or(PlyError::IntegerOverflow)
230}
231
232fn take(input: &[u8], start: usize, len: usize) -> Result<&[u8], PlyError> {
233 let end = start.checked_add(len).ok_or(PlyError::IntegerOverflow)?;
234 input.get(start..end).ok_or(PlyError::PayloadTooShort)
235}
236
237fn take_array<const N: usize>(input: &[u8], start: usize) -> Result<[u8; N], PlyError> {
238 let slice = take(input, start, N)?;
239 let mut bytes = [0_u8; N];
240 bytes.copy_from_slice(slice);
241 Ok(bytes)
242}
243
244fn validate_vertices(vertex_bytes: &[u8], count: usize) -> Result<(), PlyError> {
245 for index in 0..count {
246 let start = checked_offset(index, VERTEX_STRIDE)?;
247 read_vertex(take(vertex_bytes, start, VERTEX_STRIDE)?)?;
248 }
249 Ok(())
250}
251
252fn validate_faces(face_bytes: &[u8], count: usize, vertex_count: usize) -> Result<(), PlyError> {
253 for index in 0..count {
254 let start = checked_offset(index, TRIANGLE_FACE_STRIDE)?;
255 let triangle = read_triangle(take(face_bytes, start, TRIANGLE_FACE_STRIDE)?)?;
256 validate_triangle_bounds(triangle, vertex_count)?;
257 }
258 Ok(())
259}
260
261fn read_vertex(bytes: &[u8]) -> Result<PlyVertex, PlyError> {
262 let vertex = PlyVertex {
263 x: f32::from_le_bytes(take_array(bytes, 0)?),
264 y: f32::from_le_bytes(take_array(bytes, 4)?),
265 z: f32::from_le_bytes(take_array(bytes, 8)?),
266 };
267 if vertex.is_finite() {
268 Ok(vertex)
269 } else {
270 Err(PlyError::NonFiniteVertex)
271 }
272}
273
274fn read_triangle(bytes: &[u8]) -> Result<Triangle32, PlyError> {
275 if take(bytes, 0, 1)?.first().copied() != Some(3) {
276 return Err(PlyError::NonTriangularFace);
277 }
278 Ok(Triangle32::new(read_index(bytes, 1)?, read_index(bytes, 5)?, read_index(bytes, 9)?))
279}
280
281fn read_index(bytes: &[u8], start: usize) -> Result<u32, PlyError> {
282 let value = i32::from_le_bytes(take_array(bytes, start)?);
283 u32::try_from(value).map_err(|_| PlyError::NegativeIndex)
284}
285
286fn validate_triangle_bounds(triangle: Triangle32, vertex_count: usize) -> Result<(), PlyError> {
287 if index_is_valid(triangle.v0, vertex_count)
288 && index_is_valid(triangle.v1, vertex_count)
289 && index_is_valid(triangle.v2, vertex_count)
290 {
291 Ok(())
292 } else {
293 Err(PlyError::IndexOutOfBounds)
294 }
295}
296
297fn index_is_valid(index: u32, vertex_count: usize) -> bool {
298 usize::try_from(index).is_ok_and(|index| index < vertex_count)
299}