diff --git a/.gitignore b/.gitignore index 8d1d2c9..d557e94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ zig-cache zig-out *.blend1 -src/assets/models/emulator +src/assets/models/emulator.glb diff --git a/.gitmodules b/.gitmodules index e746f0d..8f61326 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libs/raylib"] path = libs/raylib url = https://github.com/ryupold/raylib.zig +[submodule "libs/zgltf"] + path = libs/zgltf + url = https://github.com/kooparse/zgltf.git diff --git a/build.zig b/build.zig index b0a26ec..cce1d8b 100644 --- a/build.zig +++ b/build.zig @@ -15,6 +15,10 @@ pub fn build(b: *std.Build) !void { .target = target }); + exe.addModule("zgltf", b.createModule(.{ + .source_file = .{ .path = "libs/zgltf/src/main.zig" }, + })); + // Provide filenames of all files in 'src/ROMs' to program as options { var files = std.ArrayList([]const u8).init(b.allocator); @@ -41,9 +45,9 @@ pub fn build(b: *std.Build) !void { build_models.addFileArg(.{ .path = "src/assets/models/emulator.blend" }); build_models.addArg("--background"); build_models.addArg("--python"); - build_models.addFileArg(.{ .path = "src/assets/models/export_obj.py" }); + build_models.addFileArg(.{ .path = "src/assets/models/export.py" }); build_models.addArg("--"); - build_models.addArg("src/assets/models/emulator"); + build_models.addArg("src/assets/models/emulator.glb"); build_models_step.dependOn(&build_models.step); exe.step.dependOn(build_models_step); diff --git a/build.zig.zon b/build.zig.zon index 48dd11b..0526bd5 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,5 +6,9 @@ .url = "https://github.com/raysan5/raylib/archive/6094869e3e845e90e1e8ae41b98e889fb3e13e78.tar.gz", .hash = "12203b7a16bcf8d7fe4c9990a46d92b6f2e35531a4b82eb3bdf8ba4a0dbcc5f21415", }, + .zgltf = .{ + .url = "https://github.com/kooparse/zgltf/archive/f9ed05023db75484333b6c7125a8c02a99cf3a14.tar.gz", + .hash = "12207cef14c513e160a039ddd470437ed0cd663e37a5710f9cc36334c3d21dc19ffe", + }, }, } diff --git a/libs/zgltf b/libs/zgltf new file mode 160000 index 0000000..f9ed050 --- /dev/null +++ b/libs/zgltf @@ -0,0 +1 @@ +Subproject commit f9ed05023db75484333b6c7125a8c02a99cf3a14 diff --git a/src/assets/models/emulator.blend b/src/assets/models/emulator.blend index 9c909f5..86038ef 100644 Binary files a/src/assets/models/emulator.blend and b/src/assets/models/emulator.blend differ diff --git a/src/assets/models/export.py b/src/assets/models/export.py new file mode 100644 index 0000000..8203786 --- /dev/null +++ b/src/assets/models/export.py @@ -0,0 +1,13 @@ +import bpy +import sys + +argv = sys.argv +argv = argv[argv.index("--") + 1:] +assert len(argv) >= 1 + +output_path = argv[0] +assert output_path.endswith(".gltf") or output_path.endswith(".glb") +bpy.ops.export_scene.gltf( + filepath=output_path, + use_visible=True, +) diff --git a/src/assets/models/export_obj.py b/src/assets/models/export_obj.py deleted file mode 100644 index e7de3ac..0000000 --- a/src/assets/models/export_obj.py +++ /dev/null @@ -1,128 +0,0 @@ -import bpy -from os import path -import os -from dataclasses import dataclass -import shutil -import sys - -@dataclass -class OBJMetadata: - materials: list[str] - objects: list[str] - -D = bpy.data -C = bpy.context - -def select_one_object(obj): - bpy.ops.object.select_all(action='DESELECT') - bpy.context.view_layer.objects.active = obj # type: ignore - obj.select_set(True) - -def iter_buttons(): - object_names = D.objects.keys() - assert object_names - for name in object_names: - if name.startswith("Buttons "): - yield D.objects[name] - -def extract_metadata(obj_path): - mtl_filename = None - objects = [] - with open(obj_path, "r") as f: - for line in f.readlines(): - if line.startswith("mtllib"): - mtl_filename = line.removeprefix("mtllib ") - elif line.startswith("o"): - object_name = line.strip().removeprefix("o ") - objects.append(object_name) - - materials = [] - if mtl_filename: - mtl_path = path.join(path.dirname(obj_path), mtl_filename).strip() - with open(mtl_path, "r") as f: - for line in f.readlines(): - if not line.startswith("newmtl"): continue - material_name = line.strip().removeprefix("newmtl ") - materials.append(material_name) - - return OBJMetadata(materials, objects) - -def write_list_to_file(filename, values): - with open(filename, "w") as f: - for i in range(len(values)): - if i > 0: f.write("\n") - f.writelines(values[i]) - -def main(output_folder): - if not path.exists(output_folder): - os.mkdir(output_folder) - - export_options = { - "use_triangles": True, - "use_materials": True, - "use_normals": True, - "use_vertex_groups": True, - "path_mode": "RELATIVE", - } - - bpy.ops.object.select_all(action="SELECT") - for btn in iter_buttons(): - btn.select_set(False) - - bpy.ops.export_scene.obj( - filepath=path.join(output_folder, "static_model.obj"), - use_selection=True, - **export_options - ) - - metadata = extract_metadata(path.join(output_folder, "static_model.obj")) - write_list_to_file(path.join(output_folder, "static_model.mtls.txt"), metadata.materials) - # write_list_to_file("static_model.objs.txt", metadata.objects) - - if not path.exists(path.join(output_folder, "buttons")): - os.mkdir(path.join(output_folder, "buttons")) - - object_names = D.objects.keys() - assert object_names - for name in object_names: - if name.startswith("Buttons "): - button_name = name.removeprefix("Buttons ") - button_obj_path = path.join(output_folder, f"buttons/Button {button_name}.obj") - button_mtl_path = path.join(output_folder, f"buttons/Button {button_name}.mtl") - select_one_object(D.objects[name]) - bpy.ops.export_scene.obj( - filepath=button_obj_path, - use_selection=True, - **export_options - ) - - tmp_file = "/tmp/blender_export" - with open(button_obj_path, "r") as src: - with open(tmp_file, "w") as dst: - for line in src.readlines(): - if line.startswith("mtllib"): - dst.write("mtllib Button.mtl\n") - else: - dst.write(line) - os.remove(button_obj_path) - shutil.move(tmp_file, button_obj_path) - - if button_name == "0": - os.rename(path.join(output_folder, "buttons/Button 0.mtl"), path.join(output_folder, "buttons/Button.mtl")) - else: - os.remove(button_mtl_path) - - power_switch = D.objects["Power switch"] - select_one_object(power_switch) - bpy.ops.export_scene.obj( - filepath=path.join(output_folder, "power-switch.obj"), - use_selection=True, - **export_options - ) - -argv = sys.argv -argv = argv[argv.index("--") + 1:] -assert len(argv) >= 1 -main(*argv) - - diff --git a/src/emulator-model.zig b/src/emulator-model.zig new file mode 100644 index 0000000..426daca --- /dev/null +++ b/src/emulator-model.zig @@ -0,0 +1,319 @@ +const Self = @This(); +const Gltf = @import("zgltf"); +const rl = @import("raylib"); +const std = @import("std"); +const RaylibChip = @import("raylib-chip.zig"); + +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; + +allocator: Allocator, + +materials: []rl.Material, + +models: std.ArrayList(rl.Model), +buttons: [16]rl.Model, +power_switch: rl.Model, + +bbox: rl.BoundingBox, +position: rl.Vector3, +screen_texture: rl.RenderTexture2D, + +rl_chip: *const RaylibChip, + +fn getExtensionByMimeType(mime_type: []const u8) ?[:0]const u8 { + if (std.mem.eql(u8, mime_type, "image/png")) { + return ".png\x00"; + } else if (std.mem.eql(u8, mime_type, "image/jpg")) { + return ".jpg\x00"; + } else { + return null; + } +} + +fn loadGltfImage(image: Gltf.Image) rl.Image { + assert(image.uri == null); + assert(image.data != null); + assert(image.mime_type != null); + + const image_data = image.data.?; + const mime_type = image.mime_type.?; + + const file_type = getExtensionByMimeType(mime_type); + assert(file_type != null); + + return rl.LoadImageFromMemory(file_type.?, @ptrCast(image_data.ptr), @intCast(image_data.len)); +} + +fn loadGltfMaterial(data: Gltf.Data, material: Gltf.Material) !rl.Material { + var rl_material = rl.LoadMaterialDefault(); + var albedo_map: *rl.MaterialMap = &rl_material.maps.?[@intFromEnum(rl.MaterialMapIndex.MATERIAL_MAP_ALBEDO)]; + + if (material.metallic_roughness.base_color_texture) |base_color_texture| { + const texture = data.textures.items[base_color_texture.index]; + const image = data.images.items[texture.source.?]; + + const rl_image = loadGltfImage(image); + defer rl.UnloadImage(rl_image); + albedo_map.texture = rl.LoadTextureFromImage(rl_image); + } + + albedo_map.color.r = @intFromFloat(material.metallic_roughness.base_color_factor[0]*255); + albedo_map.color.g = @intFromFloat(material.metallic_roughness.base_color_factor[1]*255); + albedo_map.color.b = @intFromFloat(material.metallic_roughness.base_color_factor[2]*255); + albedo_map.color.a = @intFromFloat(material.metallic_roughness.base_color_factor[3]*255); + + return rl_material; +} + +fn loadGltfPrimitive(allocator: Allocator, gltf: Gltf, primitive: Gltf.Primitive) !rl.Mesh { + assert(primitive.mode == .triangles); + var rl_mesh = std.mem.zeroes(rl.Mesh); + + var bin = gltf.glb_binary.?; + var f32_buffer = std.ArrayList(f32).init(allocator); + defer f32_buffer.deinit(); + + for (primitive.attributes.items) |attribute| { + switch (attribute) { + .position => |accessor_index| { + const accessor = gltf.data.accessors.items[accessor_index]; + assert(accessor.component_type == .float); + assert(accessor.type == .vec3); + f32_buffer.clearAndFree(); + gltf.getDataFromBufferView(f32, &f32_buffer, accessor, bin); + + var vertices = try allocator.dupe(f32, f32_buffer.items); + rl_mesh.vertexCount = @intCast(accessor.count); + rl_mesh.vertices = @ptrCast(vertices); + }, + .normal => |accessor_index| { + const accessor = gltf.data.accessors.items[accessor_index]; + assert(accessor.component_type == .float); + assert(accessor.type == .vec3); + f32_buffer.clearRetainingCapacity(); + gltf.getDataFromBufferView(f32, &f32_buffer, accessor, bin); + + var normals = try allocator.dupe(f32, f32_buffer.items); + rl_mesh.normals = @ptrCast(normals); + }, + .tangent => |accessor_index| { + const accessor = gltf.data.accessors.items[accessor_index]; + assert(accessor.component_type == .float); + assert(accessor.type == .vec4); + f32_buffer.clearRetainingCapacity(); + gltf.getDataFromBufferView(f32, &f32_buffer, accessor, bin); + + var tangents = try allocator.dupe(f32, f32_buffer.items); + rl_mesh.tangents = @ptrCast(tangents); + }, + .texcoord => |accessor_index| { + const accessor = gltf.data.accessors.items[accessor_index]; + assert(accessor.component_type == .float); + assert(accessor.type == .vec2); + f32_buffer.clearRetainingCapacity(); + gltf.getDataFromBufferView(f32, &f32_buffer, accessor, bin); + + var texcoords = try allocator.dupe(f32, f32_buffer.items); + rl_mesh.texcoords = @ptrCast(texcoords); + }, + else => {} + } + } + + if (primitive.indices) |accessor_index| { + const accessor = gltf.data.accessors.items[accessor_index]; + rl_mesh.triangleCount = @divExact(accessor.count, 3); + const accessor_count: usize = @intCast(accessor.count); + + var indices = try allocator.alloc(u16, accessor_count); + rl_mesh.indices = @ptrCast(indices); + + if (accessor.component_type == Gltf.ComponentType.unsigned_short) { + var u16_buffer = std.ArrayList(u16).init(allocator); + defer u16_buffer.deinit(); + gltf.getDataFromBufferView(u16, &u16_buffer, accessor, bin); + + @memcpy(indices, u16_buffer.items); + } else if (accessor.component_type == Gltf.ComponentType.unsigned_integer) { + var u32_buffer = std.ArrayList(u32).init(allocator); + defer u32_buffer.deinit(); + gltf.getDataFromBufferView(u32, &u32_buffer, accessor, bin); + + for (0..accessor_count) |i| { + indices[i] = @truncate(u32_buffer.items[i]); + } + rl.TraceLog(@intFromEnum(rl.TraceLogLevel.LOG_WARNING), "MODEL: Indices data converted from u32 to u16, possible loss of data"); + } else { + @panic("Unknown GLTF primitives indices component type. Use u16 or u32"); + } + } else { + rl_mesh.triangleCount = @divExact(rl_mesh.vertexCount, 3); + } + + rl.UploadMesh(@ptrCast(&rl_mesh), false); + return rl_mesh; +} + +pub fn init(allocator: Allocator, rl_chip: *const RaylibChip) !Self { + var gltf = Gltf.init(allocator); + defer gltf.deinit(); + try gltf.parse(@embedFile("assets/models/emulator.glb")); + + const scene = gltf.data.scenes.items[gltf.data.scene.?]; + const scene_nodes: std.ArrayList(Gltf.Index) = scene.nodes.?; + + const material_count: usize = @intCast(gltf.data.materials.items.len); + var materials = try allocator.alloc(rl.Material, material_count + 1); + materials[0] = rl.LoadMaterialDefault(); + for (0..material_count, gltf.data.materials.items) |i, material| { + materials[i+1] = try loadGltfMaterial(gltf.data, material); + } + + const screen_texture = rl.LoadRenderTexture(rl_chip.chip.display_width, rl_chip.chip.display_height); + + var models = try std.ArrayList(rl.Model).initCapacity(allocator, scene_nodes.items.len); + errdefer models.deinit(); + + var buttons: [16]rl.Model = undefined; + var power_switch: rl.Model = undefined; + + for (scene_nodes.items) |node_index| { + const node = gltf.data.nodes.items[node_index]; + if (node.mesh == null) continue; + + const transform = Gltf.getGlobalTransform(&gltf.data, node); + + var model = std.mem.zeroes(rl.Model); + model.transform = rl.Matrix{ + .m0 = transform[0][0], + .m4 = transform[1][0], + .m8 = transform[2][0], + .m12 = transform[3][0], + + .m1 = transform[0][1], + .m5 = transform[1][1], + .m9 = transform[2][1], + .m13 = transform[3][1], + + .m2 = transform[0][2], + .m6 = transform[1][2], + .m10 = transform[2][2], + .m14 = transform[3][2], + + .m3 = transform[0][3], + .m7 = transform[1][3], + .m11 = transform[2][3], + .m15 = transform[3][3] + }; + model.materials = @ptrCast(materials.ptr); + model.materialCount = @intCast(materials.len); + + const mesh = gltf.data.meshes.items[node.mesh.?]; + const primitives: []Gltf.Primitive = mesh.primitives.items; + + var meshes = try allocator.alloc(rl.Mesh, primitives.len); + var mesh_material = try allocator.alloc(i32, primitives.len); + model.meshes = @ptrCast(meshes.ptr); + model.meshCount = @intCast(primitives.len); + model.meshMaterial = @ptrCast(mesh_material.ptr); + @memset(mesh_material, 0); + + for (0.., primitives) |j, primitive| { + meshes[j] = try loadGltfPrimitive(allocator, gltf, primitive); + + if (primitive.material) |mtl| { + mesh_material[j] = @intCast(mtl+1); + } + } + + if (std.mem.eql(u8, node.name, "Power switch")) { + power_switch = model; + } else if (std.mem.startsWith(u8, node.name, "Buttons ")) { + var space = std.mem.indexOfScalar(u8, node.name, ' ').?; + const button_idx = try std.fmt.parseInt(usize, node.name[space+1..], 16); + buttons[button_idx] = model; + } else { + models.appendAssumeCapacity(model); + } + } + + { // Link screen render target to shader + var screen_mtl_idx: ?usize = null; + for (1.., gltf.data.materials.items) |idx, mtl| { + if (std.mem.eql(u8, mtl.name, "Screen")) { + screen_mtl_idx = idx; + break; + } + } + rl.SetMaterialTexture(@ptrCast(&materials[screen_mtl_idx.?]), rl.MATERIAL_MAP_DIFFUSE, screen_texture.texture); + } + + return Self{ + .allocator = allocator, + .materials = materials, + + .models = models, + .buttons = buttons, + .power_switch = power_switch, + + .bbox = rl.GetModelBoundingBox(models.items[0]), + .screen_texture = screen_texture, + .position = rl.Vector3{ .x = 0, .y = 0, .z = 0 }, + .rl_chip = rl_chip, + }; +} + +pub fn setShader(self: *Self, shader: rl.Shader) void { + for (self.materials) |*mtl| { + mtl.shader = shader; + } +} + +pub fn deinit(self: *Self) void { + self.allocator.free(self.materials); + + // rl.UnloadModel(self.static_model); + // for (self.button_models) |btn_model| { + // rl.UnloadModel(btn_model); + // } + rl.UnloadRenderTexture(self.screen_texture); +} + +pub fn updateDisplay(self: *Self) void { + rl.BeginTextureMode(self.screen_texture); + self.rl_chip.render(); + rl.EndTextureMode(); +} + +pub fn isMouseOverPowerSwitch(self: *const Self) bool { + _ = self; + return false; +} + +pub fn setPowerSwitch(self: *const Self, enabled: bool) void { + _ = enabled; + _ = self; + return false; +} + +pub fn getPowerSwitch(self: *const Self) bool { + _ = self; + return false; +} + +pub fn draw(self: *Self) void { + for (self.buttons, 0..) |button, i| { + var position = self.position; + if (self.rl_chip.chip.is_input_pressed(@intCast(i))) { + position.z += 0.035; + } + rl.DrawModel(button, position, 1.0, rl.WHITE); + } + + for (self.models.items) |model| { + rl.DrawModel(model, self.position, 1.0, rl.WHITE); + } + + rl.DrawModel(self.power_switch, self.position, 1.0, rl.WHITE); +} diff --git a/src/main-scene.zig b/src/main-scene.zig index 8cbd9bb..164c1f6 100644 --- a/src/main-scene.zig +++ b/src/main-scene.zig @@ -1,7 +1,9 @@ const Self = @This(); const rl = @import("raylib"); const std = @import("std"); +const Gltf = @import("zgltf"); const GlobalContext = @import("./global-context.zig"); +const EmulatorModel = @import("./emulator-model.zig"); const ChipContext = @import("chip.zig"); const RaylibChip = @import("raylib-chip.zig"); @@ -13,11 +15,6 @@ const StringList = std.ArrayList([]const u8); ctx: *GlobalContext, allocator: Allocator, -// model: rl.Model, -// model_bbox: rl.BoundingBox, -// model_position: rl.Vector3, -// model_buttons: [16]rl.Model, - emulator: EmulatorModel, camera_turn_vel: rl.Vector3 = rl.Vector3{ .x = 0, .y = 0, .z = 0 }, @@ -31,9 +28,7 @@ chip: *ChipContext, raylib_chip: *RaylibChip, chip_sound: rl.Sound, -// screen_texture: rl.RenderTexture2D, - -pub fn gen_sin_wave(wave: *rl.Wave, frequency: f32) void { +pub fn genSinWave(wave: *rl.Wave, frequency: f32) void { assert(wave.sampleSize == 16); // Only 16 bits are supported const sample_rate: f32 = @floatFromInt(wave.sampleRate); @@ -115,7 +110,7 @@ const Light = struct { } }; -fn get_camera_projection(camera: *const rl.Camera3D) rl.Matrix { +fn getCameraProjection(camera: *const rl.Camera3D) rl.Matrix { const screen_width: f32 = @floatFromInt(rl.GetScreenWidth()); const screen_height: f32 = @floatFromInt(rl.GetScreenHeight()); @@ -132,7 +127,7 @@ fn get_camera_projection(camera: *const rl.Camera3D) rl.Matrix { } } -fn get_screen_direction_from_camera(mat_proj: *const rl.Matrix, mat_view: *const rl.Matrix, point: rl.Vector2) rl.Vector3 { +fn getScreenDirectionFromCamera(mat_proj: *const rl.Matrix, mat_view: *const rl.Matrix, point: rl.Vector2) rl.Vector3 { const screen_width: f32 = @floatFromInt(rl.GetScreenWidth()); const screen_height: f32 = @floatFromInt(rl.GetScreenHeight()); @@ -145,7 +140,7 @@ fn get_screen_direction_from_camera(mat_proj: *const rl.Matrix, mat_view: *const return rl.Vector3Subtract(far_point, near_point).normalize(); } -fn get_preffered_distance_to_box(camera: *const rl.Camera3D, box: rl.BoundingBox) f32 { +fn getPrefferedDistanceToBox(camera: *const rl.Camera3D, box: rl.BoundingBox) f32 { const screen_width: f32 = @floatFromInt(rl.GetScreenWidth()); const screen_height: f32 = @floatFromInt(rl.GetScreenHeight()); const margin = @min(screen_width, screen_height)*0.1; @@ -156,106 +151,19 @@ fn get_preffered_distance_to_box(camera: *const rl.Camera3D, box: rl.BoundingBox // const model_screen_width = box_size.x * max_model_scale; const model_screen_height = box_size.y * max_model_scale; - const mat_proj = get_camera_projection(camera); + const mat_proj = getCameraProjection(camera); const mat_view = rl.MatrixIdentity(); // rl.MatrixLookAt(camera.position, camera.target, camera.up); const screen_middle = rl.Vector2{ .x = screen_width/2, .y = screen_height/2 }; const box_top_middle = screen_middle.add(.{ .y = -model_screen_height/2 }); - const middle_dir = get_screen_direction_from_camera(&mat_proj, &mat_view, screen_middle); - const top_middle_dir = get_screen_direction_from_camera(&mat_proj, &mat_view, box_top_middle); + const middle_dir = getScreenDirectionFromCamera(&mat_proj, &mat_view, screen_middle); + const top_middle_dir = getScreenDirectionFromCamera(&mat_proj, &mat_view, box_top_middle); const angle = top_middle_dir.angleBetween(middle_dir); const distance = 1/@tan(angle) * (box_size.y/2) + box_size.z/4; return distance; } -fn find_line_in_file(filename: []const u8, target: []const u8) !?usize { - const file = try std.fs.cwd().openFile(filename, .{ .mode = .read_only }); - defer file.close(); - - const reader = file.reader(); - var buf: [512]u8 = undefined; - var i: usize = 0; - while (true) { - const line = try reader.readUntilDelimiterOrEof(&buf, '\n'); - if (line == null) { continue; } - - if (std.mem.eql(u8, line.?, target)) { - return i; - } - i += 1; - } - - return null; -} - -const EmulatorModel = struct { - static_model: rl.Model, - bbox: rl.BoundingBox, - button_models: [16]rl.Model, - - position: rl.Vector3, - screen_texture: rl.RenderTexture2D, - - rl_chip: *const RaylibChip, - - pub fn init(comptime location: []const u8, rl_chip: *const RaylibChip, shader: rl.Shader) !EmulatorModel { - var button_models = [1]rl.Model{ undefined } ** 16; - inline for ([_][]const u8{ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" }, 0..) |letter, i| { - button_models[i] = rl.LoadModel(location ++ "/buttons/Button " ++ letter ++ ".obj"); - } - - var static_model = rl.LoadModel(location ++ "/static_model.obj"); - - for (0..@intCast(static_model.materialCount)) |i| { - static_model.materials.?[i].shader = shader; - } - for (button_models) |btn_model| { - for (0..@intCast(btn_model.materialCount)) |i| { - btn_model.materials.?[i].shader = shader; - } - } - - const screen_texture = rl.LoadRenderTexture(rl_chip.chip.display_width, rl_chip.chip.display_height); - const screen_mtl_idx = try find_line_in_file(location ++ "/static_model.mtls.txt", "Screen"); - rl.SetMaterialTexture(@ptrCast(&static_model.materials.?[screen_mtl_idx.?]), rl.MATERIAL_MAP_DIFFUSE, screen_texture.texture); - - return EmulatorModel{ - .static_model = static_model, - .bbox = rl.GetModelBoundingBox(static_model), - .button_models = button_models, - .screen_texture = screen_texture, - .position = rl.Vector3{ .x = 0, .y = 0, .z = 0 }, - .rl_chip = rl_chip - }; - } - - pub fn deinit(self: *EmulatorModel) void { - rl.UnloadModel(self.static_model); - for (self.button_models) |btn_model| { - rl.UnloadModel(btn_model); - } - rl.UnloadRenderTexture(self.screen_texture); - } - - pub fn update_display(self: *EmulatorModel) void { - rl.BeginTextureMode(self.screen_texture); - self.rl_chip.render(); - rl.EndTextureMode(); - } - - pub fn draw(self: *EmulatorModel) void { - rl.DrawModel(self.static_model, self.position, 1.0, rl.WHITE); - for (self.button_models, 0..) |btn_model, i| { - var position = self.position; - if (self.rl_chip.chip.is_input_pressed(@intCast(i))) { - position.z += 0.035; - } - rl.DrawModel(btn_model, position, 1.0, rl.WHITE); - } - } -}; - pub fn init(allocator: Allocator, ctx: *GlobalContext) !Self { const shader = rl.LoadShader("src/shaders/main.vs", "src/shaders/main.fs"); shader.locs.?[@intFromEnum(rl.ShaderLocationIndex.SHADER_LOC_VECTOR_VIEW)] = rl.GetShaderLocation(shader, "viewPos"); @@ -280,17 +188,20 @@ pub fn init(allocator: Allocator, ctx: *GlobalContext) !Self { .data = @ptrCast(data.ptr), }; - gen_sin_wave(&chip_wave, 440); + genSinWave(&chip_wave, 440); var chip_sound = rl.LoadSoundFromWave(chip_wave); rl.SetSoundVolume(chip_sound, 0.2); var raylib_chip = try allocator.create(RaylibChip); raylib_chip.* = RaylibChip.init(chip, chip_sound); + var emulator = try EmulatorModel.init(allocator, raylib_chip); + emulator.setShader(shader); + return Self { .allocator = allocator, .ctx = ctx, - .emulator = try EmulatorModel.init("src/assets/models/emulator", raylib_chip, shader), + .emulator = emulator, .shader = shader, .lights = .{light1, light2}, @@ -308,19 +219,19 @@ pub fn deinit(self: *Self) void { self.allocator.destroy(self.chip); } -fn update_camera(self: *Self, dt: f32) void { +fn updateCamera(self: *Self, dt: f32) void { const mouse_delta = rl.GetMouseDelta(); const camera = &self.ctx.camera; const emulator = &self.emulator; if (rl.IsWindowResized()) { - const distance = get_preffered_distance_to_box(camera, emulator.bbox); + const distance = getPrefferedDistanceToBox(camera, emulator.bbox); const direction = camera.position.sub(emulator.position).normalize(); camera.position = emulator.position.add(direction.scale(distance)); } if (rl.Vector3Equals(camera.position, rl.Vector3Zero()) == 1) { - const distance = get_preffered_distance_to_box(camera, self.emulator.bbox); + const distance = getPrefferedDistanceToBox(camera, self.emulator.bbox); camera.target = emulator.position; camera.position = emulator.position.sub(rl.Vector3.new(0, 0, 1).scale(distance)); } @@ -383,7 +294,7 @@ fn update_camera(self: *Self, dt: f32) void { } pub fn update(self: *Self, dt: f32) void { - self.update_camera(dt); + self.updateCamera(dt); const camera = &self.ctx.camera; const cameraPos = [3]f32{ camera.position.x, camera.position.y, camera.position.z }; @@ -392,7 +303,7 @@ pub fn update(self: *Self, dt: f32) void { light.update_values(self.shader); } - self.emulator.update_display(); + self.emulator.updateDisplay(); // { // var matProj = rl.MatrixIdentity(); @@ -444,5 +355,9 @@ pub fn update(self: *Self, dt: f32) void { } pub fn draw(self: *Self) void { - self.emulator.draw(); + rl.BeginShaderMode(self.shader); + { + self.emulator.draw(); + } + rl.EndShaderMode(); } diff --git a/src/main.zig b/src/main.zig index 57bccb5..9ec66c7 100755 --- a/src/main.zig +++ b/src/main.zig @@ -19,9 +19,11 @@ const ROM = struct { }; pub fn main() anyerror!void { - const memory = try std.heap.page_allocator.alloc(u8, megabytes(5)); - var fba = std.heap.FixedBufferAllocator.init(memory); - const allocator = fba.allocator(); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + defer { + // if (gpa.deinit() == .leak) @panic("Leaked memory"); + } rl.SetConfigFlags(rl.ConfigFlags{ .FLAG_WINDOW_RESIZABLE = true }); rl.InitWindow(1024, 720, "CHIP-8");