mesh_loader/obj/
mod.rs

1//! [Wavefront OBJ] (.obj) parser.
2//!
3//! [Wavefront OBJ]: https://en.wikipedia.org/wiki/Wavefront_.obj_file
4
5#![allow(clippy::collapsible_if, clippy::many_single_char_names)]
6
7mod error;
8
9use std::{
10    collections::HashMap,
11    io, mem,
12    path::{Path, PathBuf},
13    str,
14};
15
16use self::error::ErrorKind;
17use crate::{
18    common,
19    utils::{
20        bytes::{from_utf8_lossy, memchr_naive, memchr_naive_table, path_from_bytes, starts_with},
21        float, int,
22        utf16::decode_bytes,
23    },
24    Color4, Mesh, Scene, ShadingModel, Vec2, Vec3,
25};
26
27/// Parses meshes from bytes of Wavefront OBJ text.
28pub fn from_slice<B: AsRef<[u8]>, F: FnMut(&Path) -> io::Result<B>>(
29    bytes: &[u8],
30    path: Option<&Path>,
31    mut reader: F,
32) -> io::Result<Scene> {
33    // If it is UTF-16 with BOM, it is converted to UTF-8, otherwise it is parsed as bytes.
34    // We don't require UTF-8 here, as we want to support files that are partially non-UTF-8 like:
35    // https://github.com/assimp/assimp/blob/v5.3.1/test/models/OBJ/regr01.mtl#L67
36    let bytes = &decode_bytes(bytes)?;
37    match read_obj(bytes, path, &mut |path, materials, material_map| {
38        match reader(path) {
39            Ok(bytes) => read_mtl(bytes.as_ref(), Some(path), materials, material_map),
40            // ignore reader error for now
41            // TODO: logging?
42            Err(_e) => Ok(()),
43        }
44    }) {
45        Ok((meshes, materials)) => {
46            let materials = meshes
47                .iter()
48                .map(|m| {
49                    materials
50                        .get(m.material_index as usize)
51                        .cloned()
52                        .unwrap_or_default()
53                })
54                .collect();
55            Ok(Scene { materials, meshes })
56        }
57        Err(e) => Err(e.into_io_error(bytes, path)),
58    }
59}
60
61// -----------------------------------------------------------------------------
62// OBJ
63
64fn read_obj(
65    mut s: &[u8],
66    obj_path: Option<&Path>,
67    reader: &mut dyn FnMut(
68        &Path,
69        &mut Vec<common::Material>,
70        &mut HashMap<Vec<u8>, u32>,
71    ) -> io::Result<()>,
72) -> Result<(Vec<Mesh>, Vec<common::Material>), ErrorKind> {
73    let mut meshes = Vec::with_capacity(1); // TODO: right default capacity?
74
75    // TODO: use with_capacity
76    let mut vertices = vec![];
77    let mut normals = vec![];
78    let mut texcoords = vec![];
79    let mut colors = vec![];
80    let mut face = Vec::with_capacity(3);
81    let mut faces: Vec<Face> = vec![];
82    let mut current_group: &[u8] = b"default";
83    let mut current_material: &[u8] = &[];
84    let mut materials = vec![];
85    let mut material_map = HashMap::default();
86
87    while let Some((&c, s_next)) = s.split_first() {
88        match c {
89            b'v' => {
90                s = s_next;
91                match s.first() {
92                    Some(b' ' | b'\t') => {
93                        skip_spaces(&mut s);
94                        read_v(&mut s, &mut vertices, &mut colors)?;
95                        if !colors.is_empty() && colors.len() < vertices.len() {
96                            colors.resize(vertices.len(), [0.; 3]);
97                        }
98                        continue;
99                    }
100                    Some(b'n') => {
101                        s = &s[1..];
102                        if skip_spaces(&mut s) {
103                            read_vn(&mut s, &mut normals)?;
104                            continue;
105                        }
106                    }
107                    Some(b't') => {
108                        s = &s[1..];
109                        if skip_spaces(&mut s) {
110                            read_vt(&mut s, &mut texcoords)?;
111                            continue;
112                        }
113                    }
114                    // ignore vp or other unknown
115                    _ => {}
116                }
117            }
118            b'f' => {
119                s = s_next;
120                if skip_spaces(&mut s) {
121                    read_f(
122                        &mut s, &mut faces, &mut face, &vertices, &texcoords, &normals,
123                    )?;
124                    continue;
125                }
126            }
127            b'u' => {
128                s = s_next;
129                if token(&mut s, &b"usemtl"[1..]) {
130                    if skip_spaces(&mut s) {
131                        let (name, s_next) = name(s);
132                        if name != current_material {
133                            let material_index = material_map.get(current_material).copied();
134                            push_mesh(
135                                &mut meshes,
136                                &mut faces,
137                                &vertices,
138                                &texcoords,
139                                &normals,
140                                &colors,
141                                current_group,
142                                material_index,
143                            )?;
144                            current_material = name;
145                        }
146                        s = s_next;
147                        continue;
148                    }
149                }
150            }
151            b'm' => {
152                s = s_next;
153                if token(&mut s, &b"mtllib"[1..]) {
154                    if skip_spaces(&mut s) {
155                        let (path, s_next) = name(s);
156                        let path = if path.is_empty() {
157                            None
158                        } else {
159                            path_from_bytes(path).ok()
160                        };
161                        if let Some(path) = path {
162                            match obj_path.and_then(Path::parent) {
163                                Some(parent) => {
164                                    reader(&parent.join(path), &mut materials, &mut material_map)
165                                        .map_err(ErrorKind::Io)?;
166                                }
167                                None => {} // ignored
168                            }
169                        }
170                        s = s_next;
171                        continue;
172                    }
173                }
174                // ignore mg or other unknown
175            }
176            b'g' => {
177                s = s_next;
178                if skip_spaces(&mut s) {
179                    let (mut name, s_next) = name(s);
180                    if name.is_empty() {
181                        name = b"default";
182                    }
183                    if name != current_group {
184                        let material_index = material_map.get(current_material).copied();
185                        push_mesh(
186                            &mut meshes,
187                            &mut faces,
188                            &vertices,
189                            &texcoords,
190                            &normals,
191                            &colors,
192                            current_group,
193                            material_index,
194                        )?;
195                        current_material = &[];
196                        current_group = name;
197                    }
198                    s = s_next;
199                    continue;
200                }
201            }
202            _ => {}
203        }
204        // ignore comment, p, l, s, mg, o, or other unknown
205        skip_any_until_line(&mut s);
206    }
207
208    let material_index = material_map.get(current_material).copied();
209    push_mesh(
210        &mut meshes,
211        &mut faces,
212        &vertices,
213        &texcoords,
214        &normals,
215        &colors,
216        current_group,
217        material_index,
218    )?;
219
220    Ok((meshes, materials))
221}
222
223#[inline(always)]
224fn read_v(
225    s: &mut &[u8],
226    vertices: &mut Vec<Vec3>,
227    colors: &mut Vec<Vec3>,
228) -> Result<(), ErrorKind> {
229    // v <x> <y> <z> ([w] | [<r> <g> <b>])
230    let vertex = read_float3(s, "v")?;
231    let has_space = skip_spaces(s);
232    match s.first() {
233        Some(b'\n' | b'\r') | None => {
234            vertices.push(vertex);
235            *s = s.get(1..).unwrap_or_default();
236            return Ok(());
237        }
238        _ if !has_space => return Err(ErrorKind::ExpectedSpace("v", s.len())),
239        _ => {}
240    }
241    // [w] or [r]
242    let w = match float::parse_partial::<f32>(s) {
243        Some((f, n)) => {
244            *s = &s[n..];
245            f
246        }
247        None => return Err(ErrorKind::Float(s.len())),
248    };
249    let has_space = skip_spaces(s);
250    match s.first() {
251        Some(b'\n' | b'\r') | None => {
252            // is homogeneous vector
253            if w == 0. {
254                return Err(ErrorKind::InvalidW(s.len()));
255            }
256            vertices.push([vertex[0] / w, vertex[1] / w, vertex[2] / w]);
257            *s = s.get(1..).unwrap_or_default();
258            return Ok(());
259        }
260        _ if !has_space => return Err(ErrorKind::ExpectedSpace("v", s.len())),
261        _ => {}
262    }
263    vertices.push(vertex);
264    // is vertex color
265    let r = w;
266    let g = match float::parse_partial::<f32>(s) {
267        Some((f, n)) => {
268            *s = &s[n..];
269            f
270        }
271        None => return Err(ErrorKind::Float(s.len())),
272    };
273    if !skip_spaces(s) {
274        return Err(ErrorKind::ExpectedSpace("v", s.len()));
275    }
276    let b = match float::parse_partial::<f32>(s) {
277        Some((f, n)) => {
278            *s = &s[n..];
279            f
280        }
281        None => return Err(ErrorKind::Float(s.len())),
282    };
283    colors.push([r, g, b]);
284    if !skip_spaces_until_line(s) {
285        return Err(ErrorKind::ExpectedNewline("v", s.len()));
286    }
287    Ok(())
288}
289
290fn read_vn(s: &mut &[u8], normals: &mut Vec<Vec3>) -> Result<(), ErrorKind> {
291    // vn <i> <j> <k>
292    let normal = read_float3(s, "vn")?;
293    normals.push(normal);
294    if !skip_spaces_until_line(s) {
295        return Err(ErrorKind::ExpectedNewline("vn", s.len()));
296    }
297    Ok(())
298}
299
300fn read_vt(s: &mut &[u8], texcoords: &mut Vec<Vec2>) -> Result<(), ErrorKind> {
301    // vt <u> [v=0] [w=0]
302    let mut texcoord = [0.; 2];
303    // <u>
304    match float::parse_partial::<f32>(s) {
305        Some((f, n)) => {
306            texcoord[0] = f;
307            *s = &s[n..];
308        }
309        None => return Err(ErrorKind::Float(s.len())),
310    }
311    let has_space = skip_spaces(s);
312    match s.first() {
313        Some(b'\n' | b'\r') | None => {
314            texcoords.push(texcoord);
315            *s = s.get(1..).unwrap_or_default();
316            return Ok(());
317        }
318        _ if !has_space => return Err(ErrorKind::ExpectedSpace("vt", s.len())),
319        _ => {}
320    }
321    // [v=0]
322    match float::parse_partial::<f32>(s) {
323        Some((f, n)) => {
324            texcoord[1] = f;
325            *s = &s[n..];
326        }
327        None => return Err(ErrorKind::Float(s.len())),
328    }
329    texcoords.push(texcoord);
330    let has_space = skip_spaces(s);
331    match s.first() {
332        Some(b'\n' | b'\r') | None => {
333            *s = s.get(1..).unwrap_or_default();
334            return Ok(());
335        }
336        _ if !has_space => return Err(ErrorKind::ExpectedSpace("vt", s.len())),
337        _ => {}
338    }
339    // [w=0]
340    match float::parse_partial::<f32>(s) {
341        Some((_f, n)) => {
342            // ignored
343            *s = &s[n..];
344        }
345        None => return Err(ErrorKind::Float(s.len())),
346    }
347    if !skip_spaces_until_line(s) {
348        return Err(ErrorKind::ExpectedNewline("vt", s.len()));
349    }
350    Ok(())
351}
352
353fn read_f(
354    s: &mut &[u8],
355    faces: &mut Vec<Face>,
356    face: &mut Vec<[u32; 3]>,
357    vertices: &[Vec3],
358    texcoords: &[Vec2],
359    normals: &[Vec3],
360) -> Result<(), ErrorKind> {
361    // f <v1>/[vt1]/[vn1] <v2>/[vt2]/[vn2] <v3>/[vt3]/[vn3] ...
362    let mut f;
363    match memchr_naive_table(LINE, &TABLE, s) {
364        Some(n) => {
365            f = &s[..n];
366            *s = &s[n + 1..];
367        }
368        None => {
369            f = s;
370            *s = &[];
371        }
372    };
373    while !f.is_empty() {
374        let mut w;
375        let f_next = match memchr_naive_table(SPACE, &TABLE, f) {
376            Some(n) => {
377                w = &f[..n];
378                &f[n + 1..]
379            }
380            None => {
381                w = f;
382                &[]
383            }
384        };
385        let mut idx = [u32::MAX; 3];
386        let mut i;
387        match memchr_naive(b'/', w) {
388            Some(n) => {
389                i = &w[..n];
390                w = &w[n + 1..];
391            }
392            None => {
393                i = w;
394                w = &[];
395            }
396        };
397        match int::parse::<i32>(i) {
398            #[allow(
399                clippy::cast_possible_truncation,
400                clippy::cast_possible_wrap,
401                clippy::cast_sign_loss
402            )]
403            Some(i) => {
404                idx[0] = if i < 0 {
405                    (vertices.len() as isize + i as isize) as u32
406                } else {
407                    (i - 1) as u32
408                }
409            }
410            None => return Err(ErrorKind::Int(s.len() + !s.is_empty() as usize + f.len())),
411        }
412        match memchr_naive(b'/', w) {
413            Some(n) => {
414                i = &w[..n];
415                w = &w[n + 1..];
416            }
417            None => {
418                i = w;
419                w = &[];
420            }
421        };
422        if !i.is_empty() {
423            match int::parse::<i32>(i) {
424                #[allow(
425                    clippy::cast_possible_truncation,
426                    clippy::cast_possible_wrap,
427                    clippy::cast_sign_loss
428                )]
429                Some(i) => {
430                    idx[1] = if i < 0 {
431                        (texcoords.len() as isize + i as isize) as u32
432                    } else {
433                        (i - 1) as u32
434                    }
435                }
436                None => return Err(ErrorKind::Int(s.len() + !s.is_empty() as usize + f.len())),
437            }
438        }
439        i = w;
440        if !i.is_empty() {
441            match int::parse::<i32>(i) {
442                #[allow(
443                    clippy::cast_possible_truncation,
444                    clippy::cast_possible_wrap,
445                    clippy::cast_sign_loss
446                )]
447                Some(i) => {
448                    idx[2] = if i < 0 {
449                        (normals.len() as isize + i as isize) as u32
450                    } else {
451                        (i - 1) as u32
452                    }
453                }
454                None => return Err(ErrorKind::Int(s.len() + !s.is_empty() as usize + f.len())),
455            }
456        }
457        f = f_next;
458        skip_spaces(&mut f);
459        face.push(idx);
460    }
461    match face.len() {
462        1 => {
463            faces.push(Face::Point([face[0]]));
464            face.clear();
465        }
466        2 => {
467            faces.push(Face::Line([face[0], face[1]]));
468            face.clear();
469        }
470        3 => {
471            faces.push(Face::Triangle([face[0], face[1], face[2]]));
472            face.clear();
473        }
474        0 => return Err(ErrorKind::Expected("f", s.len())),
475        // TODO: triangulate in place here?
476        _ => faces.push(Face::Polygon(mem::take(face))),
477    }
478    Ok(())
479}
480
481fn read_float3(s: &mut &[u8], expected: &'static str) -> Result<[f32; 3], ErrorKind> {
482    let mut floats = [0.; 3];
483    match float::parse_partial::<f32>(s) {
484        Some((f, n)) => {
485            floats[0] = f;
486            *s = &s[n..];
487        }
488        None => return Err(ErrorKind::Float(s.len())),
489    }
490    if !skip_spaces(s) {
491        return Err(ErrorKind::ExpectedSpace(expected, s.len()));
492    }
493    match float::parse_partial::<f32>(s) {
494        Some((f, n)) => {
495            floats[1] = f;
496            *s = &s[n..];
497        }
498        None => return Err(ErrorKind::Float(s.len())),
499    }
500    if !skip_spaces(s) {
501        return Err(ErrorKind::ExpectedSpace(expected, s.len()));
502    }
503    match float::parse_partial::<f32>(s) {
504        Some((f, n)) => {
505            floats[2] = f;
506            *s = &s[n..];
507        }
508        None => return Err(ErrorKind::Float(s.len())),
509    }
510    Ok(floats)
511}
512
513fn read_color(s: &mut &[u8], expected: &'static str) -> Result<[f32; 3], ErrorKind> {
514    let mut floats = [0.; 3];
515    // r
516    match float::parse_partial::<f32>(s) {
517        Some((f, n)) => {
518            floats[0] = f;
519            *s = &s[n..];
520        }
521        None => return Err(ErrorKind::Float(s.len())),
522    }
523    let has_space = skip_spaces(s);
524    match s.first() {
525        Some(b'\n' | b'\r') | None => {
526            *s = s.get(1..).unwrap_or_default();
527            return Ok(floats);
528        }
529        _ if !has_space => return Err(ErrorKind::ExpectedSpace(expected, s.len())),
530        _ => {}
531    }
532    // g
533    match float::parse_partial::<f32>(s) {
534        Some((f, n)) => {
535            floats[1] = f;
536            *s = &s[n..];
537        }
538        None => return Err(ErrorKind::Float(s.len())),
539    }
540    if !skip_spaces(s) {
541        return Err(ErrorKind::ExpectedSpace(expected, s.len()));
542    }
543    // b
544    match float::parse_partial::<f32>(s) {
545        Some((f, n)) => {
546            floats[2] = f;
547            *s = &s[n..];
548        }
549        None => return Err(ErrorKind::Float(s.len())),
550    }
551    if !skip_spaces_until_line(s) {
552        return Err(ErrorKind::ExpectedNewline(expected, s.len()));
553    }
554    Ok(floats)
555}
556
557fn read_float1(s: &mut &[u8], expected: &'static str) -> Result<f32, ErrorKind> {
558    match float::parse_partial::<f32>(s) {
559        Some((f, n)) => {
560            *s = &s[n..];
561            if !skip_spaces_until_line(s) {
562                return Err(ErrorKind::ExpectedNewline(expected, s.len()));
563            }
564            Ok(f)
565        }
566        None => Err(ErrorKind::Float(s.len())),
567    }
568}
569
570#[inline(always)]
571fn push_vertex(
572    mesh: &mut Mesh,
573    vert: [u32; 3],
574    vertices: &[Vec3],
575    colors: &[Vec3],
576    texcoords: &[Vec2],
577    normals: &[Vec3],
578) -> Result<(), ErrorKind> {
579    let v = vert[0] as usize;
580    mesh.vertices
581        .push(*vertices.get(v).ok_or(ErrorKind::Oob(v, 0))?);
582    if !texcoords.is_empty() && vert[1] != u32::MAX {
583        let vt = vert[1] as usize;
584        mesh.texcoords[0].push(*texcoords.get(vt).ok_or(ErrorKind::Oob(vt, 0))?);
585    }
586    if !normals.is_empty() && vert[2] != u32::MAX {
587        let vn = vert[2] as usize;
588        mesh.normals
589            .push(*normals.get(vn).ok_or(ErrorKind::Oob(vn, 0))?);
590    }
591    if !colors.is_empty() {
592        let rgb = colors.get(v).ok_or(ErrorKind::Oob(v, 0))?;
593        // a is 1 by default: https://github.com/assimp/assimp/blob/v5.3.1/code/AssetLib/Obj/ObjFileImporter.cpp#L233
594        mesh.colors[0].push([rgb[0], rgb[1], rgb[2], 1.]);
595    }
596    Ok(())
597}
598
599fn push_mesh(
600    meshes: &mut Vec<Mesh>,
601    faces: &mut Vec<Face>,
602    vertices: &[Vec3],
603    texcoords: &[Vec2],
604    normals: &[Vec3],
605    colors: &[Vec3],
606    current_group: &[u8],
607    material_index: Option<u32>,
608) -> Result<(), ErrorKind> {
609    if !faces.is_empty() {
610        let mut mesh = Mesh {
611            name: from_utf8_lossy(current_group).into_owned(),
612            material_index: material_index.unwrap_or(u32::MAX),
613            ..Default::default()
614        };
615        // TODO
616        // mesh.faces.reserve(faces.len());
617        // mesh.vertices.reserve(faces.len() * 3);
618        // if !texcoords.is_empty() {
619        //     mesh.texcoords[0].reserve(faces.len() * 3);
620        // }
621        // if !normals.is_empty() {
622        //     mesh.normals.reserve(faces.len() * 3);
623        // }
624        // if !colors.is_empty() {
625        //     mesh.colors[0].reserve(faces.len() * 3);
626        // }
627        for face in &*faces {
628            match face {
629                Face::Point(_) | Face::Line(_) => {} // ignored
630                Face::Triangle(face) => {
631                    #[allow(clippy::cast_possible_truncation)]
632                    let vertices_indices = [
633                        mesh.vertices.len() as u32,
634                        (mesh.vertices.len() + 1) as u32,
635                        (mesh.vertices.len() + 2) as u32,
636                    ];
637                    push_vertex(&mut mesh, face[0], vertices, colors, texcoords, normals)?;
638                    push_vertex(&mut mesh, face[1], vertices, colors, texcoords, normals)?;
639                    push_vertex(&mut mesh, face[2], vertices, colors, texcoords, normals)?;
640                    mesh.faces.push(vertices_indices);
641                }
642                Face::Polygon(face) => {
643                    let a = face[0];
644                    let mut b = face[1];
645                    for &c in &face[2..] {
646                        #[allow(clippy::cast_possible_truncation)]
647                        let vertices_indices = [
648                            mesh.vertices.len() as u32,
649                            (mesh.vertices.len() + 1) as u32,
650                            (mesh.vertices.len() + 2) as u32,
651                        ];
652                        push_vertex(&mut mesh, a, vertices, colors, texcoords, normals)?;
653                        push_vertex(&mut mesh, b, vertices, colors, texcoords, normals)?;
654                        push_vertex(&mut mesh, c, vertices, colors, texcoords, normals)?;
655                        mesh.faces.push(vertices_indices);
656                        b = c;
657                    }
658                }
659            }
660        }
661        if !mesh.colors[0].is_empty() && mesh.vertices.len() != mesh.colors[0].len() {
662            // TODO: do not use (0)
663            return Err(ErrorKind::InvalidFaceIndex(0));
664        }
665        if !mesh.texcoords[0].is_empty() && mesh.vertices.len() != mesh.texcoords[0].len() {
666            return Err(ErrorKind::InvalidFaceIndex(0));
667        }
668        if !mesh.normals.is_empty() && mesh.vertices.len() != mesh.normals.len() {
669            return Err(ErrorKind::InvalidFaceIndex(0));
670        }
671        meshes.push(mesh);
672        faces.clear();
673    }
674    Ok(())
675}
676
677// -----------------------------------------------------------------------------
678// MTL
679
680// Not public API. (Used for fuzzing.)
681#[doc(hidden)]
682#[allow(clippy::implicit_hasher)] // false positive: doc(hidden) function should be treated as private
683pub fn read_mtl(
684    bytes: &[u8],
685    path: Option<&Path>,
686    materials: &mut Vec<common::Material>,
687    material_map: &mut HashMap<Vec<u8>, u32>,
688) -> io::Result<()> {
689    let bytes = &decode_bytes(bytes)?;
690    match read_mtl_internal(bytes, path.and_then(Path::parent), materials, material_map) {
691        Ok(()) => Ok(()),
692        Err(e) => Err(e.into_io_error(bytes, path)),
693    }
694}
695
696fn read_mtl_internal(
697    mut s: &[u8],
698    mtl_dir: Option<&Path>,
699    materials: &mut Vec<common::Material>,
700    material_map: &mut HashMap<Vec<u8>, u32>,
701) -> Result<(), ErrorKind> {
702    let mut mat: Option<Material<'_>> = None;
703    let mut current_name: &[u8] = b"";
704
705    while let Some((&c, s_next)) = s.split_first() {
706        match c {
707            b'K' | b'k' => {
708                s = s_next;
709                match s.first() {
710                    Some(b'a') => {
711                        s = &s[1..];
712                        if skip_spaces(&mut s) {
713                            let color = read_color(&mut s, "Ka")?;
714                            if let Some(mat) = &mut mat {
715                                mat.ambient = Some(color);
716                            }
717                            continue;
718                        }
719                    }
720                    Some(b'd') => {
721                        s = &s[1..];
722                        if skip_spaces(&mut s) {
723                            let color = read_color(&mut s, "Kd")?;
724                            if let Some(mat) = &mut mat {
725                                mat.diffuse = Some(color);
726                            }
727                            continue;
728                        }
729                    }
730                    Some(b's') => {
731                        s = &s[1..];
732                        if skip_spaces(&mut s) {
733                            let color = read_color(&mut s, "Ks")?;
734                            if let Some(mat) = &mut mat {
735                                mat.specular = Some(color);
736                            }
737                            continue;
738                        }
739                    }
740                    Some(b'e') => {
741                        s = &s[1..];
742                        if skip_spaces(&mut s) {
743                            let color = read_color(&mut s, "Ke")?;
744                            if let Some(mat) = &mut mat {
745                                mat.emissive = Some(color);
746                            }
747                            continue;
748                        }
749                    }
750                    _ => {}
751                }
752            }
753            b'T' => {
754                s = s_next;
755                match s.first() {
756                    Some(b'f') => {
757                        s = &s[1..];
758                        if skip_spaces(&mut s) {
759                            let color = read_color(&mut s, "Tf")?;
760                            if let Some(mat) = &mut mat {
761                                mat.transparent = Some(color);
762                            }
763                            continue;
764                        }
765                    }
766                    Some(b'r') => {
767                        s = &s[1..];
768                        if skip_spaces(&mut s) {
769                            let f = read_float1(&mut s, "Tr")?;
770                            if let Some(mat) = &mut mat {
771                                mat.alpha = Some(1. - f);
772                            }
773                            continue;
774                        }
775                    }
776                    _ => {}
777                }
778            }
779            b'd' => {
780                match s.get(1) {
781                    Some(b' ' | b'\t') => {
782                        s = &s[2..];
783                        skip_spaces(&mut s);
784                        let f = read_float1(&mut s, "d")?;
785                        if let Some(mat) = &mut mat {
786                            mat.alpha = Some(f);
787                        }
788                        continue;
789                    }
790                    Some(b'i') => {
791                        if read_texture(&mut s, &mut mat) {
792                            // disp
793                            continue;
794                        }
795                    }
796                    _ => {}
797                }
798                s = s_next;
799            }
800            b'N' | b'n' => match s.get(1) {
801                Some(b's') => {
802                    s = &s[2..];
803                    if skip_spaces(&mut s) {
804                        let f = read_float1(&mut s, "Ns")?;
805                        if let Some(mat) = &mut mat {
806                            mat.shininess = Some(f);
807                        }
808                        continue;
809                    }
810                }
811                Some(b'i') => {
812                    s = &s[2..];
813                    if skip_spaces(&mut s) {
814                        let f = read_float1(&mut s, "Ni")?;
815                        if let Some(mat) = &mut mat {
816                            mat.index_of_refraction = Some(f);
817                        }
818                        continue;
819                    }
820                }
821                Some(b'e') => {
822                    s = &s[2..];
823                    if token(&mut s, &b"newmtl"[2..]) {
824                        if skip_spaces(&mut s) {
825                            let (name, s_next) = name(s);
826                            if let Some(mat) = mat.replace(Material::default()) {
827                                push_material(materials, material_map, mtl_dir, current_name, &mat);
828                            }
829                            current_name = name;
830                            s = s_next;
831                            continue;
832                        }
833                    }
834                }
835                Some(b'o') => {
836                    if read_texture(&mut s, &mut mat) {
837                        // norm
838                        continue;
839                    }
840                }
841                _ => {}
842            },
843            b'P' => {
844                s = s_next;
845                match s.first() {
846                    Some(b'r') => {
847                        s = &s[1..];
848                        if skip_spaces(&mut s) {
849                            let f = read_float1(&mut s, "Pr")?;
850                            if let Some(mat) = &mut mat {
851                                mat.roughness = Some(f);
852                            }
853                            continue;
854                        }
855                    }
856                    Some(b'm') => {
857                        s = &s[1..];
858                        if skip_spaces(&mut s) {
859                            let f = read_float1(&mut s, "Pm")?;
860                            if let Some(mat) = &mut mat {
861                                mat.metallic = Some(f);
862                            }
863                            continue;
864                        }
865                    }
866                    Some(b's') => {
867                        s = &s[1..];
868                        if skip_spaces(&mut s) {
869                            let color = read_color(&mut s, "Ps")?;
870                            if let Some(mat) = &mut mat {
871                                mat.sheen = Some(color);
872                            }
873                            continue;
874                        }
875                    }
876                    Some(b'c') => {
877                        s = &s[1..];
878                        if s.first() == Some(&b'r') {
879                            if skip_spaces(&mut s) {
880                                let f = read_float1(&mut s, "Pcr")?;
881                                if let Some(mat) = &mut mat {
882                                    mat.clearcoat_roughness = Some(f);
883                                }
884                                continue;
885                            }
886                        } else if skip_spaces(&mut s) {
887                            let f = read_float1(&mut s, "Pc")?;
888                            if let Some(mat) = &mut mat {
889                                mat.clearcoat_thickness = Some(f);
890                            }
891                            continue;
892                        }
893                    }
894                    _ => {}
895                }
896            }
897            b'm' | b'b' | b'r' => {
898                if read_texture(&mut s, &mut mat) {
899                    continue;
900                }
901            }
902            b'i' => {
903                s = s_next;
904                if token(&mut s, &b"illum"[1..]) {
905                    if skip_spaces(&mut s) {
906                        match int::parse_partial::<u8>(s) {
907                            Some((i, n)) => {
908                                s = &s[n..];
909                                if !skip_spaces_until_line(&mut s) {
910                                    return Err(ErrorKind::ExpectedNewline("illum", s.len()));
911                                }
912                                if let Some(mat) = &mut mat {
913                                    mat.illumination_model = Some(i);
914                                }
915                            }
916                            None => return Err(ErrorKind::Int(s.len())),
917                        }
918                        continue;
919                    }
920                }
921            }
922            b'a' => {
923                s = s_next;
924                if skip_spaces(&mut s) {
925                    let f = read_float1(&mut s, "a")?;
926                    if let Some(mat) = &mut mat {
927                        mat.anisotropy = Some(f);
928                    }
929                    continue;
930                }
931            }
932            _ => {}
933        }
934        // ignore comment or other unknown
935        skip_any_until_line(&mut s);
936    }
937
938    if let Some(mat) = &mat {
939        push_material(materials, material_map, mtl_dir, current_name, mat);
940    }
941
942    Ok(())
943}
944
945fn read_texture<'a>(s: &mut &'a [u8], mat: &mut Option<Material<'a>>) -> bool {
946    // Empty name cases are processed later in texture_path.
947    // TODO: handle texture options
948    if token(s, b"map_Kd") {
949        if skip_spaces(s) {
950            let (name, s_next) = name(s);
951            if let Some(mat) = mat {
952                mat.diffuse_texture = Some(name);
953            }
954            *s = s_next;
955            return true;
956        }
957    } else if token(s, b"map_Ka") {
958        if skip_spaces(s) {
959            let (name, s_next) = name(s);
960            if let Some(mat) = mat {
961                mat.ambient_texture = Some(name);
962            }
963            *s = s_next;
964            return true;
965        }
966    } else if token(s, b"map_Ks") {
967        if skip_spaces(s) {
968            let (name, s_next) = name(s);
969            if let Some(mat) = mat {
970                mat.specular_texture = Some(name);
971            }
972            *s = s_next;
973            return true;
974        }
975    } else if token(s, b"map_disp") || token(s, b"disp") {
976        if skip_spaces(s) {
977            let (name, s_next) = name(s);
978            if let Some(mat) = mat {
979                mat.displacement_texture = Some(name);
980            }
981            *s = s_next;
982            return true;
983        }
984    } else if token(s, b"map_d") {
985        if skip_spaces(s) {
986            let (name, s_next) = name(s);
987            if let Some(mat) = mat {
988                mat.opacity_texture = Some(name);
989            }
990            *s = s_next;
991            return true;
992        }
993    } else if token(s, b"map_emissive") || token(s, b"map_Ke") {
994        if skip_spaces(s) {
995            let (name, s_next) = name(s);
996            if let Some(mat) = mat {
997                mat.emissive_texture = Some(name);
998            }
999            *s = s_next;
1000            return true;
1001        }
1002    } else if token(s, b"map_Bump") || token(s, b"map_bump") || token(s, b"bump") {
1003        if skip_spaces(s) {
1004            let (name, s_next) = name(s);
1005            if let Some(mat) = mat {
1006                mat.bump_texture = Some(name);
1007            }
1008            *s = s_next;
1009            return true;
1010        }
1011    } else if token(s, b"map_Kn") || token(s, b"norm") {
1012        if skip_spaces(s) {
1013            let (name, s_next) = name(s);
1014            if let Some(mat) = mat {
1015                mat.normal_texture = Some(name);
1016            }
1017            *s = s_next;
1018            return true;
1019        }
1020    } else if token(s, b"refl") {
1021        if skip_spaces(s) {
1022            let (_name, s_next) = name(s);
1023            // ignore https://github.com/assimp/assimp/blob/v5.3.1/code/AssetLib/Obj/ObjFileMtlImporter.cpp#L415
1024            *s = s_next;
1025            return true;
1026        }
1027    } else if token(s, b"map_Ns") || token(s, b"map_ns") || token(s, b"map_NS") {
1028        if skip_spaces(s) {
1029            let (name, s_next) = name(s);
1030            if let Some(mat) = mat {
1031                mat.specularity_texture = Some(name);
1032            }
1033            *s = s_next;
1034            return true;
1035        }
1036    } else if token(s, b"map_Pr") {
1037        if skip_spaces(s) {
1038            let (name, s_next) = name(s);
1039            if let Some(mat) = mat {
1040                mat.roughness_texture = Some(name);
1041            }
1042            *s = s_next;
1043            return true;
1044        }
1045    } else if token(s, b"map_Pm") {
1046        if skip_spaces(s) {
1047            let (name, s_next) = name(s);
1048            if let Some(mat) = mat {
1049                mat.metallic_texture = Some(name);
1050            }
1051            *s = s_next;
1052            return true;
1053        }
1054    } else if token(s, b"map_Ps") {
1055        if skip_spaces(s) {
1056            let (name, s_next) = name(s);
1057            if let Some(mat) = mat {
1058                mat.sheen_texture = Some(name);
1059            }
1060            *s = s_next;
1061            return true;
1062        }
1063    }
1064    false
1065}
1066
1067fn push_material(
1068    materials: &mut Vec<common::Material>,
1069    material_map: &mut HashMap<Vec<u8>, u32>,
1070    mtl_dir: Option<&Path>,
1071    current_name: &[u8],
1072    mat: &Material<'_>,
1073) {
1074    fn color4(color3: Option<[f32; 3]>) -> Option<Color4> {
1075        let rgb = color3?;
1076        // a is 1 by default: https://github.com/assimp/assimp/blob/v5.3.1/code/AssetLib/Obj/ObjFileImporter.cpp#L233
1077        Some([rgb[0], rgb[1], rgb[2], 1.])
1078    }
1079    fn texture_path(texture: Option<&[u8]>, mtl_dir: Option<&Path>) -> Option<PathBuf> {
1080        let mut p = texture?;
1081        if p.is_empty() {
1082            return None;
1083        }
1084        match mtl_dir {
1085            Some(mtl_dir) => {
1086                let tmp: Vec<_>;
1087                if p.contains(&b'\\') {
1088                    tmp = p
1089                        .iter()
1090                        .map(|&b| if b == b'\\' { b'/' } else { b })
1091                        .collect();
1092                    p = &*tmp;
1093                }
1094                if p.starts_with(b"/..") {
1095                    p = p.strip_prefix(b"/").unwrap_or(p);
1096                }
1097                p = p.strip_prefix(b"./").unwrap_or(p);
1098                let p = path_from_bytes(p).ok()?;
1099                let p = mtl_dir.join(p);
1100                if p.to_str().map_or(false, |s| {
1101                    s.starts_with("https://") || p.starts_with("http://")
1102                }) || p.exists()
1103                {
1104                    Some(p)
1105                } else {
1106                    None
1107                }
1108            }
1109            None => {
1110                let p = path_from_bytes(p).ok()?.to_owned();
1111                Some(p)
1112            }
1113        }
1114    }
1115    #[allow(clippy::cast_possible_truncation)]
1116    let material_index = materials.len() as u32;
1117    materials.push(common::Material {
1118        name: from_utf8_lossy(current_name).into_owned(),
1119        // Refs: https://github.com/assimp/assimp/blob/v5.3.1/code/AssetLib/Obj/ObjFileImporter.cpp#L591
1120        shading_model: match mat.illumination_model {
1121            Some(0) => Some(ShadingModel::NoShading),
1122            Some(1) => Some(ShadingModel::Gouraud),
1123            Some(2) => Some(ShadingModel::Phong),
1124            _ => None,
1125        },
1126        shininess: mat.shininess,
1127        opacity: mat.alpha,
1128        reflectivity: None,
1129        index_of_refraction: mat.index_of_refraction,
1130        // roughness_factor: mat.roughness,
1131        // metallic_factor: mat.metallic,
1132        // sheen_color_factor: mat.sheen,
1133        // clearcoat_factor: mat.clearcoat_thickness,
1134        // clearcoat_roughness_factor: mat.clearcoat_roughness,
1135        // anisotropy_factor: mat.anisotropy,
1136        color: common::Colors {
1137            ambient: color4(mat.ambient),
1138            diffuse: color4(mat.diffuse),
1139            specular: color4(mat.specular),
1140            emissive: color4(mat.emissive),
1141            transparent: color4(mat.transparent),
1142            reflective: None,
1143        },
1144        texture: common::Textures {
1145            diffuse: texture_path(mat.diffuse_texture, mtl_dir),
1146            ambient: texture_path(mat.ambient_texture, mtl_dir),
1147            emissive: texture_path(mat.emissive_texture, mtl_dir),
1148            specular: texture_path(mat.specular_texture, mtl_dir),
1149            height: texture_path(mat.bump_texture, mtl_dir),
1150            normal: texture_path(mat.normal_texture, mtl_dir),
1151            reflection: None, // TODO
1152            displacement: texture_path(mat.displacement_texture, mtl_dir),
1153            opacity: texture_path(mat.opacity_texture, mtl_dir),
1154            shininess: texture_path(mat.specularity_texture, mtl_dir),
1155            lightmap: None,
1156        },
1157    });
1158    material_map.insert(current_name.to_owned(), material_index);
1159}
1160
1161// -----------------------------------------------------------------------------
1162// Helpers
1163
1164enum Face {
1165    Point(#[allow(dead_code)] [[u32; 3]; 1]),
1166    Line(#[allow(dead_code)] [[u32; 3]; 2]),
1167    Triangle([[u32; 3]; 3]),
1168    Polygon(Vec<[u32; 3]>),
1169}
1170
1171#[derive(Default)]
1172struct Material<'a> {
1173    // Textures
1174    diffuse_texture: Option<&'a [u8]>,
1175    specular_texture: Option<&'a [u8]>,
1176    ambient_texture: Option<&'a [u8]>,
1177    emissive_texture: Option<&'a [u8]>,
1178    bump_texture: Option<&'a [u8]>,
1179    normal_texture: Option<&'a [u8]>,
1180    // reflection_texture: Option<&'a [u8]>,
1181    specularity_texture: Option<&'a [u8]>,
1182    opacity_texture: Option<&'a [u8]>,
1183    displacement_texture: Option<&'a [u8]>,
1184    roughness_texture: Option<&'a [u8]>,
1185    metallic_texture: Option<&'a [u8]>,
1186    sheen_texture: Option<&'a [u8]>,
1187    // rma_texture: Option<&'a [u8]>,
1188
1189    // Colors
1190    ambient: Option<[f32; 3]>,
1191    diffuse: Option<[f32; 3]>,
1192    specular: Option<[f32; 3]>,
1193    emissive: Option<[f32; 3]>,
1194    alpha: Option<f32>,
1195    shininess: Option<f32>,
1196    illumination_model: Option<u8>,
1197    index_of_refraction: Option<f32>,
1198    transparent: Option<[f32; 3]>,
1199
1200    roughness: Option<f32>,
1201    metallic: Option<f32>,
1202    sheen: Option<[f32; 3]>,
1203    clearcoat_thickness: Option<f32>,
1204    clearcoat_roughness: Option<f32>,
1205    anisotropy: Option<f32>,
1206    // bump_multiplier: Option<f32>,
1207}
1208
1209// [\r\n]
1210const LINE: u8 = 1 << 0;
1211// [ \t]
1212const SPACE: u8 = 1 << 1;
1213// [ \r\n\t]
1214const WHITESPACE: u8 = 1 << 2;
1215
1216static TABLE: [u8; 256] = {
1217    const __: u8 = 0;
1218    const LN: u8 = WHITESPACE | LINE;
1219    const NL: u8 = WHITESPACE | SPACE;
1220    [
1221        //  _1  _2  _3  _4  _5  _6  _7  _8  _9  _A  _B  _C  _D  _E  _F
1222        __, __, __, __, __, __, __, __, __, NL, LN, __, __, LN, __, __, // 0_
1223        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 1_
1224        NL, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2_
1225        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3_
1226        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4_
1227        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 5_
1228        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6_
1229        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7_
1230        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8_
1231        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9_
1232        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A_
1233        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B_
1234        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C_
1235        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D_
1236        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E_
1237        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F_
1238    ]
1239};
1240#[test]
1241fn table() {
1242    for b in u8::MIN..=u8::MAX {
1243        match b {
1244            b' ' | b'\t' => {
1245                assert_eq!(
1246                    TABLE[b as usize],
1247                    WHITESPACE | SPACE,
1248                    "{:?}({b:#X})",
1249                    b as char
1250                );
1251            }
1252            b'\n' | b'\r' => {
1253                assert_eq!(
1254                    TABLE[b as usize],
1255                    WHITESPACE | LINE,
1256                    "{:?}({b:#X})",
1257                    b as char
1258                );
1259            }
1260            _ => assert_eq!(TABLE[b as usize], 0, "{:?}({b:#X})", b as char),
1261        }
1262    }
1263}
1264
1265#[inline]
1266fn skip_whitespace_until_byte_or_eof(s: &mut &[u8], byte_mask: u8, whitespace_mask: u8) -> bool {
1267    while let Some((&b, s_next)) = s.split_first() {
1268        let t = TABLE[b as usize];
1269        if t & byte_mask != 0 {
1270            *s = s_next;
1271            break;
1272        }
1273        if t & whitespace_mask != 0 {
1274            *s = s_next;
1275            continue;
1276        }
1277        if b == b'\\' && matches!(s_next.first(), Some(b'\n' | b'\r')) {
1278            if s_next.starts_with(b"\r\n") {
1279                *s = &s_next[2..];
1280            } else {
1281                *s = &s_next[1..];
1282            }
1283            continue;
1284        }
1285        return false;
1286    }
1287    true
1288}
1289
1290#[inline]
1291fn skip_spaces_until_line(s: &mut &[u8]) -> bool {
1292    skip_whitespace_until_byte_or_eof(s, LINE, SPACE)
1293}
1294
1295/// Skips spaces or tabs, and returns `true` if one or more spaces or tabs are
1296/// present. (not consumes non-{space,tab} characters.
1297#[inline]
1298fn skip_spaces(s: &mut &[u8]) -> bool {
1299    let start = *s;
1300    while let Some((&b, s_next)) = s.split_first() {
1301        if TABLE[b as usize] & SPACE != 0 {
1302            *s = s_next;
1303            continue;
1304        }
1305        if b == b'\\' && matches!(s_next.first(), Some(b'\n' | b'\r')) {
1306            if s_next.starts_with(b"\r\n") {
1307                *s = &s_next[2..];
1308            } else {
1309                *s = &s_next[1..];
1310            }
1311            continue;
1312        }
1313        break;
1314    }
1315    start.len() != s.len()
1316}
1317
1318/// Skips non-line (non-`[\r\n]`) characters. (consumes line character).
1319#[inline]
1320fn skip_any_until_line(s: &mut &[u8]) {
1321    while let Some((&b, s_next)) = s.split_first() {
1322        if TABLE[b as usize] & LINE != 0 {
1323            *s = s_next;
1324            break;
1325        }
1326        if b == b'\\' && matches!(s_next.first(), Some(b'\n' | b'\r')) {
1327            if s_next.starts_with(b"\r\n") {
1328                *s = &s_next[2..];
1329            } else {
1330                *s = &s_next[1..];
1331            }
1332            continue;
1333        }
1334        *s = s_next;
1335        continue;
1336    }
1337}
1338
1339#[inline]
1340fn token(s: &mut &[u8], token: &'static [u8]) -> bool {
1341    if starts_with(s, token) {
1342        *s = &s[token.len()..];
1343        true
1344    } else {
1345        false
1346    }
1347}
1348
1349fn name(mut s: &[u8]) -> (&[u8], &[u8]) {
1350    let start = s;
1351    skip_any_until_line(&mut s);
1352    let mut name = &start[..start.len() - s.len()];
1353    // Allow spaces in middle, trim end
1354    // https://github.com/assimp/assimp/commit/c84a14a7a8ae4329114269a0ffc1921c838eda9e
1355    while let Some((&b, name_next)) = name.split_last() {
1356        if TABLE[b as usize] & WHITESPACE != 0 {
1357            name = name_next;
1358            continue;
1359        }
1360        break;
1361    }
1362    (name, s)
1363}