• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ekarpp / lumo / 4788921599

pending completion
4788921599

push

github

GitHub
0.2.6 (#24)

618 of 618 new or added lines in 32 files covered. (100.0%)

1627 of 3087 relevant lines covered (52.7%)

9641000.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.51
/src/obj.rs
1
use crate::tracer::{Material, Triangle};
2
use glam::{DMat3, DVec2, DVec3};
3
use std::fs::File;
4
use std::io::{self, BufRead, BufReader, Result};
5
use std::io::{Cursor, Read, Seek, Write};
6
use zip::ZipArchive;
7

8
/// Function to create io::Error
9
fn obj_error(message: &str) -> io::Error {
×
10
    io::Error::new(io::ErrorKind::InvalidData, message)
×
11
}
×
12

13
/// Loads a .OBJ file at the given path
14
pub fn obj_from_path(path: &str) -> Result<Vec<Triangle>> {
×
15
    println!("Loading .OBJ file \"{}\"", path);
×
16
    load_obj_file(File::open(path)?)
×
17
}
×
18

19
/// Loads .OBJ file from resource at an URL. Supports direct .OBJ files and
20
/// .OBJ files within a zip archive.
21
pub fn obj_from_url(url: &str) -> Result<Vec<Triangle>> {
4✔
22
    println!("Loading .OBJ from \"{}\"", url);
4✔
23
    let mut bytes = Vec::new();
4✔
24
    ureq::get(url)
4✔
25
        .call()
4✔
26
        .map_err(|_| obj_error("Error during HTTP, error parsing not implemented"))?
4✔
27
        .into_reader()
4✔
28
        .read_to_end(&mut bytes)?;
4✔
29

30
    if url.ends_with(".zip") {
4✔
31
        println!("Found zip archive, searching for .OBJ files");
3✔
32
        let mut zip = ZipArchive::new(Cursor::new(bytes))?;
3✔
33
        bytes = Vec::new();
3✔
34
        for i in 0..zip.len() {
9✔
35
            let mut file = zip.by_index(i)?;
9✔
36

37
            if file.name().ends_with("obj") {
9✔
38
                println!("Extracting \"{}\"", file.name());
3✔
39
                file.read_to_end(&mut bytes)?;
3✔
40
                break;
3✔
41
            }
6✔
42
        }
43
        if bytes.is_empty() {
3✔
44
            return Err(obj_error("Could not find .OBJ files in the archive"));
×
45
        }
3✔
46
    } else if !url.ends_with(".obj") {
1✔
47
        return Err(obj_error(
×
48
            "Bad URL, or at least does not end with .zip or .obj",
×
49
        ));
×
50
    }
1✔
51

52
    let mut tmp_file = tempfile::tempfile()?;
4✔
53
    tmp_file.write_all(&bytes)?;
4✔
54
    tmp_file.rewind()?;
4✔
55
    load_obj_file(tmp_file)
4✔
56
}
4✔
57

58
/// https://github.com/ekzhang/rpt/blob/master/src/io.rs
59
/// https://www.cs.cmu.edu/~mbz/personal/graphics/obj.html
60
fn load_obj_file(file: File) -> Result<Vec<Triangle>> {
4✔
61
    let mut vertices: Vec<DVec3> = Vec::new();
4✔
62
    let mut normals: Vec<DVec3> = Vec::new();
4✔
63
    let mut uvs: Vec<DVec2> = Vec::new();
4✔
64
    let mut triangles: Vec<Triangle> = Vec::new();
4✔
65

4✔
66
    let reader = BufReader::new(file);
4✔
67
    for line in reader.lines() {
75,120✔
68
        let line = line?.trim().to_string();
75,120✔
69
        if line.starts_with('#') || line.is_empty() {
75,120✔
70
            continue;
65✔
71
        }
75,055✔
72
        let tokens: Vec<&str> = line.split_ascii_whitespace().collect();
75,055✔
73

75,055✔
74
        match tokens[0] {
75,055✔
75
            "v" => {
75,055✔
76
                let vertex = parse_vec3(&tokens)?;
23,712✔
77
                vertices.push(vertex);
23,712✔
78
            }
79
            "vn" => {
51,343✔
80
                let normal = parse_vec3(&tokens)?;
960✔
81
                normals.push(normal);
960✔
82
            }
83
            "vt" => {
50,383✔
84
                let uv = parse_vec2(&tokens)?;
25,962✔
85
                uvs.push(uv);
25,962✔
86
            }
87
            "f" => {
24,421✔
88
                let face = parse_face(&tokens, &vertices, &normals, &uvs)?;
24,404✔
89
                triangles.extend(face);
24,404✔
90
            }
91
            _ => (),
17✔
92
        }
93
    }
94

95
    println!("Parsed .OBJ file with {} triangles", triangles.len());
4✔
96
    Ok(triangles)
4✔
97
}
4✔
98

99
fn parse_double(token: &str) -> Result<f64> {
125,940✔
100
    token
125,940✔
101
        .parse()
125,940✔
102
        .map_err(|_| obj_error("Could not parse double in .OBJ"))
125,940✔
103
}
125,940✔
104

105
fn parse_vec2(tokens: &[&str]) -> Result<DVec2> {
25,962✔
106
    Ok(DVec2::new(
25,962✔
107
        parse_double(tokens[1])?,
25,962✔
108
        parse_double(tokens[2])?,
25,962✔
109
    ))
110
}
25,962✔
111

112
fn parse_vec3(tokens: &[&str]) -> Result<DVec3> {
24,672✔
113
    Ok(DVec3::new(
24,672✔
114
        parse_double(tokens[1])?,
24,672✔
115
        parse_double(tokens[2])?,
24,672✔
116
        parse_double(tokens[3])?,
24,672✔
117
    ))
118
}
24,672✔
119

120
fn parse_idx(token: &str, vec_len: usize) -> Result<usize> {
193,440✔
121
    token
193,440✔
122
        .parse::<i32>()
193,440✔
123
        .map(|idx| {
193,440✔
124
            if idx > 0 {
193,440✔
125
                (idx - 1) as usize
193,440✔
126
            } else {
127
                (vec_len as i32 + idx) as usize
×
128
            }
129
        })
193,440✔
130
        .map_err(|_| obj_error("Could not parse index in .OBJ"))
193,440✔
131
}
193,440✔
132

133
/// Some .objs have degenerate triangles. This filters them out.
134
fn degenerate_triangle(abc: DMat3) -> bool {
47,432✔
135
    let a = abc.col(0); let b = abc.col(1); let c = abc.col(2);
47,432✔
136
    let ng = (b - a).cross(c - a);
47,432✔
137
    ng.length() == 0.0
47,432✔
138
}
47,432✔
139

140
/// Some .objs have zero vector normals. This fixes them to geometric normal.
141
fn fixed_normals(abc: DMat3, na: DVec3, nb: DVec3, nc: DVec3) -> DMat3 {
320✔
142
    let a = abc.col(0); let b = abc.col(1); let c = abc.col(2);
320✔
143
    // cant be degenerate at this point
320✔
144
    let ng = (b - a).cross(c - a);
320✔
145
    let ng = ng.normalize();
320✔
146

320✔
147
    DMat3::from_cols(
320✔
148
        if na.length() == 0.0 { ng } else { na },
320✔
149
        if nb.length() == 0.0 { ng } else { nb },
320✔
150
        if nc.length() == 0.0 { ng } else { nc },
320✔
151
    )
152
}
320✔
153

154
fn parse_face(
24,404✔
155
    tokens: &[&str],
24,404✔
156
    vertices: &[DVec3],
24,404✔
157
    normals: &[DVec3],
24,404✔
158
    uvs: &[DVec2],
24,404✔
159
) -> Result<Vec<Triangle>> {
24,404✔
160
    let mut vidxs: Vec<usize> = Vec::new();
24,404✔
161
    let mut nidxs: Vec<usize> = Vec::new();
24,404✔
162
    let mut tidxs: Vec<usize> = Vec::new();
24,404✔
163

164
    for token in &tokens[1..] {
96,240✔
165
        let arguments: Vec<&str> = token.split('/').collect();
96,240✔
166

167
        let vidx = parse_idx(arguments[0], vertices.len())?;
96,240✔
168
        vidxs.push(vidx);
96,240✔
169

96,240✔
170
        if arguments.len() > 1 && !arguments[1].is_empty() {
96,240✔
171
            let tidx = parse_idx(arguments[1], uvs.len())?;
96,240✔
172
            tidxs.push(tidx);
96,240✔
173
        }
×
174

175
        if arguments.len() > 2 {
96,240✔
176
            let nidx = parse_idx(arguments[2], normals.len())?;
960✔
177
            nidxs.push(nidx);
960✔
178
        }
95,280✔
179
    }
180

181
    let mut triangles: Vec<Triangle> = Vec::new();
24,404✔
182

183
    for i in 1..vidxs.len() - 1 {
47,432✔
184
        let (a, b, c) = (0, i, i + 1);
47,432✔
185
        let (va, vb, vc) = (vidxs[a], vidxs[b], vidxs[c]);
47,432✔
186
        let abc = DMat3::from_cols(vertices[va], vertices[vb], vertices[vc]);
47,432✔
187

47,432✔
188
        if degenerate_triangle(abc) {
47,432✔
189
            continue;
×
190
        }
47,432✔
191

192
        let nabc = if nidxs.is_empty() {
47,432✔
193
            None
47,112✔
194
        } else {
195
            let (na, nb, nc) = (nidxs[a], nidxs[b], nidxs[c]);
320✔
196
            Some(fixed_normals(abc, normals[na], normals[nb], normals[nc]))
320✔
197
        };
198

199
        let tabc = if tidxs.is_empty() {
47,432✔
200
            None
×
201
        } else {
202
            let (ta, tb, tc) = (tidxs[a], tidxs[b], tidxs[c]);
47,432✔
203
            Some(DMat3::from_cols(
47,432✔
204
                uvs[ta].extend(0.0),
47,432✔
205
                uvs[tb].extend(0.0),
47,432✔
206
                uvs[tc].extend(0.0),
47,432✔
207
            ))
47,432✔
208
        };
209

210
        triangles.push(*Triangle::new(abc, nabc, tabc, Material::Blank));
47,432✔
211
    }
212

213
    Ok(triangles)
24,404✔
214
}
24,404✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc