const std = @import("std"); const formats = @import("formats"); const asset_manifest = @import("asset_manifest"); const Vector3 = formats.Vector3; const c = @cImport({ @cInclude("assimp/cimport.h"); @cInclude("assimp/scene.h"); @cInclude("assimp/mesh.h"); @cInclude("assimp/postprocess.h"); }); const ASSET_MAX_BYTES = 1024 * 1024 * 1024; const AssetType = enum { Mesh, Shader, ShaderProgram, }; pub fn resolveAssetTypeByExtension(path: []const u8) ?AssetType { if (std.mem.endsWith(u8, path, ".obj")) { return .Mesh; } if (std.mem.endsWith(u8, path, ".prog")) { return .ShaderProgram; } if (std.mem.endsWith(u8, path, ".glsl")) { return .Shader; } return null; } pub fn main() !void { const allocator = std.heap.c_allocator; const argv = std.os.argv; if (argv.len < 3) { std.log.err("usage assetc \n", .{}); return error.MissingArgs; } const input = argv[argv.len - 2]; const output = std.mem.span(argv[argv.len - 1]); const asset_type = resolveAssetTypeByExtension(std.mem.span(input)) orelse return error.UnknownAssetType; switch (asset_type) { .Mesh => try processMesh(allocator, input, output), .ShaderProgram => try processShaderProgram(allocator, std.mem.span(input), output), else => return error.DontProcessShaders, } } fn processMesh(allocator: std.mem.Allocator, input: [*:0]const u8, output: []const u8) !void { const maybe_scene: ?*const c.aiScene = @ptrCast(c.aiImportFile( input, c.aiProcess_CalcTangentSpace | c.aiProcess_Triangulate | c.aiProcess_JoinIdenticalVertices | c.aiProcess_SortByPType | c.aiProcess_GenNormals, )); if (maybe_scene == null) { std.log.err("assimp import error: {s}\n", .{c.aiGetErrorString()}); return error.ImportFailed; } const scene = maybe_scene.?; defer c.aiReleaseImport(scene); if (scene.mNumMeshes == 0) return error.NoMeshes; if (scene.mNumMeshes > 1) return error.TooManyMeshes; const mesh: *c.aiMesh = @ptrCast(scene.mMeshes[0]); if (mesh.mNormals == null) return error.MissingNormals; var vertices = try allocator.alloc(Vector3, @intCast(mesh.mNumVertices)); var normals = try allocator.alloc(Vector3, @intCast(mesh.mNumVertices)); var indices = try allocator.alloc(u16, @intCast(mesh.mNumFaces * 3)); // triangles for (0..mesh.mNumVertices) |i| { vertices[i] = .{ .x = mesh.mVertices[i].x, .y = mesh.mVertices[i].y, .z = mesh.mVertices[i].z, }; normals[i] = .{ .x = mesh.mNormals[i].x, .y = mesh.mNormals[i].y, .z = mesh.mNormals[i].z, }; } for (0..mesh.mNumFaces) |i| { std.debug.assert(mesh.mFaces[i].mNumIndices == 3); for (0..3) |j| { indices[i * 3 + j] = @intCast(mesh.mFaces[i].mIndices[j]); } } const out_mesh = formats.Mesh{ .vertices = vertices, .normals = normals, .indices = indices, }; const out_file = try std.fs.createFileAbsolute(output, .{}); defer out_file.close(); var buf_writer = std.io.bufferedWriter(out_file.writer()); try formats.writeMesh( buf_writer.writer(), out_mesh, formats.native_endian, // TODO: use target endiannes ); try buf_writer.flush(); } fn processShaderProgram(allocator: std.mem.Allocator, absolute_input: []const u8, output: []const u8) !void { var cwd_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; const cwd_path = try std.os.getcwd(&cwd_buf); const input = try std.fs.path.relative(allocator, cwd_path, absolute_input); defer allocator.free(input); const input_dir = std.fs.path.dirname(input).?; var file_contents: []u8 = undefined; { const input_file = try std.fs.cwd().openFile(input, .{}); defer input_file.close(); file_contents = try input_file.readToEndAlloc(allocator, ASSET_MAX_BYTES); } defer allocator.free(file_contents); const ShaderProgram = struct { vertex: []const u8, fragment: []const u8, }; const program = try std.json.parseFromSlice(ShaderProgram, allocator, file_contents, .{}); defer program.deinit(); const vertex_path = try std.fs.path.resolve(allocator, &.{ input_dir, program.value.vertex }); const vertex_asset_id = asset_manifest.getAssetByPath(vertex_path); if (vertex_asset_id == 0) { std.log.debug("{s}\n", .{vertex_path}); return error.InvalidVertexAssetPath; } const frag_path = try std.fs.path.resolve(allocator, &.{ input_dir, program.value.fragment }); defer allocator.free(frag_path); const frag_asset_id = asset_manifest.getAssetByPath(frag_path); if (vertex_asset_id == 0) { std.log.debug("{s}\n", .{frag_path}); return error.InvalidFragmentAssetPath; } const out_file = try std.fs.createFileAbsolute(output, .{}); defer out_file.close(); var buf_writer = std.io.bufferedWriter(out_file.writer()); try formats.writeShaderProgram(buf_writer.writer(), vertex_asset_id, frag_asset_id, formats.native_endian); try buf_writer.flush(); }