Simple Ray Tracing in Rust 2: A .obj Parser
In Part 1, we wrote some basic structs to represent our scenes, and covered the essentials of the algorithm we’ll use to render them into an image. This begs the question: How do we construct these scenes?
Building a 3D scene from scratch would be an involved effort, of course. Thankfully, we don’t have to. Artists, animators, and computer graphics researchers alike have built many file formats to store objects in exportable files that can be shared from computer to computer and parsed by your favorite rendering suite, such as Blender.
For my ray tracer, I’ll implement a subset of Wavefront’s .obj
file format,
which is open source and widely used.
Argument Parsing
Let’s start simple. We compile and run our program with Rust’s trusty package manager,
cargo
, and we pass in an argument: the path to our .obj
file.
Our plan is to parse this file into a 3D scene within our program. First we collect the argument:
Let’s define a helper function that opens a file and returns a reader for easy line processing. We can take advantage of some cool functions that are already provided in Rust’s standard library!
With this helper, we can lazily process lines of text from our .obj
file.
Back in the main function, we can use if let
syntax to idiomatically
instantiate our line reader for further processing:
Time to get our hands dirty. We now go through our .obj
file
line by line, translating it into a Vec<Triangle>
. Conveniently enough,
Wikipedia has a neat overview
of how .obj
files store data.
First you go parse each line one by one, translating it into a corresponding data structure.
Rust’s match
blocks are perfect for this task. We parse each line depending on the initial
word:
- Lines starting with
v
correspond to the coordinates of triangle vertices - Lines starting with
f
correspond to vertex triplets that make up triangles
With this information, we can begin building our triangle mesh:
After extracting triangle vertex and face information in the form of somewhat disorganized lists of numbers, we can finally start instantiating the structs we previously defined.
We take advantage of Rust’s awesome anonymous functions, known
to Rustaceans as closures. We functionally map over our vector
of triangle faces, extracting their vertex information,
and turning them into native Triangle
structs to be processed
by our program:
Finally, we return to our main function, where we can use the nifty input parser we just defined:
Conclusion
In this part, we learned how to extract a CLI argument, open a file using
tools from Rust’s standard library, and parse each of the lines
in our .obj
file into
coordinate information of our vertices. Then we converted raw vertex
data into Triangle
structs that can easily be digested by our program later on.
Stay tuned; in the coming parts we will organize our measly Triangles
into
a 3D scene, and lay down a solid foundation for our ray tracing loop!