Skip to content
LinkedInGitHubYouTube

Learning Zig Basics with an OBJ Parser

Projects, Zig, 3D Parsing, JavaScript, PixiJS4 min read

Why I Built It

I wanted to learn Zig, a low-level language that’s all about performance and control. I’ve been into JavaScript for a while, but I wanted to see how things work under the hood. So, I thought, why not build something cool? I picked an OBJ parser because I’m into 3D graphics and wanted to see how to read those files. It’s not done yet, but it’s a start.

How I Started It

I set up a project with Zig files—main.zig to run it, root.zig for the parser—and added example files (cube.obj, cube.mtl) from Blender. The OBJ file has lines like:

v 1.0 1.0 1.0 # A vertex at (1,1,1)
f 1 2 3 # A triangle face

I wanted to read those and pull out the points. In Zig, I started with something like this in main.zig:

const std = @import("std");
pub fn main() !void {
const allocator = std.heap.page_allocator;
const file = try std.fs.cwd().openFile("examples/cube.obj", .{});
defer file.close();
const content = try file.readToEndAlloc(allocator, 1024 * 1024);
defer allocator.free(content);
std.debug.print("File content: {s}\n", .{content});
}

It just opens the file and prints it—super basic. I had no clue about Zig’s try or defer at first, so I kept breaking it—like forgetting defer and wondering why it leaked memory. Testing it with zig run src/main.zig showed me the file’s text, and that’s when I got excited to parse it.

Trying to Parse It

I moved to root.zig to split the OBJ lines. I wanted to grab vertices (the v lines) and faces (the f lines). Here’s a bit I worked on:

const std = @import("std");
pub const Vertex = struct {
x: f32,
y: f32,
z: f32,
};
pub fn parseOBJ(content: []const u8, allocator: std.mem.Allocator) ![]Vertex {
var vertices = std.ArrayList(Vertex).init(allocator);
defer vertices.deinit();
var lines = std.mem.split(u8, content, "\n");
while (lines.next()) |line| {
if (std.mem.startsWith(u8, line, "v ")) {
var parts = std.mem.split(u8, line[2..], " ");
const x = try std.fmt.parseFloat(f32, parts.next().?);
const y = try std.fmt.parseFloat(f32, parts.next().?);
const z = try std.fmt.parseFloat(f32, parts.next().?);
try vertices.append(Vertex{ .x = x, .y = y, .z = z });
}
}
return vertices.toOwnedSlice();
}

This grabs v lines—like v 1.0 2.0 3.0—and turns them into a Vertex struct. I tested it with my cube file, but it crashed on empty lines. I had to add checks—like if (line.len > 2)—and that’s when I started getting how Zig makes you handle errors yourself.

Why Zig?

I could’ve stuck with JavaScript—it’s easier with stuff like:

const vertices = [];
const lines = file.split('\n');
lines.forEach(line => {
if (line.startsWith('v ')) {
const [_, x, y, z] = line.split(' ').map(parseFloat);
vertices.push({ x, y, z });
}
});

But Zig forced me to think about memory. In JS, I didn’t care—garbage collection handled it. In Zig, I had to use allocator and defer to free stuff, like vertices.deinit(). I broke it a bunch—forgot to free memory, got crashes—and fixed it by reading the docs. It was a pain, but I liked seeing how low-level stuff works.

Adding PixiJS for Fun

I didn’t stop at Zig—I wanted to see the shapes. So I added a JavaScript part with PixiJS (from my Pong project). I’d parse in Zig, save it to a binary file (not done yet), then load it in JS:

const app = new PIXI.Application({ width: 800, height: 600 });
document.body.appendChild(app.view);
const graphics = new PIXI.Graphics();
graphics.beginFill(0xffffff);
graphics.drawPolygon([100, 100, 200, 200, 300, 100]); // A test triangle
app.stage.addChild(graphics);

I tested it with a hardcoded triangle—worked fine—but I haven’t linked the Zig output yet. I broke the PixiJS part by messing up coordinates (e.g., 300, -100 went off-screen), fixed it, and learned how 3D points turn into visuals.

What I Learned

This wasn’t about finishing a perfect parser—it’s half-baked right now. I built it to learn Zig basics: how it handles memory, errors, and structs. I got that by crashing it—like forgetting try and seeing error: expected type 'void'—and fixing it step-by-step. The OBJ part taught me 3D files aren’t magic—just text I can break down. And PixiJS showed me how parsing leads to stuff you can see. It’s a mess, but I had fun figuring it out. Check the repo at github.com/BradleyMatera/obj-parser—it’s got my Zig code, examples, and notes.

Why It Matters

I’m not a pro—this is me learning by doing. Zig’s tough, but it made me think about what’s under the hood, not just slap code together. I want to keep going—maybe finish the binary output or parse faces next—but for now, it’s a real start to understanding 3D and a new language.

© 2025 by Bradley's Portfolio. All rights reserved.
Theme by LekoArts