lumo/parser/
obj.rs

1use super::*;
2
3/// https://github.com/ekzhang/rpt/blob/master/src/io.rs
4/// https://www.cs.cmu.edu/~mbz/personal/graphics/obj.html
5pub fn load_file(file: File, material: Material) -> Result<Mesh> {
6    let mut vertices: Vec<Point> = Vec::new();
7    let mut normals: Vec<Normal> = Vec::new();
8    let mut uvs: Vec<Vec2> = Vec::new();
9    let mut faces: Vec<Face> = Vec::new();
10
11    let reader = BufReader::new(file);
12    for line in reader.lines() {
13        let line = line?.trim().to_string();
14        if line.starts_with('#') || line.is_empty() {
15            continue;
16        }
17        let tokens: Vec<&str> = line.split_ascii_whitespace().collect();
18
19        parse_tokens(tokens, &mut vertices, &mut normals, &mut uvs, &mut faces)?;
20    }
21
22    Ok(TriangleMesh::new(vertices, faces, normals, uvs, material))
23}
24
25
26pub fn load_scene(file: File, materials: HashMap<String, MtlConfig>) -> Result<Scene> {
27    let mut scene = Scene::default();
28    let mut vertices: Vec<Point> = Vec::new();
29    let mut normals: Vec<Normal> = Vec::new();
30    let mut uvs: Vec<Vec2> = Vec::new();
31    let mut faces: Vec<Face> = Vec::new();
32    let mut meshes: Vec<(Vec<Face>, Material)> = Vec::new();
33    let mut material = Material::Blank;
34
35    let reader = BufReader::new(file);
36    for line in reader.lines() {
37        let line = line?.trim().to_string();
38        if line.starts_with('#') || line.is_empty() {
39            continue;
40        }
41        let tokens: Vec<&str> = line.split_ascii_whitespace().collect();
42
43        match tokens[0] {
44            "g" | "o" => {
45                if !faces.is_empty() {
46                    meshes.push((faces, material));
47                    faces = Vec::new();
48                    material = Material::Blank;
49                }
50            }
51            "usemtl" => {
52                match materials.get(tokens[1]) {
53                    Some(mtl_cfg) => material = mtl_cfg.build_material(),
54                    None => {
55                        return Err(obj_error(
56                            &format!("Could not find material {}", tokens[1])
57                        ));
58                    }
59                }
60            }
61            _ => {
62                parse_tokens(
63                    tokens,
64                    &mut vertices,
65                    &mut normals,
66                    &mut uvs,
67                    &mut faces
68                )?
69            }
70        }
71    }
72
73    meshes.push((faces, material));
74
75    let triangle_mesh = Arc::new(TriangleMesh {
76        vertices,
77        normals,
78        uvs,
79    });
80
81    for (faces, mtl) in meshes {
82        let is_light = matches!(mtl, Material::Light(_));
83        let object = Box::new(TriangleMesh::new_from_faces(
84            triangle_mesh.clone(),
85            faces,
86            mtl,
87        ));
88
89        if is_light {
90            scene.add_light(object);
91        } else {
92            scene.add(object);
93        }
94    }
95
96    Ok(scene)
97}
98
99fn parse_tokens(
100    tokens: Vec<&str>,
101    vertices: &mut Vec<Point>,
102    normals: &mut Vec<Normal>,
103    uvs: &mut Vec<Vec2>,
104    faces: &mut Vec<Face>,
105) -> Result<()> {
106    match tokens[0] {
107        "v" => {
108            let vertex = parse_vec3(&tokens)?;
109            vertices.push(vertex);
110        }
111        "vn" => {
112            let normal = parse_vec3(&tokens)?;
113            normals.push(normal);
114        }
115        "vt" => {
116            let uv = parse_vec2(&tokens)?;
117            uvs.push(uv);
118        }
119        "f" => {
120            let face = parse_face(&tokens, vertices, normals, uvs)?;
121            faces.extend(face);
122        }
123        _ => (),
124    }
125    Ok(())
126}
127
128/// Parses a face from a .obj file
129fn parse_face(
130    tokens: &[&str],
131    vertices: &[Point],
132    normals: &[Normal],
133    uvs: &[Vec2],
134) -> Result<Vec<Face>> {
135    let mut vidxs: Vec<usize> = Vec::new();
136    let mut nidxs: Vec<usize> = Vec::new();
137    let mut tidxs: Vec<usize> = Vec::new();
138
139    for token in &tokens[1..] {
140        let arguments: Vec<&str> = token.split('/').collect();
141
142        let vidx = parse_idx(arguments[0], vertices.len())?;
143        vidxs.push(vidx);
144
145        if arguments.len() > 1 && !arguments[1].is_empty() {
146            let tidx = parse_idx(arguments[1], uvs.len())?;
147            tidxs.push(tidx);
148        }
149
150        if arguments.len() > 2 {
151            let nidx = parse_idx(arguments[2], normals.len())?;
152            nidxs.push(nidx);
153        }
154    }
155
156    let mut faces: Vec<Face> = Vec::new();
157
158    for i in 1..vidxs.len() - 1 {
159        let (a, b, c) = (0, i, i + 1);
160        let vidx = vec![vidxs[a], vidxs[b], vidxs[c]];
161
162        let nidx = if nidxs.is_empty() {
163            Vec::new()
164        } else {
165            vec![nidxs[a], nidxs[b], nidxs[c]]
166        };
167
168        let tidx = if tidxs.is_empty() {
169            Vec::new()
170        } else {
171            vec![tidxs[a], tidxs[b], tidxs[c]]
172        };
173
174        faces.push(Face::new(vidx, nidx, tidx));
175    }
176
177    Ok(faces)
178}