Skip to content

Commit

Permalink
Update the MTL parser for the more general streaming API
Browse files Browse the repository at this point in the history
This was straightforward, just had to be more careful with memory since it has
to dupe strings out of the input stream.
  • Loading branch information
bens committed Feb 24, 2024
1 parent b2d577b commit 9e58dab
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 91 deletions.
1 change: 1 addition & 0 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub const ObjData = obj.ObjData;
pub const Mesh = obj.Mesh;

pub const parseMtl = mtl.parse;
pub const parseMtlCustom = mtl.parseCustom;
pub const MaterialData = mtl.MaterialData;
pub const Material = mtl.Material;

Expand Down
182 changes: 121 additions & 61 deletions src/mtl.zig
Original file line number Diff line number Diff line change
@@ -1,17 +1,93 @@
const std = @import("std");
const tokenizeAny = std.mem.tokenizeAny;
const Allocator = std.mem.Allocator;

const parseFloat = std.fmt.parseFloat;
const parseInt = std.fmt.parseInt;
const lineIterator = @import("utils.zig").lineIterator;

pub const MaterialData = struct {
materials: std.StringHashMapUnmanaged(Material),

pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
var iter = self.materials.iterator();
while (iter.next()) |m| {
m.value_ptr.deinit(allocator);
allocator.free(m.key_ptr.*);
}
self.materials.deinit(allocator);
}

const Builder = struct {
allocator: Allocator,
current_material: Material = .{},
current_name: ?[]const u8 = null,
materials: std.StringHashMapUnmanaged(Material) = .{},

fn onError(self: *Builder) void {
var iter = self.materials.iterator();
while (iter.next()) |m| {
m.value_ptr.deinit(self.allocator);
self.allocator.free(m.key_ptr.*);
}
self.materials.deinit(self.allocator);
if (self.current_name) |n|
self.allocator.free(n);
}

fn finish(self: *Builder) !MaterialData {
if (self.current_name) |nm|
try self.materials.put(self.allocator, nm, self.current_material);
return MaterialData{ .materials = self.materials };
}

fn new_material(self: *Builder, name: []const u8) !void {
if (self.current_name) |n| {
try self.materials.put(
self.allocator,
n,
self.current_material,
);
self.current_material = Material{};
}
self.current_name = try self.allocator.dupe(u8, name);
}
fn ambient_color(self: *Builder, rgb: [3]f32) !void {
self.current_material.ambient_color = rgb;
}
fn diffuse_color(self: *Builder, rgb: [3]f32) !void {
self.current_material.diffuse_color = rgb;
}
fn specular_color(self: *Builder, rgb: [3]f32) !void {
self.current_material.specular_color = rgb;
}
fn specular_highlight(self: *Builder, v: f32) !void {
self.current_material.specular_highlight = v;
}
fn emissive_coefficient(self: *Builder, rgb: [3]f32) !void {
self.current_material.emissive_coefficient = rgb;
}
fn optical_density(self: *Builder, v: f32) !void {
self.current_material.optical_density = v;
}
fn dissolve(self: *Builder, v: f32) !void {
self.current_material.dissolve = v;
}
fn illumination(self: *Builder, v: u8) !void {
self.current_material.illumination = v;
}
fn bump_map_path(self: *Builder, path: []const u8) !void {
self.current_material.bump_map_path = try self.allocator.dupe(u8, path);
}
fn diffuse_map_path(self: *Builder, path: []const u8) !void {
self.current_material.diffuse_map_path = try self.allocator.dupe(u8, path);
}
fn specular_map_path(self: *Builder, path: []const u8) !void {
self.current_material.specular_map_path = try self.allocator.dupe(u8, path);
}
};
};

// NOTE: I'm not sure which material statements are optional. For now, I'm assuming all of them are.
// NOTE: I'm not sure which material statements are optional. For now, I'm
// assuming all of them are.
pub const Material = struct {
ambient_color: ?[3]f32 = null,
diffuse_color: ?[3]f32 = null,
Expand All @@ -25,6 +101,12 @@ pub const Material = struct {
bump_map_path: ?[]const u8 = null,
diffuse_map_path: ?[]const u8 = null,
specular_map_path: ?[]const u8 = null,

pub fn deinit(self: *Material, allocator: Allocator) void {
if (self.bump_map_path) |p| allocator.free(p);
if (self.diffuse_map_path) |p| allocator.free(p);
if (self.specular_map_path) |p| allocator.free(p);
}
};

const Keyword = enum {
Expand All @@ -43,73 +125,51 @@ const Keyword = enum {
specular_map_path,
};

pub fn parse(allocator: std.mem.Allocator, data: []const u8) !MaterialData {
var materials = std.StringHashMapUnmanaged(Material){};

var lines = std.mem.tokenize(u8, data, "\r\n");
var current_material = Material{};
var name: ?[]const u8 = null;

while (lines.next()) |line| {
var words = std.mem.tokenize(u8, line, " ");
const keyword = try parseKeyword(words.next().?);
pub fn parse(allocator: Allocator, data: []const u8) !MaterialData {
var b = MaterialData.Builder{ .allocator = allocator };
errdefer b.onError();
var fbs = std.io.fixedBufferStream(data);
return try parseCustom(MaterialData, &b, fbs.reader());
}

switch (keyword) {
pub fn parseCustom(comptime T: type, b: *T.Builder, reader: anytype) !T {
var buffer: [128]u8 = undefined;
var lines = lineIterator(reader, &buffer);
while (try lines.next()) |line| {
var iter = tokenizeAny(u8, line, " ");
const def_type =
if (iter.next()) |tok| try parseKeyword(tok) else continue;
switch (def_type) {
.comment => {},
.new_material => {
if (name) |n| {
try materials.put(allocator, n, current_material);
current_material = Material{};
}
name = words.next().?;
},
.ambient_color => {
current_material.ambient_color = try parseVec3(&words);
},
.diffuse_color => {
current_material.diffuse_color = try parseVec3(&words);
},
.specular_color => {
current_material.specular_color = try parseVec3(&words);
},
.specular_highlight => {
current_material.specular_highlight = try parseFloat(f32, words.next().?);
},
.emissive_coefficient => {
current_material.emissive_coefficient = try parseVec3(&words);
},
.optical_density => {
current_material.optical_density = try parseFloat(f32, words.next().?);
},
.dissolve => {
current_material.dissolve = try parseFloat(f32, words.next().?);
},
.illumination => {
current_material.illumination = try parseInt(u8, words.next().?, 10);
},
.bump_map_path => {
current_material.bump_map_path = words.next().?;
},
.diffuse_map_path => {
current_material.diffuse_map_path = words.next().?;
},
.specular_map_path => {
current_material.specular_map_path = words.next().?;
},
.new_material => try b.new_material(iter.next().?),
.ambient_color => try b.ambient_color(try parseVec3(&iter)),
.diffuse_color => try b.diffuse_color(try parseVec3(&iter)),
.specular_color => try b.specular_color(try parseVec3(&iter)),
.specular_highlight => try b.specular_highlight(try parseF32(&iter)),
.emissive_coefficient => try b.emissive_coefficient(try parseVec3(&iter)),
.optical_density => try b.optical_density(try parseF32(&iter)),
.dissolve => try b.dissolve(try parseF32(&iter)),
.illumination => try b.illumination(try parseU8(&iter)),
.bump_map_path => try b.bump_map_path(iter.next().?),
.diffuse_map_path => try b.diffuse_map_path(iter.next().?),
.specular_map_path => try b.specular_map_path(iter.next().?),
}
}
return try b.finish();
}

if (name) |n| {
try materials.put(allocator, n, current_material);
}
fn parseU8(iter: *std.mem.TokenIterator(u8, .any)) !u8 {
return try std.fmt.parseInt(u8, iter.next().?, 10);
}

return MaterialData{ .materials = materials };
fn parseF32(iter: *std.mem.TokenIterator(u8, .any)) !f32 {
return try std.fmt.parseFloat(f32, iter.next().?);
}

fn parseVec3(iter: *std.mem.TokenIterator(u8, .any)) ![3]f32 {
const x = try parseFloat(f32, iter.next().?);
const y = try parseFloat(f32, iter.next().?);
const z = try parseFloat(f32, iter.next().?);
const x = try std.fmt.parseFloat(f32, iter.next().?);
const y = try std.fmt.parseFloat(f32, iter.next().?);
const z = try std.fmt.parseFloat(f32, iter.next().?);
return [_]f32{ x, y, z };
}

Expand Down
35 changes: 5 additions & 30 deletions src/obj.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const assert = std.debug.assert;
const parseFloat = std.fmt.parseFloat;
const parseInt = std.fmt.parseInt;

const lineIterator = @import("utils.zig").lineIterator;

pub const ObjData = struct {
material_libs: []const []const u8,

Expand All @@ -29,7 +31,7 @@ pub const ObjData = struct {
}

const Builder = struct {
allocator: std.mem.Allocator,
allocator: Allocator,
material_libs: ArrayListUnmanaged([]const u8) = .{},
vertices: ArrayListUnmanaged(f32) = .{},
tex_coords: ArrayListUnmanaged(f32) = .{},
Expand Down Expand Up @@ -258,9 +260,9 @@ pub fn parse(allocator: Allocator, data: []const u8) !ObjData {
return try parseCustom(ObjData, &b, fbs.reader());
}

pub fn parseCustom(comptime T: type, b: *T.Builder, rdr: anytype) !T {
pub fn parseCustom(comptime T: type, b: *T.Builder, reader: anytype) !T {
var buffer: [128]u8 = undefined;
var lines = lineIterator(rdr, &buffer);
var lines = lineIterator(reader, &buffer);
while (try lines.next()) |line| {
var iter = tokenizeAny(u8, line, " ");
const def_type =
Expand Down Expand Up @@ -303,33 +305,6 @@ pub fn parseCustom(comptime T: type, b: *T.Builder, rdr: anytype) !T {
return try b.finish();
}

fn LineIterator(comptime Reader: type) type {
return struct {
buffer: []u8,
reader: Reader,

fn next(self: *@This()) !?[]const u8 {
var fbs = std.io.fixedBufferStream(self.buffer);
self.reader.streamUntilDelimiter(
fbs.writer(),
'\n',
fbs.buffer.len,
) catch |err| switch (err) {
error.EndOfStream => if (fbs.getWritten().len == 0) return null,
else => |e| return e,
};
var line = fbs.getWritten();
if (0 < line.len and line[line.len - 1] == '\r')
line = line[0 .. line.len - 1];
return line;
}
};
}

fn lineIterator(rdr: anytype, buffer: []u8) LineIterator(@TypeOf(rdr)) {
return .{ .buffer = buffer, .reader = rdr };
}

fn parseOptionalIndex(v: []const u8, n_items: usize) !?u32 {
if (std.mem.eql(u8, v, "")) return null;
const i = try parseInt(i32, v, 10);
Expand Down
28 changes: 28 additions & 0 deletions src/utils.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const std = @import("std");

pub fn LineIterator(comptime Reader: type) type {
return struct {
buffer: []u8,
reader: Reader,

pub fn next(self: *@This()) !?[]const u8 {
var fbs = std.io.fixedBufferStream(self.buffer);
self.reader.streamUntilDelimiter(
fbs.writer(),
'\n',
fbs.buffer.len,
) catch |err| switch (err) {
error.EndOfStream => if (fbs.getWritten().len == 0) return null,
else => |e| return e,
};
var line = fbs.getWritten();
if (0 < line.len and line[line.len - 1] == '\r')
line = line[0 .. line.len - 1];
return line;
}
};
}

pub fn lineIterator(rdr: anytype, buffer: []u8) LineIterator(@TypeOf(rdr)) {
return .{ .buffer = buffer, .reader = rdr };
}

0 comments on commit 9e58dab

Please sign in to comment.