1use super::math::V3;
4use super::{Mesh, Model, ParseError, Result};
5
6#[derive(Clone, Copy, PartialEq)]
7enum Ty {
8 I8,
9 U8,
10 I16,
11 U16,
12 I32,
13 U32,
14 F32,
15 F64,
16}
17
18impl Ty {
19 fn from_name(s: &str) -> Option<Ty> {
20 Some(match s {
21 "char" | "int8" => Ty::I8,
22 "uchar" | "uint8" => Ty::U8,
23 "short" | "int16" => Ty::I16,
24 "ushort" | "uint16" => Ty::U16,
25 "int" | "int32" => Ty::I32,
26 "uint" | "uint32" => Ty::U32,
27 "float" | "float32" => Ty::F32,
28 "double" | "float64" => Ty::F64,
29 _ => return None,
30 })
31 }
32
33 fn size(self) -> usize {
34 match self {
35 Ty::I8 | Ty::U8 => 1,
36 Ty::I16 | Ty::U16 => 2,
37 Ty::I32 | Ty::U32 | Ty::F32 => 4,
38 Ty::F64 => 8,
39 }
40 }
41
42 fn color_scale(self) -> f64 {
44 match self {
45 Ty::U8 | Ty::I8 => 255.0,
46 Ty::U16 | Ty::I16 => 65535.0,
47 Ty::U32 | Ty::I32 => 4294967295.0,
48 Ty::F32 | Ty::F64 => 1.0,
49 }
50 }
51}
52
53struct Prop {
54 name: String,
55 ty: Ty,
56 list: Option<Ty>,
58}
59
60struct Element {
61 name: String,
62 count: usize,
63 props: Vec<Prop>,
64}
65
66#[derive(Clone, Copy, PartialEq)]
67enum Format {
68 Ascii,
69 Little,
70 Big,
71}
72
73struct Header {
74 format: Format,
75 elements: Vec<Element>,
76 body: usize,
78}
79
80fn parse_header(bytes: &[u8]) -> Result<Header> {
81 let mut pos = 0;
82 let mut format = None;
83 let mut elements: Vec<Element> = Vec::new();
84 let mut first = true;
85 loop {
86 if pos >= bytes.len() {
87 return Err(ParseError::new("PLY header has no end_header"));
88 }
89 let end = bytes[pos..].iter().position(|&b| b == b'\n').map_or(bytes.len(), |i| pos + i);
90 let line = String::from_utf8_lossy(&bytes[pos..end]).into_owned();
91 pos = (end + 1).min(bytes.len());
92 let mut w = line.split_whitespace();
93 let Some(key) = w.next() else { continue };
94 if first {
95 if key != "ply" {
96 return Err(ParseError::new("not a PLY file"));
97 }
98 first = false;
99 continue;
100 }
101 let bad = || ParseError(format!("bad PLY header line: {}", line.trim()));
102 match key {
103 "format" => {
104 format = Some(match w.next() {
105 Some("ascii") => Format::Ascii,
106 Some("binary_little_endian") => Format::Little,
107 Some("binary_big_endian") => Format::Big,
108 _ => return Err(bad()),
109 });
110 }
111 "element" => {
112 let name = w.next().ok_or_else(bad)?.to_string();
113 let count = w.next().and_then(|c| c.parse().ok()).ok_or_else(bad)?;
114 elements.push(Element { name, count, props: Vec::new() });
115 }
116 "property" => {
117 let el = elements.last_mut().ok_or_else(bad)?;
118 let t = w.next().ok_or_else(bad)?;
119 if t == "list" {
120 let ct = w.next().and_then(Ty::from_name).ok_or_else(bad)?;
121 let it = w.next().and_then(Ty::from_name).ok_or_else(bad)?;
122 let name = w.next().ok_or_else(bad)?.to_string();
123 el.props.push(Prop { name, ty: it, list: Some(ct) });
124 } else {
125 let ty = Ty::from_name(t).ok_or_else(bad)?;
126 let name = w.next().ok_or_else(bad)?.to_string();
127 el.props.push(Prop { name, ty, list: None });
128 }
129 }
130 "end_header" => break,
131 _ => {} }
133 }
134 let format = format.ok_or_else(|| ParseError::new("PLY header has no format"))?;
135 Ok(Header { format, elements, body: pos })
136}
137
138struct Src<'a> {
140 data: &'a [u8],
141 pos: usize,
142 format: Format,
143}
144
145impl Src<'_> {
146 #[inline]
147 fn read(&mut self, ty: Ty) -> Option<f64> {
148 if self.format == Format::Ascii {
149 return self.read_ascii();
150 }
151 let n = ty.size();
152 let s = self.data.get(self.pos..self.pos + n)?;
153 self.pos += n;
154 let big = self.format == Format::Big;
155 macro_rules! num {
156 ($t:ty) => {{
157 let a = s.try_into().ok()?;
158 (if big { <$t>::from_be_bytes(a) } else { <$t>::from_le_bytes(a) }) as f64
159 }};
160 }
161 Some(match ty {
162 Ty::I8 => s[0] as i8 as f64,
163 Ty::U8 => s[0] as f64,
164 Ty::I16 => num!(i16),
165 Ty::U16 => num!(u16),
166 Ty::I32 => num!(i32),
167 Ty::U32 => num!(u32),
168 Ty::F32 => num!(f32),
169 Ty::F64 => num!(f64),
170 })
171 }
172
173 fn read_ascii(&mut self) -> Option<f64> {
174 let d = self.data;
175 let mut p = self.pos;
176 while p < d.len() && d[p].is_ascii_whitespace() {
177 p += 1;
178 }
179 let start = p;
180 while p < d.len() && !d[p].is_ascii_whitespace() {
181 p += 1;
182 }
183 self.pos = p;
184 std::str::from_utf8(&d[start..p]).ok()?.parse().ok()
185 }
186}
187
188fn eof() -> ParseError {
189 ParseError::new("PLY data ends early or is malformed")
190}
191
192pub fn parse(bytes: &[u8]) -> Result<Model> {
193 let h = parse_header(bytes)?;
194 let mut src = Src { data: bytes, pos: h.body, format: h.format };
195 let mut mesh = Mesh::default();
196 let mut have_vertex = false;
197 for el in &h.elements {
198 match el.name.as_str() {
199 "vertex" if !have_vertex => {
200 have_vertex = true;
201 read_vertices(&mut src, el, &mut mesh)?;
202 }
203 "face" => read_faces(&mut src, el, &mut mesh)?,
204 _ => skip_element(&mut src, el)?,
205 }
206 }
207 Model { meshes: vec![mesh] }.finish()
208}
209
210fn skip_element(src: &mut Src, el: &Element) -> Result<()> {
211 if src.format != Format::Ascii && el.props.iter().all(|p| p.list.is_none()) {
212 let row: usize = el.props.iter().map(|p| p.ty.size()).sum();
213 src.pos = row.checked_mul(el.count).and_then(|n| src.pos.checked_add(n)).filter(|&e| e <= src.data.len()).ok_or_else(eof)?;
214 return Ok(());
215 }
216 for _ in 0..el.count {
217 for p in &el.props {
218 skip_prop(src, p)?;
219 }
220 }
221 Ok(())
222}
223
224fn skip_prop(src: &mut Src, p: &Prop) -> Result<()> {
225 match p.list {
226 None => {
227 src.read(p.ty).ok_or_else(eof)?;
228 }
229 Some(ct) => {
230 let n = src.read(ct).ok_or_else(eof)? as usize;
231 if src.format == Format::Ascii {
232 for _ in 0..n {
233 src.read(p.ty).ok_or_else(eof)?;
234 }
235 } else {
236 src.pos = src.pos.saturating_add(n.saturating_mul(p.ty.size()));
237 if src.pos > src.data.len() {
238 return Err(eof());
239 }
240 }
241 }
242 }
243 Ok(())
244}
245
246fn read_vertices(src: &mut Src, el: &Element, mesh: &mut Mesh) -> Result<()> {
247 let find = |n: &str| el.props.iter().position(|p| p.name == n && p.list.is_none());
248 let (Some(x), Some(y), Some(z)) = (find("x"), find("y"), find("z")) else {
249 return Err(ParseError::new("PLY vertices have no x, y, z"));
250 };
251 let normal = find("nx").zip(find("ny")).zip(find("nz")).map(|((a, b), c)| [a, b, c]);
252 let color = find("red").zip(find("green")).zip(find("blue")).map(|((a, b), c)| [a, b, c]);
253 let cap = el.count.min(src.data.len());
254 mesh.positions.reserve(cap);
255 let mut normals: Vec<V3> = Vec::with_capacity(if normal.is_some() { cap } else { 0 });
256 let mut colors: Vec<V3> = Vec::with_capacity(if color.is_some() { cap } else { 0 });
257 let mut row = vec![0.0f64; el.props.len()];
258 for _ in 0..el.count {
259 for (i, p) in el.props.iter().enumerate() {
260 if p.list.is_some() {
261 skip_prop(src, p)?;
262 } else {
263 row[i] = src.read(p.ty).ok_or_else(eof)?;
264 }
265 }
266 mesh.positions.push([row[x] as f32, row[y] as f32, row[z] as f32]);
267 if let Some(n) = normal {
268 normals.push(n.map(|i| row[i] as f32));
269 }
270 if let Some(c) = color {
271 colors.push(c.map(|i| (row[i] / el.props[i].ty.color_scale()) as f32));
272 }
273 }
274 mesh.normals = normal.map(|_| normals);
275 mesh.colors = color.map(|_| colors);
276 Ok(())
277}
278
279fn read_faces(src: &mut Src, el: &Element, mesh: &mut Mesh) -> Result<()> {
280 mesh.indices.reserve(el.count.min(src.data.len()) * 3);
281 let mut poly: Vec<u32> = Vec::new();
282 for _ in 0..el.count {
283 for p in &el.props {
284 let is_idx = p.list.is_some() && (p.name == "vertex_indices" || p.name == "vertex_index");
285 if !is_idx {
286 skip_prop(src, p)?;
287 continue;
288 }
289 let n = src.read(p.list.unwrap_or(Ty::U8)).ok_or_else(eof)? as usize;
290 poly.clear();
291 let mut ok = true;
292 for _ in 0..n {
293 let v = src.read(p.ty).ok_or_else(eof)?;
294 if v < 0.0 || v > u32::MAX as f64 {
295 ok = false;
296 }
297 poly.push(v as u32);
298 }
299 if ok {
300 for k in 1..poly.len().saturating_sub(1) {
301 mesh.indices.extend([poly[0], poly[k], poly[k + 1]]);
302 }
303 }
304 }
305 }
306 Ok(())
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 const SQUARE: &str = "ply\nformat ascii 1.0\ncomment square\nelement vertex 4\nproperty float x\nproperty float y\nproperty float z\n\
314 element face 1\nproperty list uchar int vertex_indices\nend_header\n0 0 0\n1 0 0\n1 1 0\n0 1 0\n4 0 1 2 3\n";
315
316 #[test]
317 fn ascii_quad_is_two_triangles() {
318 let m = parse(SQUARE.as_bytes()).unwrap();
319 assert_eq!(m.meshes.len(), 1);
320 assert_eq!(m.triangle_count(), 2);
321 assert_eq!(m.vertex_count(), 4);
322 assert_eq!(m.meshes[0].indices, vec![0, 1, 2, 0, 2, 3]);
323 assert!(m.meshes[0].colors.is_none() && m.meshes[0].normals.is_none());
324 }
325
326 #[test]
327 fn crlf_header() {
328 let m = parse(SQUARE.replace('\n', "\r\n").as_bytes()).unwrap();
329 assert_eq!(m.triangle_count(), 2);
330 }
331
332 fn binary_with_unknown_element(big: bool) -> Vec<u8> {
333 let fmt = if big { "binary_big_endian" } else { "binary_little_endian" };
334 let mut out = format!(
335 "ply\nformat {fmt} 1.0\nelement vertex 3\nproperty float x\nproperty float y\nproperty float z\nproperty float nx\nproperty float ny\nproperty float nz\n\
336 property uchar red\nproperty uchar green\nproperty uchar blue\nproperty uchar alpha\n\
337 element junk 2\nproperty uchar tag\nproperty list uchar short things\n\
338 element face 1\nproperty list uchar uint vertex_indices\nproperty float quality\nend_header\n"
339 )
340 .into_bytes();
341 let f = |v: f32| if big { v.to_be_bytes() } else { v.to_le_bytes() };
342 let pts = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
343 for (i, p) in pts.iter().enumerate() {
344 for c in p {
345 out.extend(f(*c));
346 }
347 for c in [0.0, 0.0, 1.0] {
348 out.extend(f(c));
349 }
350 out.extend([255, 0, 51 * i as u8, 255]);
351 }
352 out.extend([9, 3, 1, 0, 2, 0, 3, 0]); out.extend([8, 0]); out.push(3);
355 for i in [0u32, 1, 2] {
356 out.extend(if big { i.to_be_bytes() } else { i.to_le_bytes() });
357 }
358 out.extend(f(0.5));
359 out
360 }
361
362 #[test]
363 fn binary_little_endian_colours_normals_and_skipped_element() {
364 let bytes = binary_with_unknown_element(false);
365 let m = parse(&bytes).unwrap();
367 let mesh = &m.meshes[0];
368 assert_eq!(m.triangle_count(), 1);
369 assert_eq!(mesh.positions[1], [1.0, 0.0, 0.0]);
370 assert_eq!(mesh.normals.as_ref().unwrap()[2], [0.0, 0.0, 1.0]);
371 let c = mesh.colors.as_ref().unwrap();
372 assert_eq!(c[0], [1.0, 0.0, 0.0]);
373 assert!((c[2][2] - 102.0 / 255.0).abs() < 1e-6);
374 }
375
376 #[test]
377 fn binary_big_endian() {
378 let mut bytes = binary_with_unknown_element(true);
379 let at = bytes.windows(8).position(|w| w == [9, 3, 1, 0, 2, 0, 3, 0]).unwrap();
381 bytes[at + 1..at + 8].copy_from_slice(&[3, 0, 1, 0, 2, 0, 3]);
382 let m = parse(&bytes).unwrap();
383 assert_eq!(m.triangle_count(), 1);
384 assert_eq!(m.meshes[0].positions[2], [0.0, 1.0, 0.0]);
385 }
386
387 #[test]
388 fn ascii_float_colours() {
389 let src = "ply\nformat ascii 1.0\nelement vertex 3\nproperty float x\nproperty float y\nproperty float z\n\
390 property float red\nproperty float green\nproperty float blue\nelement face 1\nproperty list uchar int vertex_index\nend_header\n\
391 0 0 0 1 0 0\n1 0 0 0 0.5 0\n0 1 0 0 0 1\n3 0 1 2\n";
392 let m = parse(src.as_bytes()).unwrap();
393 let c = m.meshes[0].colors.as_ref().unwrap();
394 assert_eq!(c[0], [1.0, 0.0, 0.0]);
395 assert_eq!(c[1], [0.0, 0.5, 0.0]);
396 }
397
398 #[test]
399 fn point_cloud_is_an_error() {
400 let src = "ply\nformat ascii 1.0\nelement vertex 2\nproperty float x\nproperty float y\nproperty float z\nend_header\n0 0 0\n1 1 1\n";
401 assert!(parse(src.as_bytes()).is_err());
402 }
403
404 #[test]
405 fn bad_input_is_an_error() {
406 assert!(parse(b"hello").is_err());
407 assert!(parse(b"ply\nformat ascii 1.0\nelement vertex 1\n").is_err());
408 let b = binary_with_unknown_element(false);
410 assert!(parse(&b[..b.len() - 10]).is_err());
411 }
412
413 #[test]
414 fn out_of_range_faces_are_dropped() {
415 let src = SQUARE.replace("4 0 1 2 3", "3 0 1 9");
416 assert!(parse(src.as_bytes()).is_err());
417 }
418}