Introducing TK_ObjFile.hIntroducing TK_ObjFile.h
January 02, 2016
Late last year, I was lucky enough to attend HandmadeCon 2015. It was a great and inspiring series of talks. After the talks, and some dinner and drinks and lots of geeky programming talk, I was inspired to start a "small" project that I had been thinking about for ages. I had a few more hours sitting around at the airport the next morning, and even kept working on it on the flight until the turbulence got so bad that I couldn't type (not even joking).
I made a good start, and then over the christmas holidays I had a lot of time sitting around while people cooked things or ate things or planned our day, and managed to finish it up. The result: tk_objfile.h.
tk_objfile is a simple, header-only, single-file .OBJ file loader. It has zero dependancies and does no allocations, parsing in place and using only scratch memory passed in by the caller. The result, hopefully, is an easy way get triangle data out of an .OBJ file for any project.
This was heavily inspired by the stb_ family of libraries by Sean Barrett, and also by Casey Muratori's Handmade Hero series. That's the style of programming I learned back in the day (though I'm not as good as Sean or Casey), and that's how I used to write code. Then C++ came along, and I started writing things more enterprisey, and using terrible things like boost, even dabbling in template metaprogramming (an obfuscation technique that makes you feel smart, your compiler slow, and your code unfixable). Slowly, I came to the dawning realization that the code that I actually kept reusing, the code that still worked in new projects, turned out to be the simpler, C or vanilla CPP style code. I gradually recanted all the c++ nonsense, but I still felt guilty about it until I started watching the handmade hero stream. I can't claim it was fast to write this way, reimplementing stuff like atof and lacking any data structures, but it was fun to do. Anyways, this was a good exercise to get back to a minimal style and hopefully produce something useful.
To paraphrase, OBJ is a terrible format, but it's better than all the others. Maybe not better, but it's usually the shortest path to get things out of a 3D authoring package. If you can live with only having position, ST and normals, it's quick and easy. (btw, I'd LOVE someone to make an OBJ-like format that was just a tiny bit more extensable, textual and easy to parse but allowing more custom uniform or per-vertex attributes).
Now, parsing an OBJ file is not a difficult task. In fact, if you're learning programming, I would highly reccommend it as an exercise. It's simple enough to get working in a few hours, it's useful in the real world, and there's no shortage of examples to test on. I've written OBJ parsers many times before. And therein lies the problem, I keep rewriting it. I kept not being able to re-use my previous ones, maybe it was using a vector/matrix library that I wasn't using anymore, maybe it was using stl and I didn't want to drag that in just to load an OBJ file, maybe it made assumptions about normals or geometry that were fine for a previous project but didn't work for a current one.
Also, the one tricky detail after writing a handful of these tends to be handling materials. I tend to name my materials with the name of the texture in blender, that way I can export an .OBJ with multiple materials instead of having to split and keep track of a bunch of subobjects. Parsing these and batching the triangles by material is annoying and error-prone, but useful enough that I wanted to have that. So I wanted to make sure I made a loader that would handle multiple materials in a nice way. And if you don't care about the materials, you can just ignore it and don't have to do any extra junk to iterate over the material groups or anything.
Usage and Availability
The library is available at tk_objfile on GitHub. It's released under the MIT license, so you should be able to use it in any project, commercial or non-commercial.
The API tries to be simple: you give it the contents of an .OBJ file, and it hands you a bunch of triangles. It's a little weird since it's kind of a generator, based on callbacks, but that's the way I've used it in the past that has worked best for me. You can read more about the usage details on the github page, and in the header file itself.
I'll probably eventually make an "simple mode" wrapper that uses the C stdlib to do the file read and allocations and stuff, and just hands you an array with a bunch of triangles. Doing that seems obvious, but that makes a lot of assumptions about the calling program, and can end up being more work to rearrange the data. Of course, when those assumptions fit, and often they do, it's handy to have a simple one-shot API.
Also, fair warning, this is pretty untested, and there are probably bugs in there.
I wrote a simple .OBJ viewer as a test case, based mostly on the glfw example from IMGUI. It also uses stb_image for loading textures. The code is quite messy and horrible, and the viewer is buggy and nearly unusable. How's that for a sales pitch? But I figured it was better to include it as something is better than nothing. I might continue to improve it and clean up and add features, but don't hold your breath (I'm actually not even using .OBJ in my personal pipeline anymore, which makes this project even more silly).
The viewer does handle multiple materials, and draws them each with a different tint color. Also, if you name a material with the same name as a .png file in the current directory, it will load that file as a texture map (as in the Hugzilla example above).
I didn't really try especially hard to make this fast, but the handmade style does kind of lend itself to performance, and it turned out pretty quick. It parses the ajax_jotero_com.obj mesh (50MB, 544k triangles) in 700ms, which is maybe not great for a AAA game pipeline that processes tens of thousands of objects, but if you're working on such a pipeline, you probably shouldn't be using .OBJ in the first place. For comparison, blender loads that same .OBJ in 21 seconds. But of course blender is doing a lot more than just parsing it. But anyways, it's pretty fast.
This was a fun side project for me. It was fun to go back to writing code in a "from scratch" style, and hopefully this will be useful to someone. At least, I'm pretty sure that I'll never have to write another OBJ parser again for my own stuff, which is worth it right there.
Let me know if you have any feedback or suggestions, through email, or a comment on this post, or on twitter (@joeld42). Suggestions or pull requests are welcome!