Skip to content

Commit

Permalink
Merge pull request #4 from bens/streaming-api-mtl
Browse files Browse the repository at this point in the history
Update the MTL parser for the more general streaming API
  • Loading branch information
chip2n authored Mar 1, 2024
2 parents b2d577b + 9e58dab commit ca8c101
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 ca8c101

Please sign in to comment.