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

ekarpp / lumo / 4649875409

pending completion
4649875409

push

github

GitHub
0.2.3 (#14)

219 of 219 new or added lines in 16 files covered. (100.0%)

921 of 2498 relevant lines covered (36.87%)

11962438.25 hits per line

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

90.37
/src/obj.rs
1
use crate::tracer::{Material, Triangle};
2
use glam::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 triangles: Vec<Triangle> = Vec::new();
4✔
64

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

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

90
    println!("Parsed .OBJ file with {} triangles", triangles.len());
4✔
91
    Ok(triangles)
4✔
92
}
4✔
93

94
fn parse_double(token: &str) -> Result<f64> {
74,016✔
95
    token
74,016✔
96
        .parse()
74,016✔
97
        .map_err(|_| obj_error("Could not parse double in .OBJ"))
74,016✔
98
}
74,016✔
99

100
fn parse_vec3(tokens: &[&str]) -> Result<DVec3> {
24,672✔
101
    Ok(DVec3::new(
24,672✔
102
        parse_double(tokens[1])?,
24,672✔
103
        parse_double(tokens[2])?,
24,672✔
104
        parse_double(tokens[3])?,
24,672✔
105
    ))
106
}
24,672✔
107

108
fn parse_idx(token: &str, vec_len: usize) -> Result<usize> {
97,200✔
109
    token
97,200✔
110
        .parse::<i32>()
97,200✔
111
        .map(|idx| {
97,200✔
112
            if idx > 0 {
97,200✔
113
                (idx - 1) as usize
97,200✔
114
            } else {
115
                (vec_len as i32 + idx) as usize
×
116
            }
117
        })
97,200✔
118
        .map_err(|_| obj_error("Could not parse index in .OBJ"))
97,200✔
119
}
97,200✔
120

121
/// Some .objs have degenerate triangles. This filters them out.
122
fn degenerate_triangle(abc: (DVec3, DVec3, DVec3)) -> bool {
47,432✔
123
    let ng = (abc.1 - abc.0).cross(abc.2 - abc.0);
47,432✔
124
    ng.length() == 0.0
47,432✔
125
}
47,432✔
126

127
/// Some .objs have zero vector normals. This fixes them to geometric normal.
128
fn fixed_normals(
320✔
129
    abc: (DVec3, DVec3, DVec3),
320✔
130
    na: DVec3,
320✔
131
    nb: DVec3,
320✔
132
    nc: DVec3
320✔
133
) -> (DVec3, DVec3, DVec3) {
320✔
134
    // cant be degenerate at this point
320✔
135
    let ng = (abc.1 - abc.0).cross(abc.2 - abc.0);
320✔
136
    let ng = ng.normalize();
320✔
137

138
    let na = if na.length() == 0.0 { ng } else { na };
320✔
139
    let nb = if nb.length() == 0.0 { ng } else { nb };
320✔
140
    let nc = if nc.length() == 0.0 { ng } else { nc };
320✔
141

142
    (na, nb, nc)
320✔
143
}
320✔
144

145
fn parse_face(tokens: &[&str], vertices: &[DVec3], normals: &[DVec3]) -> Result<Vec<Triangle>> {
24,404✔
146
    let mut vidxs: Vec<usize> = Vec::new();
24,404✔
147
    let mut nidxs: Vec<usize> = Vec::new();
24,404✔
148

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

152
        let vidx = parse_idx(arguments[0], vertices.len())?;
96,240✔
153
        vidxs.push(vidx);
96,240✔
154
        if arguments.len() >= 3 {
96,240✔
155
            let nidx = parse_idx(arguments[2], normals.len())?;
960✔
156
            nidxs.push(nidx);
960✔
157
        }
95,280✔
158
    }
159

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

162
    for i in 1..vidxs.len() - 1 {
47,432✔
163
        let (a, b, c) = (0, i, i + 1);
47,432✔
164
        let (va, vb, vc) = (vidxs[a], vidxs[b], vidxs[c]);
47,432✔
165
        let abc = (vertices[va], vertices[vb], vertices[vc]);
47,432✔
166

47,432✔
167
        if degenerate_triangle(abc) {
47,432✔
168
            continue;
×
169
        }
47,432✔
170

47,432✔
171
        if nidxs.is_empty() {
47,432✔
172
            triangles.push(*Triangle::new(abc, Material::Blank));
47,112✔
173
        } else {
47,112✔
174
            let (na, nb, nc) = (nidxs[a], nidxs[b], nidxs[c]);
320✔
175
            let nabc = fixed_normals(abc, normals[na], normals[nb], normals[nc]);
320✔
176
            triangles.push(Triangle::new_w_normals(abc, nabc));
320✔
177
        }
320✔
178
    }
179

180
    Ok(triangles)
24,404✔
181
}
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