Skip to content

Commit 9e58dab

Browse files
committed
Update the MTL parser for the more general streaming API
This was straightforward, just had to be more careful with memory since it has to dupe strings out of the input stream.
1 parent b2d577b commit 9e58dab

File tree

4 files changed

+155
-91
lines changed

4 files changed

+155
-91
lines changed

src/main.zig

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub const ObjData = obj.ObjData;
99
pub const Mesh = obj.Mesh;
1010

1111
pub const parseMtl = mtl.parse;
12+
pub const parseMtlCustom = mtl.parseCustom;
1213
pub const MaterialData = mtl.MaterialData;
1314
pub const Material = mtl.Material;
1415

src/mtl.zig

+121-61
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,93 @@
11
const std = @import("std");
2+
const tokenizeAny = std.mem.tokenizeAny;
3+
const Allocator = std.mem.Allocator;
24

3-
const parseFloat = std.fmt.parseFloat;
4-
const parseInt = std.fmt.parseInt;
5+
const lineIterator = @import("utils.zig").lineIterator;
56

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

910
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
11+
var iter = self.materials.iterator();
12+
while (iter.next()) |m| {
13+
m.value_ptr.deinit(allocator);
14+
allocator.free(m.key_ptr.*);
15+
}
1016
self.materials.deinit(allocator);
1117
}
18+
19+
const Builder = struct {
20+
allocator: Allocator,
21+
current_material: Material = .{},
22+
current_name: ?[]const u8 = null,
23+
materials: std.StringHashMapUnmanaged(Material) = .{},
24+
25+
fn onError(self: *Builder) void {
26+
var iter = self.materials.iterator();
27+
while (iter.next()) |m| {
28+
m.value_ptr.deinit(self.allocator);
29+
self.allocator.free(m.key_ptr.*);
30+
}
31+
self.materials.deinit(self.allocator);
32+
if (self.current_name) |n|
33+
self.allocator.free(n);
34+
}
35+
36+
fn finish(self: *Builder) !MaterialData {
37+
if (self.current_name) |nm|
38+
try self.materials.put(self.allocator, nm, self.current_material);
39+
return MaterialData{ .materials = self.materials };
40+
}
41+
42+
fn new_material(self: *Builder, name: []const u8) !void {
43+
if (self.current_name) |n| {
44+
try self.materials.put(
45+
self.allocator,
46+
n,
47+
self.current_material,
48+
);
49+
self.current_material = Material{};
50+
}
51+
self.current_name = try self.allocator.dupe(u8, name);
52+
}
53+
fn ambient_color(self: *Builder, rgb: [3]f32) !void {
54+
self.current_material.ambient_color = rgb;
55+
}
56+
fn diffuse_color(self: *Builder, rgb: [3]f32) !void {
57+
self.current_material.diffuse_color = rgb;
58+
}
59+
fn specular_color(self: *Builder, rgb: [3]f32) !void {
60+
self.current_material.specular_color = rgb;
61+
}
62+
fn specular_highlight(self: *Builder, v: f32) !void {
63+
self.current_material.specular_highlight = v;
64+
}
65+
fn emissive_coefficient(self: *Builder, rgb: [3]f32) !void {
66+
self.current_material.emissive_coefficient = rgb;
67+
}
68+
fn optical_density(self: *Builder, v: f32) !void {
69+
self.current_material.optical_density = v;
70+
}
71+
fn dissolve(self: *Builder, v: f32) !void {
72+
self.current_material.dissolve = v;
73+
}
74+
fn illumination(self: *Builder, v: u8) !void {
75+
self.current_material.illumination = v;
76+
}
77+
fn bump_map_path(self: *Builder, path: []const u8) !void {
78+
self.current_material.bump_map_path = try self.allocator.dupe(u8, path);
79+
}
80+
fn diffuse_map_path(self: *Builder, path: []const u8) !void {
81+
self.current_material.diffuse_map_path = try self.allocator.dupe(u8, path);
82+
}
83+
fn specular_map_path(self: *Builder, path: []const u8) !void {
84+
self.current_material.specular_map_path = try self.allocator.dupe(u8, path);
85+
}
86+
};
1287
};
1388

14-
// NOTE: I'm not sure which material statements are optional. For now, I'm assuming all of them are.
89+
// NOTE: I'm not sure which material statements are optional. For now, I'm
90+
// assuming all of them are.
1591
pub const Material = struct {
1692
ambient_color: ?[3]f32 = null,
1793
diffuse_color: ?[3]f32 = null,
@@ -25,6 +101,12 @@ pub const Material = struct {
25101
bump_map_path: ?[]const u8 = null,
26102
diffuse_map_path: ?[]const u8 = null,
27103
specular_map_path: ?[]const u8 = null,
104+
105+
pub fn deinit(self: *Material, allocator: Allocator) void {
106+
if (self.bump_map_path) |p| allocator.free(p);
107+
if (self.diffuse_map_path) |p| allocator.free(p);
108+
if (self.specular_map_path) |p| allocator.free(p);
109+
}
28110
};
29111

30112
const Keyword = enum {
@@ -43,73 +125,51 @@ const Keyword = enum {
43125
specular_map_path,
44126
};
45127

46-
pub fn parse(allocator: std.mem.Allocator, data: []const u8) !MaterialData {
47-
var materials = std.StringHashMapUnmanaged(Material){};
48-
49-
var lines = std.mem.tokenize(u8, data, "\r\n");
50-
var current_material = Material{};
51-
var name: ?[]const u8 = null;
52-
53-
while (lines.next()) |line| {
54-
var words = std.mem.tokenize(u8, line, " ");
55-
const keyword = try parseKeyword(words.next().?);
128+
pub fn parse(allocator: Allocator, data: []const u8) !MaterialData {
129+
var b = MaterialData.Builder{ .allocator = allocator };
130+
errdefer b.onError();
131+
var fbs = std.io.fixedBufferStream(data);
132+
return try parseCustom(MaterialData, &b, fbs.reader());
133+
}
56134

57-
switch (keyword) {
135+
pub fn parseCustom(comptime T: type, b: *T.Builder, reader: anytype) !T {
136+
var buffer: [128]u8 = undefined;
137+
var lines = lineIterator(reader, &buffer);
138+
while (try lines.next()) |line| {
139+
var iter = tokenizeAny(u8, line, " ");
140+
const def_type =
141+
if (iter.next()) |tok| try parseKeyword(tok) else continue;
142+
switch (def_type) {
58143
.comment => {},
59-
.new_material => {
60-
if (name) |n| {
61-
try materials.put(allocator, n, current_material);
62-
current_material = Material{};
63-
}
64-
name = words.next().?;
65-
},
66-
.ambient_color => {
67-
current_material.ambient_color = try parseVec3(&words);
68-
},
69-
.diffuse_color => {
70-
current_material.diffuse_color = try parseVec3(&words);
71-
},
72-
.specular_color => {
73-
current_material.specular_color = try parseVec3(&words);
74-
},
75-
.specular_highlight => {
76-
current_material.specular_highlight = try parseFloat(f32, words.next().?);
77-
},
78-
.emissive_coefficient => {
79-
current_material.emissive_coefficient = try parseVec3(&words);
80-
},
81-
.optical_density => {
82-
current_material.optical_density = try parseFloat(f32, words.next().?);
83-
},
84-
.dissolve => {
85-
current_material.dissolve = try parseFloat(f32, words.next().?);
86-
},
87-
.illumination => {
88-
current_material.illumination = try parseInt(u8, words.next().?, 10);
89-
},
90-
.bump_map_path => {
91-
current_material.bump_map_path = words.next().?;
92-
},
93-
.diffuse_map_path => {
94-
current_material.diffuse_map_path = words.next().?;
95-
},
96-
.specular_map_path => {
97-
current_material.specular_map_path = words.next().?;
98-
},
144+
.new_material => try b.new_material(iter.next().?),
145+
.ambient_color => try b.ambient_color(try parseVec3(&iter)),
146+
.diffuse_color => try b.diffuse_color(try parseVec3(&iter)),
147+
.specular_color => try b.specular_color(try parseVec3(&iter)),
148+
.specular_highlight => try b.specular_highlight(try parseF32(&iter)),
149+
.emissive_coefficient => try b.emissive_coefficient(try parseVec3(&iter)),
150+
.optical_density => try b.optical_density(try parseF32(&iter)),
151+
.dissolve => try b.dissolve(try parseF32(&iter)),
152+
.illumination => try b.illumination(try parseU8(&iter)),
153+
.bump_map_path => try b.bump_map_path(iter.next().?),
154+
.diffuse_map_path => try b.diffuse_map_path(iter.next().?),
155+
.specular_map_path => try b.specular_map_path(iter.next().?),
99156
}
100157
}
158+
return try b.finish();
159+
}
101160

102-
if (name) |n| {
103-
try materials.put(allocator, n, current_material);
104-
}
161+
fn parseU8(iter: *std.mem.TokenIterator(u8, .any)) !u8 {
162+
return try std.fmt.parseInt(u8, iter.next().?, 10);
163+
}
105164

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

109169
fn parseVec3(iter: *std.mem.TokenIterator(u8, .any)) ![3]f32 {
110-
const x = try parseFloat(f32, iter.next().?);
111-
const y = try parseFloat(f32, iter.next().?);
112-
const z = try parseFloat(f32, iter.next().?);
170+
const x = try std.fmt.parseFloat(f32, iter.next().?);
171+
const y = try std.fmt.parseFloat(f32, iter.next().?);
172+
const z = try std.fmt.parseFloat(f32, iter.next().?);
113173
return [_]f32{ x, y, z };
114174
}
115175

src/obj.zig

+5-30
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const assert = std.debug.assert;
77
const parseFloat = std.fmt.parseFloat;
88
const parseInt = std.fmt.parseInt;
99

10+
const lineIterator = @import("utils.zig").lineIterator;
11+
1012
pub const ObjData = struct {
1113
material_libs: []const []const u8,
1214

@@ -29,7 +31,7 @@ pub const ObjData = struct {
2931
}
3032

3133
const Builder = struct {
32-
allocator: std.mem.Allocator,
34+
allocator: Allocator,
3335
material_libs: ArrayListUnmanaged([]const u8) = .{},
3436
vertices: ArrayListUnmanaged(f32) = .{},
3537
tex_coords: ArrayListUnmanaged(f32) = .{},
@@ -258,9 +260,9 @@ pub fn parse(allocator: Allocator, data: []const u8) !ObjData {
258260
return try parseCustom(ObjData, &b, fbs.reader());
259261
}
260262

261-
pub fn parseCustom(comptime T: type, b: *T.Builder, rdr: anytype) !T {
263+
pub fn parseCustom(comptime T: type, b: *T.Builder, reader: anytype) !T {
262264
var buffer: [128]u8 = undefined;
263-
var lines = lineIterator(rdr, &buffer);
265+
var lines = lineIterator(reader, &buffer);
264266
while (try lines.next()) |line| {
265267
var iter = tokenizeAny(u8, line, " ");
266268
const def_type =
@@ -303,33 +305,6 @@ pub fn parseCustom(comptime T: type, b: *T.Builder, rdr: anytype) !T {
303305
return try b.finish();
304306
}
305307

306-
fn LineIterator(comptime Reader: type) type {
307-
return struct {
308-
buffer: []u8,
309-
reader: Reader,
310-
311-
fn next(self: *@This()) !?[]const u8 {
312-
var fbs = std.io.fixedBufferStream(self.buffer);
313-
self.reader.streamUntilDelimiter(
314-
fbs.writer(),
315-
'\n',
316-
fbs.buffer.len,
317-
) catch |err| switch (err) {
318-
error.EndOfStream => if (fbs.getWritten().len == 0) return null,
319-
else => |e| return e,
320-
};
321-
var line = fbs.getWritten();
322-
if (0 < line.len and line[line.len - 1] == '\r')
323-
line = line[0 .. line.len - 1];
324-
return line;
325-
}
326-
};
327-
}
328-
329-
fn lineIterator(rdr: anytype, buffer: []u8) LineIterator(@TypeOf(rdr)) {
330-
return .{ .buffer = buffer, .reader = rdr };
331-
}
332-
333308
fn parseOptionalIndex(v: []const u8, n_items: usize) !?u32 {
334309
if (std.mem.eql(u8, v, "")) return null;
335310
const i = try parseInt(i32, v, 10);

src/utils.zig

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const std = @import("std");
2+
3+
pub fn LineIterator(comptime Reader: type) type {
4+
return struct {
5+
buffer: []u8,
6+
reader: Reader,
7+
8+
pub fn next(self: *@This()) !?[]const u8 {
9+
var fbs = std.io.fixedBufferStream(self.buffer);
10+
self.reader.streamUntilDelimiter(
11+
fbs.writer(),
12+
'\n',
13+
fbs.buffer.len,
14+
) catch |err| switch (err) {
15+
error.EndOfStream => if (fbs.getWritten().len == 0) return null,
16+
else => |e| return e,
17+
};
18+
var line = fbs.getWritten();
19+
if (0 < line.len and line[line.len - 1] == '\r')
20+
line = line[0 .. line.len - 1];
21+
return line;
22+
}
23+
};
24+
}
25+
26+
pub fn lineIterator(rdr: anytype, buffer: []u8) LineIterator(@TypeOf(rdr)) {
27+
return .{ .buffer = buffer, .reader = rdr };
28+
}

0 commit comments

Comments
 (0)