diff --git a/src/main.zig b/src/main.zig index 22821d7..4629dd1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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; diff --git a/src/mtl.zig b/src/mtl.zig index c488a96..e26fc75 100644 --- a/src/mtl.zig +++ b/src/mtl.zig @@ -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, @@ -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 { @@ -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 }; } diff --git a/src/obj.zig b/src/obj.zig index b64097e..cb20a7c 100644 --- a/src/obj.zig +++ b/src/obj.zig @@ -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, @@ -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) = .{}, @@ -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 = @@ -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); diff --git a/src/utils.zig b/src/utils.zig new file mode 100644 index 0000000..4610830 --- /dev/null +++ b/src/utils.zig @@ -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 }; +}