diff --git a/.gitmodules b/.gitmodules index 71c171f..41ef811 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "libs/zgltf"] - path = libs/zgltf - url = https://github.com/kooparse/zgltf.git [submodule "libs/raylib/raylib"] path = libs/raylib/raylib url = git@github.com:raysan5/raylib.git diff --git a/build.zig b/build.zig index dcc40eb..3fa86d6 100644 --- a/build.zig +++ b/build.zig @@ -15,10 +15,6 @@ 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); @@ -39,20 +35,6 @@ pub fn build(b: *std.Build) !void { raylib.addTo(b, exe, target, optimize, .{}); - { - var build_models_step = b.step("models", "Export .blend files"); - var build_models = b.addSystemCommand(&[_][]const u8{ "blender" }); - 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.py" }); - build_models.addArg("--"); - build_models.addArg("src/assets/models/emulator.glb"); - - build_models_step.dependOn(&build_models.step); - exe.step.dependOn(build_models_step); - } - const run_cmd = b.addRunArtifact(exe); const run_step = b.step("run", "Run chip8-zig"); diff --git a/libs/zgltf b/libs/zgltf deleted file mode 160000 index f9ed050..0000000 --- a/libs/zgltf +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f9ed05023db75484333b6c7125a8c02a99cf3a14 diff --git a/src/assets/models/Buttons texture.png b/src/assets/models/Buttons texture.png deleted file mode 100644 index a1ae914..0000000 Binary files a/src/assets/models/Buttons texture.png and /dev/null differ diff --git a/src/assets/models/emulator.blend b/src/assets/models/emulator.blend deleted file mode 100644 index cbb6d49..0000000 Binary files a/src/assets/models/emulator.blend and /dev/null differ diff --git a/src/assets/models/export.py b/src/assets/models/export.py deleted file mode 100644 index 8203786..0000000 --- a/src/assets/models/export.py +++ /dev/null @@ -1,13 +0,0 @@ -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/screen-texture.png b/src/assets/models/screen-texture.png deleted file mode 100644 index 14fd8f7..0000000 Binary files a/src/assets/models/screen-texture.png and /dev/null differ diff --git a/src/emulator-model.zig b/src/emulator-model.zig deleted file mode 100644 index e5ad74d..0000000 --- a/src/emulator-model.zig +++ /dev/null @@ -1,523 +0,0 @@ -const Self = @This(); -const Gltf = @import("zgltf"); -const rl = @import("raylib"); -const std = @import("std"); -const RaylibChip = @import("raylib-chip.zig"); -const Light = @import("./light.zig"); - -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; - -allocator: Allocator, - -materials: []rl.Material, - -models: std.ArrayList(rl.Model), -static_models: std.ArrayList(*rl.Model), -buttons: [16]*rl.Model, -power_switch: *rl.Model, -power_light_position: rl.Vector3, - -bbox: rl.BoundingBox, -position: rl.Vector3, - -button_state: *[16]bool, -powered: *bool, - -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); - - const rl_image = rl.LoadImageFromMemory(file_type.?, @ptrCast(image_data.ptr), @intCast(image_data.len)); - if (@as(?*anyopaque, @ptrCast(rl_image.data)) == null) { - return error.Failed; - } - return rl_image; -} - -fn loadGltfMaterial(data: Gltf.Data, material: Gltf.Material) !rl.Material { - var rl_material = rl.LoadMaterialDefault(); - errdefer rl.UnloadMaterial(rl_material); - - 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 = try 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(gltf: Gltf, primitive: Gltf.Primitive) !rl.Mesh { - var allocator = std.heap.c_allocator; - - assert(primitive.mode == .triangles); - var rl_mesh = std.mem.zeroes(rl.Mesh); - errdefer rl.UnloadMesh(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; -} - -fn loadGltfMesh(materials: []rl.Material, gltf: Gltf, node: Gltf.Node) !rl.Model { - const allocator = std.heap.c_allocator; - const transform = Gltf.getGlobalTransform(&gltf.data, node); - - var model = std.mem.zeroes(rl.Model); - errdefer rl.UnloadModel(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] - }; - - if (node.mesh) |mesh_idx| { - const mesh = gltf.data.meshes.items[mesh_idx]; - const primitives: []Gltf.Primitive = mesh.primitives.items; - - var meshes = try allocator.alloc(rl.Mesh, primitives.len); - model.meshCount = @intCast(primitives.len); - model.meshes = @ptrCast(meshes.ptr); - - var mesh_material = try allocator.alloc(i32, primitives.len); - model.meshMaterial = @ptrCast(mesh_material.ptr); - @memset(mesh_material, 0); - - var used_material_ids = try allocator.alloc(usize, materials.len); - var used_materials_count: usize = 0; - defer allocator.free(used_material_ids); - - for (0.., primitives) |j, primitive| { - meshes[j] = try loadGltfPrimitive(gltf, primitive); - - var mtl: usize = 0; - if (primitive.material) |material| { - mtl = material + 1; - } - - var mtl_index = std.mem.indexOfScalar(usize, used_material_ids[0..used_materials_count], mtl); - if (mtl_index == null) { - mtl_index = used_materials_count; - used_material_ids[used_materials_count] = mtl; - used_materials_count += 1; - } - - mesh_material[j] = @intCast(mtl_index.?); - } - - var used_materials = try allocator.alloc(rl.Material, used_materials_count+1); - model.materials = @ptrCast(used_materials); - model.materialCount = 0; - for (0..used_materials_count) |i| { - used_materials[i] = materials[used_material_ids[i]]; - - const max_material_maps = 12; - const maps = try allocator.dupe(rl.MaterialMap, used_materials[i].maps.?[0..max_material_maps]); - used_materials[i].maps = @ptrCast(maps); - model.materialCount += 1; - } - } - - return model; -} - -pub fn init(allocator: Allocator, powered: *bool, button_state: *[16]bool, screen_texture: rl.RenderTexture2D) !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); - @memset(materials, std.mem.zeroes(rl.Material)); - errdefer allocator.free(materials); - - materials[0] = rl.LoadMaterialDefault(); - errdefer { - for (materials) |mtl| { - rl.UnloadMaterial(mtl); - } - } - - for (0..material_count, gltf.data.materials.items) |i, material| { - materials[i+1] = try loadGltfMaterial(gltf.data, material); - } - - var models = try std.ArrayList(rl.Model).initCapacity(allocator, scene_nodes.items.len); - errdefer models.deinit(); - errdefer { - for (models.items) |model| { - rl.UnloadModel(model); - } - } - - var static_models = std.ArrayList(*rl.Model).init(allocator); - errdefer static_models.deinit(); - - - var buttons: [16]*rl.Model = undefined; - var power_switch: *rl.Model = undefined; - var power_light: ?*rl.Model = null; - - for (scene_nodes.items) |node_index| { - const node = gltf.data.nodes.items[node_index]; - if (node.mesh == null) continue; - - models.appendAssumeCapacity(try loadGltfMesh(materials, gltf, node)); - var model: *rl.Model = &models.items[models.items.len-1]; - - const name = node.name; - if (std.mem.eql(u8, name, "Power switch")) { - power_switch = model; - } else if (std.mem.startsWith(u8, name, "Buttons ")) { - var space = std.mem.indexOfScalar(u8, name, ' ').?; - const button_idx = try std.fmt.parseInt(usize, name[space+1..], 16); - buttons[button_idx] = model; - } else { - try static_models.append(model); - } - - if (std.mem.eql(u8, name, "Screen")) { - const screen_material = &model.materials.?[0]; - rl.SetMaterialTexture(@ptrCast(screen_material), rl.MATERIAL_MAP_DIFFUSE, screen_texture.texture); - } else if (std.mem.eql(u8, name, "Power indicator")) { - power_light = model; - } - } - - var power_light_position = rl.Vector3.zero(); - if (power_light) |model| { - power_light_position = matrixGetTranslation(model.transform); - } - - return Self{ - .allocator = allocator, - - .materials = materials, - - .static_models = static_models, - .models = models, - .buttons = buttons, - .power_switch = power_switch, - .powered = powered, - .button_state = button_state, - - .power_light_position = power_light_position, - .bbox = rl.GetModelBoundingBox(static_models.items[0].*), - .position = rl.Vector3{ .x = 0, .y = 0, .z = 0 }, - }; -} - -pub fn setShader(self: *Self, shader: rl.Shader) void { - for (self.models.items) |*model| { - if (model.materials == null) continue; - - for (0..@intCast(model.materialCount)) |i| { - model.materials.?[i].shader = shader; - } - } -} - -pub fn deinit(self: *Self) void { - for (self.models.items) |model| { - rl.UnloadModel(model); - } - for (self.materials) |mtl| { - rl.UnloadMaterial(mtl); - } - self.allocator.free(self.materials); - self.static_models.deinit(); - self.models.deinit(); -} - -fn getRayCollisionModel(ray: rl.Ray, model: rl.Model, position: rl.Vector3) rl.RayCollision { - var closest_hit_info = std.mem.zeroes(rl.RayCollision); - const transform = rl.MatrixMultiply(model.transform, rl.MatrixTranslate(position.x, position.y, position.z)); - - for (0..@intCast(model.meshCount)) |i| { - if (model.meshes == null) break; - const mesh = model.meshes.?[i]; - - const hit_info = rl.GetRayCollisionMesh(ray, mesh, transform); - if (!hit_info.hit) continue; - - if ((!closest_hit_info.hit) or (closest_hit_info.distance > hit_info.distance)) { - closest_hit_info = hit_info; - } - } - - return closest_hit_info; -} - -const ModelCollision = struct { collision: rl.RayCollision, model: *rl.Model }; -fn getRayCollisionModels(ray: rl.Ray, models: []rl.Model, position: rl.Vector3) ?ModelCollision { - var closest_hit_info: ?rl.RayCollision = null; - var closest_model: ?*rl.Model = null; - - for (models) |*model| { - const hit_info = getRayCollisionModel(ray, model.*, position); - if (!hit_info.hit) continue; - - if ((closest_hit_info == null) or (closest_hit_info.?.distance > hit_info.distance)) { - closest_hit_info = hit_info; - closest_model = model; - } - } - - if (closest_hit_info == null) { - return null; - } - - return ModelCollision{ - .collision = closest_hit_info.?, - .model = closest_model.? - }; -} - -pub fn isOverPowerSwitch(self: *const Self, ray: rl.Ray) bool { - const collision = getRayCollisionModels(ray, self.models.items, self.position); - if (collision) |c| { - return c.model == self.power_switch; - } - - return false; -} - -fn matrixGetTranslation(mat: rl.Matrix) rl.Vector3 { - return rl.Vector3{ - .x = mat.m12, - .y = mat.m13, - .z = mat.m14, - }; -} - -fn matrixGetScale(mat: rl.Matrix) rl.Vector3 { - return rl.Vector3{ - .x = rl.Vector3Length(rl.Vector3{ .x = mat.m0, .y = mat.m1, .z = mat.m2 }), - .y = rl.Vector3Length(rl.Vector3{ .x = mat.m4, .y = mat.m5, .z = mat.m6 }), - .z = rl.Vector3Length(rl.Vector3{ .x = mat.m8, .y = mat.m9, .z = mat.m10 }), - }; -} - -// https://stackoverflow.com/a/64336115 -fn matrixGetRotation(mat: rl.Matrix) rl.Vector3 { - const scale = matrixGetScale(mat); - const R11 = mat.m0 / scale.x; - const R12 = mat.m1 / scale.x; - const R13 = mat.m2 / scale.x; - - const R21 = mat.m4 / scale.y; - // const R22 = mat.m5 / scale.y; - // const R23 = mat.m6 / scale.y; - - const R31 = mat.m8 / scale.z; - const R32 = mat.m9 / scale.z; - const R33 = mat.m10 / scale.z; - - const asin = std.math.asin; - const cos = std.math.cos; - const atan2 = std.math.atan2; - const pi = rl.PI; - - var roll: f32 = undefined; - var pitch: f32 = undefined; - var yaw: f32 = undefined; - if (R31 != 1 and R31 != -1) { - const pitch_1 = -1 * asin(R31); - // const pitch_2 = pi - pitch_1; - const roll_1 = atan2(f32, R32 / cos(pitch_1), R33 / cos(pitch_1)); - // const roll_2 = atan2( R32 / cos(pitch_2) , R33 / cos(pitch_2)); - const yaw_1 = atan2(f32, R21 / cos(pitch_1), R11 / cos(pitch_1)); - // const yaw_2 = atan2( R21 / cos(pitch_2) , R11 / cos(pitch_2)); - - // IMPORTANT NOTE here, there is more than one solution but we choose the first for this case for simplicity ! - // You can insert your own domain logic here on how to handle both solutions appropriately (see the reference publication link for more info). - pitch = pitch_1; - roll = roll_1; - yaw = yaw_1; - } else { - yaw = 0; // anything (we default this to zero) - if (R31 == -1) { - pitch = pi / 2; - roll = yaw + atan2(f32, R12, R13); - } else { - pitch = -pi / 2; - roll = -1*yaw + atan2(f32, -1*R12, -1*R13); - } - } - - // convert from radians to degrees - roll = roll * rl.RAD2DEG; - pitch = pitch * rl.RAD2DEG; - yaw = yaw * rl.RAD2DEG; - - return rl.Vector3{ - .x = roll, - .y = pitch, - .z = yaw, - }; -} - -pub fn draw(self: *Self) void { - for (self.buttons, 0..) |button, i| { - var position = self.position; - if (self.button_state[i]) { - position.z += 0.035; - } - rl.DrawModel(button.*, position, 1.0, rl.WHITE); - } - - for (self.static_models.items) |model| { - rl.DrawModel(model.*, self.position, 1.0, rl.WHITE); - } - - { // Power switch - const on_angle: f32 = 45; - const off_angle: f32 = -45; - const target_angle = if (self.powered.*) on_angle else off_angle; - - var transform = self.power_switch.transform; - const rotation = matrixGetRotation(transform); - const dt = rl.GetFrameTime(); - const delta_angle = rotation.z - target_angle; - transform = rl.MatrixMultiply(rl.MatrixRotateZ((delta_angle * dt * 0.2)), transform); - - self.power_switch.transform = transform; - rl.DrawModel(self.power_switch.*, self.position, 1.0, rl.WHITE); - } -} - -pub fn get_power_light_position(self: *Self) rl.Vector3 { - return self.power_light_position.add(self.position); -} - -pub fn get_power_light_color(self: *Self) rl.Color { - _ = self; - return rl.GREEN; -} diff --git a/src/light.zig b/src/light.zig deleted file mode 100644 index edc61ab..0000000 --- a/src/light.zig +++ /dev/null @@ -1,88 +0,0 @@ -const Self = @This(); -const rl = @import("raylib"); -const std = @import("std"); - -pub const LightType = enum(i32) { - DIRECTIONAL = 0, - POINT = 1, -}; - -shader: rl.Shader, - -enabledLoc: i32, -typeLoc: i32, -positionLoc: i32, -targetLoc: i32, -colorLoc: i32, -intensityLoc: i32, - -fn getLightShaderLocation(shader: rl.Shader, idx: usize, comptime name: []const u8) i32 { - var buf: [128]u8 = undefined; - var fba = std.heap.FixedBufferAllocator.init(&buf); - const prop_name = std.fmt.allocPrintZ(fba.allocator(), "lights[{d}]." ++ name, .{idx}) catch unreachable; - return rl.GetShaderLocation(shader, prop_name); -} - -pub fn init(shader: rl.Shader, idx: usize) Self { - return Self{ - .shader = shader, - - .enabledLoc = getLightShaderLocation(shader, idx, "enabled"), - .typeLoc = getLightShaderLocation(shader, idx, "type"), - .positionLoc = getLightShaderLocation(shader, idx, "position"), - .targetLoc = getLightShaderLocation(shader, idx, "target"), - .colorLoc = getLightShaderLocation(shader, idx, "color"), - .intensityLoc = getLightShaderLocation(shader, idx, "intensity"), - }; -} - -pub fn set_directional(self: *const Self, color: rl.Color, position: rl.Vector3, intensity: f32, target: rl.Vector3) void { - self.set_type(.DIRECTIONAL); - self.set_intensity(intensity); - self.set_enabled(true); - self.set_position(position); - self.set_color(color); - self.set_target(target); -} - -pub fn set_point(self: *const Self, color: rl.Color, position: rl.Vector3, intensity: f32) void { - self.set_type(.POINT); - self.set_intensity(intensity); - self.set_enabled(true); - self.set_color(color); - self.set_position(position); -} - -pub fn set_enabled(self: *const Self, enabled: bool) void { - const enabled_i32: i32 = @intFromBool(enabled); - rl.SetShaderValue(self.shader, self.enabledLoc, &enabled_i32, .SHADER_UNIFORM_INT); -} - -pub fn set_type(self: *const Self, light_type: LightType) void { - const light_type_i32: i32 = @intFromEnum(light_type); - rl.SetShaderValue(self.shader, self.typeLoc, &light_type_i32, .SHADER_UNIFORM_INT); -} - -pub fn set_intensity(self: *const Self, intensity: f32) void { - rl.SetShaderValue(self.shader, self.intensityLoc, &intensity, .SHADER_UNIFORM_FLOAT); -} - -pub fn set_position(self: *const Self, pos: rl.Vector3) void { - const position = [3]f32{ pos.x, pos.y, pos.z }; - rl.SetShaderValue(self.shader, self.positionLoc, &position, .SHADER_UNIFORM_VEC3); -} - -pub fn set_target(self: *const Self, target: rl.Vector3) void { - const target_f32 = [3]f32{ target.x, target.y, target.z }; - rl.SetShaderValue(self.shader, self.targetLoc, &target_f32, .SHADER_UNIFORM_VEC3); -} - -pub fn set_color(self: *const Self, color: rl.Color) void { - const color_f32 = [4]f32{ - @as(f32, @floatFromInt(color.r)) / 255.0, - @as(f32, @floatFromInt(color.g)) / 255.0, - @as(f32, @floatFromInt(color.b)) / 255.0, - @as(f32, @floatFromInt(color.a)) / 255.0, - }; - rl.SetShaderValue(self.shader, self.colorLoc, &color_f32, .SHADER_UNIFORM_VEC4); -} diff --git a/src/main-scene.zig b/src/main-scene.zig index 4b9390b..6c72bcd 100644 --- a/src/main-scene.zig +++ b/src/main-scene.zig @@ -1,11 +1,7 @@ const Self = @This(); const rl = @import("raylib"); const std = @import("std"); -const Gltf = @import("zgltf"); -const EmulatorModel = @import("./emulator-model.zig"); const ROM = @import("./roms.zig").ROM; -const Light = @import("./light.zig"); -const ShaderCode = @import("./shader-code.zig"); const ChipContext = @import("chip.zig"); const RaylibChip = @import("raylib-chip.zig"); @@ -14,26 +10,12 @@ const assert = std.debug.assert; const Allocator = std.mem.Allocator; const StringList = std.ArrayList([]const u8); -const max_lights: u32 = 2; - allocator: Allocator, -emulator: EmulatorModel, - -camera_turn_vel: rl.Vector3 = rl.Vector3{ .x = 0, .y = 0, .z = 0 }, -camera_target_orientation: ?rl.Vector3 = null, -previous_click_time: f64 = 0.0, - -shader: rl.Shader, -lights: [max_lights]Light, - -powered: *bool, chip: *ChipContext, raylib_chip: *RaylibChip, chip_sound: rl.Sound, -screen_texture: rl.RenderTexture2D, rom: ?ROM = null, -camera: rl.Camera3D, pub fn genSinWave(wave: *rl.Wave, frequency: f32) void { assert(wave.sampleSize == 16); // Only 16 bits are supported @@ -50,83 +32,7 @@ pub fn genSinWave(wave: *rl.Wave, frequency: f32) void { } } -fn getCameraProjection(camera: *const rl.Camera3D) rl.Matrix { - const screen_width: f32 = @floatFromInt(rl.GetScreenWidth()); - const screen_height: f32 = @floatFromInt(rl.GetScreenHeight()); - - if (camera.projection == .CAMERA_PERSPECTIVE) { - return rl.MatrixPerspective(camera.fovy*rl.DEG2RAD, screen_width/screen_height, rl.RL_CULL_DISTANCE_NEAR, rl.RL_CULL_DISTANCE_FAR); - } else if (camera.projection == .CAMERA_ORTHOGRAPHIC) { - const aspect = screen_width/screen_height; - const top = camera.fovy/2.0; - const right = top*aspect; - - return rl.MatrixOrtho(-right, right, -top, top, rl.RL_CULL_DISTANCE_NEAR, rl.RL_CULL_DISTANCE_FAR); - } else { - unreachable; - } -} - -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()); - - const ndc_x = (2.0*point.x) / screen_width - 1.0; - const ndc_y = 1.0 - (2.0*point.y) / screen_height; - - var near_point = rl.Vector3Unproject(.{ .x = ndc_x, .y = ndc_y, .z = 0.0 }, mat_proj.*, mat_view.*); - var far_point = rl.Vector3Unproject(.{ .x = ndc_x, .y = ndc_y, .z = 1.0 }, mat_proj.*, mat_view.*); - - return rl.Vector3Subtract(far_point, near_point).normalize(); -} - -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; - - const box_size = box.max.sub(box.min); - - const max_model_scale = @min((screen_width-2*margin)/box_size.x, (screen_height-2*margin)/box_size.y); - // const model_screen_width = box_size.x * max_model_scale; - const model_screen_height = box_size.y * max_model_scale; - - 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 = 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; -} - pub fn init(allocator: Allocator) !Self { - const shader_code = try ShaderCode.init(allocator, max_lights); - defer shader_code.deinit(); - const shader = rl.LoadShaderFromMemory(shader_code.vertex, shader_code.fragment); - errdefer rl.UnloadShader(shader); - - if (shader.id == rl.rlGetShaderIdDefault()) { - return error.CompileShader; - } - - shader.locs.?[@intFromEnum(rl.ShaderLocationIndex.SHADER_LOC_VECTOR_VIEW)] = rl.GetShaderLocation(shader, "viewPos"); - - const ambientLoc = rl.GetShaderLocation(shader, "ambient"); - rl.SetShaderValue(shader, ambientLoc, &[4]f32{ 0.6, 0.6, 1, 1.0 }, .SHADER_UNIFORM_VEC4); - - var lights: [max_lights]Light = undefined; - for (0..max_lights) |i| { - lights[i] = Light.init(shader, i); - } - - lights[0].set_directional(rl.WHITE, rl.Vector3.new(0.2, 0, -0.2), 1, rl.Vector3.zero()); - lights[1].set_directional(rl.WHITE, rl.Vector3.new(0.2, 0, 0.2), 1, rl.Vector3.zero()); - var chip = try allocator.create(ChipContext); chip.* = try ChipContext.init(allocator); @@ -148,45 +54,19 @@ pub fn init(allocator: Allocator) !Self { var raylib_chip = try allocator.create(RaylibChip); raylib_chip.* = RaylibChip.init(chip, chip_sound); - const screen_texture = rl.LoadRenderTexture(raylib_chip.chip.display_width, raylib_chip.chip.display_height); - errdefer rl.UnloadRenderTexture(screen_texture); - - const powered = try allocator.create(bool); - errdefer allocator.destroy(powered); - - var emulator = try EmulatorModel.init(allocator, powered, &chip.input, screen_texture); - errdefer emulator.deinit(); - emulator.setShader(shader); - return Self { .allocator = allocator, - .emulator = emulator, - .shader = shader, - .lights = lights, .chip = chip, .raylib_chip = raylib_chip, .chip_sound = chip_sound, - .screen_texture = screen_texture, - .powered = powered, - .camera = rl.Camera3D{ - .position = rl.Vector3.new(0, 0, -10), - .target = rl.Vector3.new(0.0, 0.0, 0.0), - .up = rl.Vector3.new(0.0, 1.0, 0.0), - .fovy = 45.0, - .projection = rl.CameraProjection.CAMERA_PERSPECTIVE, - }, }; } pub fn deinit(self: *Self) void { - self.emulator.deinit(); - rl.UnloadShader(self.shader); rl.UnloadSound(self.chip_sound); - rl.UnloadRenderTexture(self.screen_texture); self.chip.deinit(); - self.allocator.destroy(self.powered); self.allocator.destroy(self.raylib_chip); self.allocator.destroy(self.chip); } @@ -197,182 +77,12 @@ pub fn set_rom(self: *Self, rom: ROM) void { self.chip.set_memory(0x200, rom.data); } -pub fn turn_on(self: *Self) void { - self.powered.* = true; -} - -pub fn turn_off(self: *Self) void { - self.powered.* = false; - self.chip.reset(); - if (self.rom) |rom| { - self.chip.set_memory(0x200, rom.data); - } -} - -pub fn toggle_power(self: *Self) void { - if (self.powered.*) { - self.turn_off(); - } else { - self.turn_on(); - } -} - -fn updateCamera(self: *Self, dt: f32) void { - const mouse_delta = rl.GetMouseDelta(); - const camera = &self.camera; - const emulator = &self.emulator; - - if (rl.IsWindowResized()) { - 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 = getPrefferedDistanceToBox(camera, self.emulator.bbox); - camera.target = emulator.position; - camera.position = emulator.position.sub(rl.Vector3.new(0, 0, 1).scale(distance)); - } - - var camera_turn_acc = rl.Vector3Zero(); - if (rl.IsMouseButtonDown(rl.MouseButton.MOUSE_BUTTON_LEFT)) { - if (@fabs(mouse_delta.x) > 5) { - const rotation_speed = 2; // Radians/second - camera_turn_acc.x = -rotation_speed*mouse_delta.x; - } - if (@fabs(mouse_delta.x) < 5) { - self.camera_turn_vel = self.camera_turn_vel.scale(0.90); // Holding drag - } - } - - if (rl.IsMouseButtonPressed(rl.MouseButton.MOUSE_BUTTON_LEFT)) { - self.camera_target_orientation = null; - - const now = rl.GetTime(); - const duration_between_clicks = now - self.previous_click_time; - if (duration_between_clicks < 0.3) { - const ray = rl.GetMouseRay(rl.GetMousePosition(), camera.*); - const collision = rl.GetRayCollisionBox(ray, self.emulator.bbox); - if (collision.hit) { - const front_face_normal = rl.Vector3.new(0, 0, -1); - const back_face_normal = rl.Vector3.new(0, 0, 1); - if (rl.Vector3Equals(collision.normal, front_face_normal) == 1) { - self.camera_target_orientation = front_face_normal; - } else if (rl.Vector3Equals(collision.normal, back_face_normal) == 1) { - self.camera_target_orientation = back_face_normal; - } - } - } - self.previous_click_time = now; - } - - if (self.camera_target_orientation) |target| { - const current_direction = camera.position.sub(emulator.position).normalize(); - const current_angle = std.math.atan2(f32, current_direction.z, current_direction.x); - const target_angle = std.math.atan2(f32, target.z, target.x); - const diff_angle = std.math.pi - @mod((target_angle - current_angle) + 3*std.math.pi, 2*std.math.pi); - if (@fabs(diff_angle) < 0.001) { - self.camera_turn_vel.x = 0; - self.camera_target_orientation = null; - } else { - self.camera_turn_vel.x = diff_angle*3; - } - } - - self.camera_turn_vel = self.camera_turn_vel.scale(0.95); // Ambient drag - self.camera_turn_vel = self.camera_turn_vel.add(camera_turn_acc.scale(dt)); - - const camera_min_vel = 0; - if (self.camera_turn_vel.length() > camera_min_vel) { - const rotation = rl.MatrixRotate(camera.up.normalize(), self.camera_turn_vel.x*dt); - var view = rl.Vector3Subtract(camera.position, camera.target); - view = rl.Vector3Transform(view, rotation); - camera.position = rl.Vector3Add(camera.target, view); - } -} - pub fn update(self: *Self, dt: f32) void { - self.updateCamera(dt); - - const camera = &self.camera; - const cameraPos = [3]f32{ camera.position.x, camera.position.y, camera.position.z }; - rl.SetShaderValue(self.shader, self.shader.locs.?[@intFromEnum(rl.ShaderLocationIndex.SHADER_LOC_VECTOR_VIEW)], &cameraPos, .SHADER_UNIFORM_VEC3); - - const ray = rl.GetMouseRay(rl.GetMousePosition(), self.camera); - if (self.emulator.isOverPowerSwitch(ray)) { - if (rl.IsMouseButtonPressed(rl.MouseButton.MOUSE_BUTTON_LEFT)) { - self.toggle_power(); - } - } - - self.raylib_chip.update_input(); - if (self.powered.*) { - self.raylib_chip.update(dt); - } - - rl.BeginTextureMode(self.screen_texture); - { - self.raylib_chip.render(); - } - rl.EndTextureMode(); - - // { - // var matProj = rl.MatrixIdentity(); - // // projection = CAMERA_PERSPECTIVE - // matProj = rl.MatrixPerspective(camera.fovy*rl.DEG2RAD, (screen_width/screen_height), rl.RL_CULL_DISTANCE_NEAR, rl.RL_CULL_DISTANCE_FAR); - // - // var matView = rl.MatrixLookAt(camera.position, camera.target, camera.up); - // // Convert world position vector to quaternion - // var worldPos = rl.Vector4{ .x = position.x, .y = position.y, .z = position.z, .w = 1.0 }; - // - // std.debug.print("worldPos {}\n", .{worldPos}); - // // Transform world position to view - // worldPos = rl.QuaternionTransform(worldPos, matView); - // - // // Transform result to projection (clip space position) - // worldPos = rl.QuaternionTransform(worldPos, matProj); - // - // // Calculate normalized device coordinates (inverted y) - // var ndcPos = rl.Vector3.new( worldPos.x/worldPos.w, -worldPos.y/worldPos.w, worldPos.z/worldPos.w ); - // - // // Calculate 2d screen position vector - // screen_position = rl.Vector2{ .x = (ndcPos.x + 1.0)/2.0*screen_width, .y = (ndcPos.y + 1.0)/2.0*screen_height }; - // } - - // const target_screen_position = rl.Vector2{ .x = screen_width/2, .y = screen_height*0.1 }; - // { - // var matProj = get_camera_projection(&camera); - // var matView = rl.MatrixLookAt(camera.position, camera.target, camera.up); - // - // const ndc_x = (2.0*target_screen_position.x) / screen_width - 1.0; - // const ndc_y = 1.0 - (2.0*target_screen_position.y) / screen_height; - // - // var near_point = rl.Vector3Unproject(.{ .x = ndc_x, .y = ndc_y, .z = 0.0 }, matProj, matView); - // var far_point = rl.Vector3Unproject(.{ .x = ndc_x, .y = ndc_y, .z = 1.0 }, matProj, matView); - // - // var direction = rl.Vector3Subtract(far_point, near_point).normalize(); - // - // var origin: rl.Vector3 = undefined; - // if (camera.projection == .CAMERA_PERSPECTIVE) { - // origin = camera.position; - // } else { - // origin = rl.Vector3Unproject(.{ .x = ndc_x, .y = ndc_y, .z = -1.0 }, matProj, matView); - // } - // - // var world_pos = origin.add(direction.scale(3)); - // - // model_position = world_pos; - // } + self.raylib_chip.update(dt); } pub fn draw(self: *Self) void { rl.ClearBackground(rl.Color{ .r = 33, .g = 33, .b = 33 }); - rl.BeginShaderMode(self.shader); - { - rl.BeginMode3D(self.camera); - self.emulator.draw(); - rl.EndMode3D(); - } - rl.EndShaderMode(); + self.raylib_chip.render(); } diff --git a/src/shader-code.zig b/src/shader-code.zig deleted file mode 100644 index 2b33c7a..0000000 --- a/src/shader-code.zig +++ /dev/null @@ -1,36 +0,0 @@ -const Self = @This(); -const std = @import("std"); -const Allocator = std.mem.Allocator; - -allocator: Allocator, -vertex: [:0]u8, -fragment: [:0]u8, - -pub fn init(allocator: Allocator, max_lights_count: u32) !Self { - const base_vertex_code = @embedFile("shaders/main_vs.glsl"); - const base_fragment_code = @embedFile("shaders/main_fs.glsl"); - - const vertex = try allocator.dupeZ(u8, base_vertex_code); - errdefer allocator.free(vertex); - - const newline = std.mem.indexOfScalar(u8, base_fragment_code, '\n') orelse unreachable; - const first_line = base_fragment_code[0..newline]; - const after_first_line = base_fragment_code[(newline+1)..]; - const fragment = try std.fmt.allocPrintZ(allocator, - \\{s} - \\#define MAX_LIGHTS {} - \\{s} - , .{ first_line, max_lights_count, after_first_line }); - errdefer allocator.free(fragment); - - return Self { - .allocator = allocator, - .vertex = vertex, - .fragment = fragment - }; -} - -pub fn deinit(self: Self) void { - self.allocator.free(self.vertex); - self.allocator.free(self.fragment); -} diff --git a/src/shaders/main_fs.glsl b/src/shaders/main_fs.glsl deleted file mode 100644 index 22069f3..0000000 --- a/src/shaders/main_fs.glsl +++ /dev/null @@ -1,78 +0,0 @@ -#version 330 - -// Input vertex attributes (from vertex shader) -in vec3 fragPosition; -in vec2 fragTexCoord; -in vec4 fragColor; -in vec3 fragNormal; - -// Input uniform values -uniform sampler2D texture0; -uniform vec4 colDiffuse; - -// Output fragment color -layout (location = 0) out vec4 finalColor; -layout (location = 1) out vec4 bloomColor; - -struct MaterialProperty { - vec3 color; - int useSampler; - sampler2D sampler; -}; - -#define LIGHT_DIRECTIONAL 0 -#define LIGHT_POINT 1 - -struct Light { - int type; - bool enabled; - float intensity; - vec3 position; - vec4 color; - - // Directional light arguments: - vec3 target; -}; - -// Input lighting values -uniform Light lights[MAX_LIGHTS]; -uniform vec4 ambient; -uniform vec3 viewPos; - -void main() { - // Texel color fetching from texture sampler - vec4 texelColor = texture(texture0, fragTexCoord); - - finalColor = fragColor * texelColor; - - { // Apply lights - vec3 lightDot = vec3(0.0); - vec3 normal = normalize(fragNormal); - vec3 viewD = normalize(viewPos - fragPosition); - vec3 specular = vec3(0.0); - - for (int i = 0; i < MAX_LIGHTS; i++) { - if (!lights[i].enabled) continue; - - vec3 light = vec3(0.0); - if (lights[i].type == LIGHT_DIRECTIONAL) { - light = -normalize(lights[i].target - lights[i].position); - } else if (lights[i].type == LIGHT_POINT) { - light = normalize(lights[i].position - fragPosition); - } - - float NdotL = max(dot(normal, light), 0.0); - lightDot += lights[i].color.rgb * NdotL * lights[i].intensity; - - float specCo = 0.0; - if (NdotL > 0.0) specCo = pow(max(0.0, dot(viewD, reflect(-(light), normal))), 16.0); // 16 refers to shine - specular += specCo; - } - - finalColor *= ((colDiffuse + vec4(specular, 1.0))*vec4(lightDot, 1.0) + (ambient/10.0)*colDiffuse); - } - - // Gamma correction - finalColor = pow(finalColor, vec4(1.0/1.9)); -} - diff --git a/src/shaders/main_vs.glsl b/src/shaders/main_vs.glsl deleted file mode 100644 index dceceff..0000000 --- a/src/shaders/main_vs.glsl +++ /dev/null @@ -1,31 +0,0 @@ -#version 330 - -// Input vertex attributes -in vec3 vertexPosition; -in vec2 vertexTexCoord; -in vec3 vertexNormal; -in vec4 vertexColor; - -// Input uniform values -uniform mat4 mvp; -uniform mat4 matModel; -uniform mat4 matNormal; - -// Output vertex attributes (to fragment shader) -out vec3 fragPosition; -out vec2 fragTexCoord; -out vec4 fragColor; -out vec3 fragNormal; - -// NOTE: Add here your custom variables - -void main() { - // Send vertex attributes to fragment shader - fragPosition = vec3(matModel*vec4(vertexPosition, 1.0)); - fragTexCoord = vertexTexCoord; - fragColor = vertexColor; - fragNormal = normalize(vec3(matNormal*vec4(vertexNormal, 1.0))); - - // Calculate final vertex position - gl_Position = mvp*vec4(vertexPosition, 1.0); -}