1
0

Compare commits

...

2 Commits

Author SHA1 Message Date
cdfd81fc91 include all ROMs into final binary at comptime 2023-11-09 00:20:28 +02:00
441a3df8ea show chip8 display on model 2023-11-08 23:34:25 +02:00
16 changed files with 3487 additions and 3384 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
zig-cache zig-cache
zig-out zig-out
*.blend1

4
build-models.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
cd src/assets/models
blender emulator.blend --background --python export_obj.py

View File

@ -32,6 +32,24 @@ pub fn build(b: *std.Build) !void {
const exe = b.addExecutable(.{ .name = "chip8-zig", .root_source_file = .{ .path = "src/main.zig" }, .optimize = optimize, .target = target }); const exe = b.addExecutable(.{ .name = "chip8-zig", .root_source_file = .{ .path = "src/main.zig" }, .optimize = optimize, .target = target });
// Provide filenames of all files in 'src/ROMs' to program as options
{
var files = std.ArrayList([]const u8).init(b.allocator);
defer files.deinit();
var options = b.addOptions();
var dir = try std.fs.cwd().openIterableDir("src/ROMs", .{ });
var it = dir.iterate();
while (try it.next()) |file| {
if (file.kind != .file) {
continue;
}
try files.append(b.pathJoin(&.{"ROMs", file.name}));
}
options.addOption([]const []const u8, "roms", files.items);
exe.addOptions("options", options);
}
raylib.addTo(b, exe, target, optimize); raylib.addTo(b, exe, target, optimize);
// rl.link(b, exe, target, optimize); // rl.link(b, exe, target, optimize);

BIN
src/ROMs/snek.ch8 Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
# Blender 3.5.1 MTL File: 'emulator.blend' # Blender MTL File: 'emulator.blend'
# www.blender.org # Material Count: 4
newmtl Behind_buttons newmtl Behind_buttons
Ns 0.000000 Ns 0.000000
@ -14,15 +14,16 @@ illum 2
newmtl Button newmtl Button
Ns 250.000000 Ns 250.000000
Ka 1.000000 1.000000 1.000000 Ka 1.000000 1.000000 1.000000
Kd 0.003095 0.002616 0.002869
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.450000 Ni 1.450000
d 1.000000 d 1.000000
illum 2 illum 2
map_Kd Buttons texture.png map_Kd /home/rokas/code/fun/chip8-zig/src/assets/models/Buttons texture.png
newmtl Case newmtl Case
Ns 298.057037 Ns 298.057005
Ka 1.000000 1.000000 1.000000 Ka 1.000000 1.000000 1.000000
Kd 0.057402 0.087001 0.228570 Kd 0.057402 0.087001 0.228570
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
@ -40,3 +41,4 @@ Ke 0.000000 0.000000 0.000000
Ni 1.450000 Ni 1.450000
d 1.000000 d 1.000000
illum 2 illum 2
map_Kd /home/rokas/code/fun/chip8-zig/src/assets/models/screen-texture.png

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
import bpy
bpy.ops.export_scene.obj(
filepath="emulator.obj",
use_triangles=True,
use_materials=True,
use_normals=True,
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

View File

@ -4,7 +4,7 @@ const Allocator = std.mem.Allocator;
const Errors = error { UnknownInstruction }; const Errors = error { UnknownInstruction };
allocator: *const Allocator, allocator: Allocator,
display: []bool, display: []bool,
display_width: u8, display_width: u8,
@ -43,7 +43,7 @@ fn get_inst_n(inst: u16) u4 {
return @truncate(inst & 0x000F); return @truncate(inst & 0x000F);
} }
pub fn init(allocator: *const Allocator) !Self { pub fn init(allocator: Allocator) !Self {
const seed_bits: u128 = @bitCast(std.time.nanoTimestamp()); const seed_bits: u128 = @bitCast(std.time.nanoTimestamp());
const seed: u64 = @truncate(seed_bits); const seed: u64 = @truncate(seed_bits);

19
src/global-context.zig Normal file
View File

@ -0,0 +1,19 @@
const Self = @This();
const rl = @import("raylib");
camera: rl.Camera3D,
pub fn init() Self {
var 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,
};
return Self {
.camera = camera
};
}

413
src/main-scene.zig Normal file
View File

@ -0,0 +1,413 @@
const Self = @This();
const rl = @import("raylib");
const std = @import("std");
const MemoryView = @import("memory-view.zig").MemoryView;
const Range = @import("memory-view.zig").Range;
const UI = @import("ui.zig").UI;
const GlobalContext = @import("./global-context.zig");
const ChipContext = @import("chip.zig");
const RaylibChip = @import("raylib-chip.zig");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Tab = enum {
MemoryView
};
var edit_mode = false;
var tab = Tab.MemoryView;
ctx: *GlobalContext,
allocator: Allocator,
model: rl.Model,
model_bbox: rl.BoundingBox,
model_position: rl.Vector3,
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,
light: Light,
chip: *ChipContext,
raylib_chip: RaylibChip,
chip_sound: rl.Sound,
screen_texture: rl.RenderTexture2D,
pub fn gen_sin_wave(wave: *rl.Wave, frequency: f32) void {
assert(wave.sampleSize == 16); // Only 16 bits are supported
const sample_rate: f32 = @floatFromInt(wave.sampleRate);
const sample_size: u5 = @truncate(wave.sampleSize);
const max_sample_value: f32 = @floatFromInt(@shlExact(@as(u32, 1), sample_size - 1));
const data: [*]i16 = @ptrCast(@alignCast(wave.data));
for (0..wave.frameCount) |i| {
const i_f32: f32 = @floatFromInt(i);
const sin_value: f32 = @sin(std.math.pi*2*frequency/sample_rate*i_f32);
data[i] = @intFromFloat(sin_value*max_sample_value);
}
}
const Light = struct {
const LightType = enum(i32) {
DIRECTIONAL = 0,
POINT = 1,
};
type: LightType,
enabled: bool,
position: rl.Vector3,
target: rl.Vector3,
color: rl.Color,
attenuation: f32 = 0.0,
enabledLoc: i32,
typeLoc: i32,
positionLoc: i32,
targetLoc: i32,
colorLoc: i32,
attenuationLoc: i32 = 0,
fn create(light_type: LightType, postion: rl.Vector3, target: rl.Vector3, color: rl.Color, shader: rl.Shader) Light {
var light = Light{
.type = light_type,
.enabled = true,
.position = postion,
.target = target,
.color = color,
.enabledLoc = rl.GetShaderLocation(shader, "lights[0].enabled"),
.typeLoc = rl.GetShaderLocation(shader, "lights[0].type"),
.positionLoc = rl.GetShaderLocation(shader, "lights[0].position"),
.targetLoc = rl.GetShaderLocation(shader, "lights[0].target"),
.colorLoc = rl.GetShaderLocation(shader, "lights[0].color"),
};
light.update_values(shader);
return light;
}
fn update_values(light: *Light, shader: rl.Shader) void {
const enabled: i32 = @intFromBool(light.enabled);
rl.SetShaderValue(shader, light.enabledLoc, &enabled, rl.ShaderUniformDataType.SHADER_UNIFORM_INT);
const lightType: i32 = @intFromEnum(light.type);
rl.SetShaderValue(shader, light.typeLoc, &lightType, rl.ShaderUniformDataType.SHADER_UNIFORM_INT);
const position = [3]f32{ light.position.x, light.position.y, light.position.z };
rl.SetShaderValue(shader, light.positionLoc, &position, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC3);
const target = [3]f32{ light.target.x, light.target.y, light.target.z };
rl.SetShaderValue(shader, light.targetLoc, &target, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC3);
const color = [4]f32{
@as(f32, @floatFromInt(light.color.r)) / 255.0,
@as(f32, @floatFromInt(light.color.g)) / 255.0,
@as(f32, @floatFromInt(light.color.b)) / 255.0,
@as(f32, @floatFromInt(light.color.a)) / 255.0,
};
rl.SetShaderValue(shader, light.colorLoc, &color, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC4);
}
};
fn get_camera_projection(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 get_screen_direction_from_camera(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 get_preffered_distance_to_box(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 = get_camera_projection(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 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, ctx: *GlobalContext) !Self {
// var temp_mem = [1]u8{0xAA} ** (16*80 + 10);
// var memory_view = MemoryView.init(&temp_mem, &font, 32);
// var memory_view = MemoryView.init(chip.memory, &font, font_size, &allocator);
// var selected_memory = Range{};
// var ui = UI.init();
var model = rl.LoadModel("src/assets/models/emulator.obj");
var model_bbox = rl.GetModelBoundingBox(model);
var model_position = rl.Vector3{ };
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");
const ambientLoc = rl.GetShaderLocation(shader, "ambient");
rl.SetShaderValue(shader, ambientLoc, &[4]f32{ 0.2, 0.2, 0.2, 1.0 }, .SHADER_UNIFORM_VEC4);
var light = Light.create(.DIRECTIONAL, rl.Vector3.new(0.2, 0, -0.2), rl.Vector3.zero(), rl.WHITE, shader);
for (0..@intCast(model.materialCount)) |i| {
model.materials.?[i].shader = shader;
}
var chip = try allocator.create(ChipContext);
chip.* = try ChipContext.init(allocator);
const sample_rate = 44100;
var data = try allocator.alloc(i16, sample_rate);
defer allocator.free(data);
var chip_wave = rl.Wave{
.frameCount = sample_rate,
.sampleRate = sample_rate,
.sampleSize = 16,
.channels = 1,
.data = @ptrCast(data.ptr),
};
gen_sin_wave(&chip_wave, 440);
var chip_sound = rl.LoadSoundFromWave(chip_wave);
rl.SetSoundVolume(chip_sound, 0.2);
var raylib_chip = RaylibChip.init(chip, chip_sound);
var screen_texture = rl.LoadRenderTexture(chip.display_width, chip.display_height);
// TODO: Don't use the fourth material, use name of material to get its index. Or some other more reliable method
rl.SetMaterialTexture(@ptrCast(&model.materials.?[3]), rl.MATERIAL_MAP_DIFFUSE, screen_texture.texture);
var self = Self {
.allocator = allocator,
.ctx = ctx,
.model = model,
.model_bbox = model_bbox,
.model_position = model_position,
.shader = shader,
.light = light,
.chip = chip,
.raylib_chip = raylib_chip,
.chip_sound = chip_sound,
.screen_texture = screen_texture,
};
return self;
}
pub fn deinit(self: *Self) void {
rl.UnloadRenderTexture(self.screen_texture);
rl.UnloadSound(self.chip_sound);
self.chip.deinit();
self.allocator.destroy(self.chip);
}
fn update_camera(self: *Self, dt: f32) void {
const mouse_delta = rl.GetMouseDelta();
const camera = &self.ctx.camera;
if (rl.IsWindowResized()) {
const distance = get_preffered_distance_to_box(camera, self.model_bbox);
const direction = camera.position.sub(self.model_position).normalize();
camera.position = self.model_position.add(direction.scale(distance));
}
if (rl.Vector3Equals(camera.position, rl.Vector3Zero()) == 1) {
const distance = get_preffered_distance_to_box(camera, self.model_bbox);
camera.target = self.model_position;
camera.position = self.model_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.model_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(self.model_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 {
if (rl.IsKeyPressed(rl.KeyboardKey.KEY_TAB)) {
edit_mode = !edit_mode;
}
if (edit_mode) {
if (rl.IsKeyPressed(rl.KeyboardKey.KEY_ONE)) {
tab = .MemoryView;
}
}
self.update_camera(dt);
const camera = &self.ctx.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, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC3);
self.light.update_values(self.shader);
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;
// }
}
pub fn draw(self: *Self) void {
rl.DrawModel(self.model, self.model_position, 1.0, rl.WHITE);
// rl.DrawMesh(self.screen_mesh, self.screen_material, self.screen_transform);
// if (!edit_mode) {
// rl.ClearBackground(rl.DARKGRAY);
//
// const scale_x = @divFloor(screen_width, chip.display_width);
// const scale_y = @divFloor(screen_height, chip.display_height);
// const min_scale = @min(scale_x, scale_y);
//
// const display_width = chip.display_width * min_scale;
// const display_height = chip.display_height * min_scale;
// const display_x = @divFloor(screen_width - display_width, 2);
// const display_y = @divFloor(screen_height - display_height, 2);
// raylib_chip.render(display_x, display_y, display_width, display_height);
// } else {
// rl.ClearBackground(rl.RAYWHITE);
// ui.update();
//
// if (tab == .MemoryView) {
// try memory_view.show(&ui, 0, 0, @floatFromInt(screen_width), @floatFromInt(screen_height), &selected_memory);
// }
// }
}

View File

@ -5,169 +5,26 @@ const print = std.debug.print;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ChipContext = @import("chip.zig"); const ChipContext = @import("chip.zig");
const RaylibChip = @import("raylib-chip.zig"); const RaylibChip = @import("raylib-chip.zig");
const UI = @import("ui.zig").UI; const GlobalContext = @import("./global-context.zig");
const MainScene = @import("./main-scene.zig");
const MemoryView = @import("memory-view.zig").MemoryView; const options = @import("options");
const Range = @import("memory-view.zig").Range;
const assert = std.debug.assert;
const Tab = enum {
MemoryView
};
pub fn gen_sin_wave(wave: *rl.Wave, frequency: f32) void {
assert(wave.sampleSize == 16); // Only 16 bits are supported
const sample_rate: f32 = @floatFromInt(wave.sampleRate);
const sample_size: u5 = @truncate(wave.sampleSize);
const max_sample_value: f32 = @floatFromInt(@shlExact(@as(u32, 1), sample_size - 1));
const data: [*]i16 = @ptrCast(@alignCast(wave.data));
for (0..wave.frameCount) |i| {
const i_f32: f32 = @floatFromInt(i);
const sin_value: f32 = @sin(std.math.pi*2*frequency/sample_rate*i_f32);
data[i] = @intFromFloat(sin_value*max_sample_value);
}
}
fn megabytes(amount: usize) usize { fn megabytes(amount: usize) usize {
return amount * 1024 * 1024; return amount * 1024 * 1024;
} }
const Light = struct { const ROM = struct {
const LightType = enum(i32) { name: []const u8,
DIRECTIONAL = 0, data: []const u8,
POINT = 1,
};
type: LightType,
enabled: bool,
position: rl.Vector3,
target: rl.Vector3,
color: rl.Color,
attenuation: f32 = 0.0,
enabledLoc: i32,
typeLoc: i32,
positionLoc: i32,
targetLoc: i32,
colorLoc: i32,
attenuationLoc: i32 = 0,
fn create(light_type: LightType, postion: rl.Vector3, target: rl.Vector3, color: rl.Color, shader: rl.Shader) Light {
var light = Light{
.type = light_type,
.enabled = true,
.position = postion,
.target = target,
.color = color,
.enabledLoc = rl.GetShaderLocation(shader, "lights[0].enabled"),
.typeLoc = rl.GetShaderLocation(shader, "lights[0].type"),
.positionLoc = rl.GetShaderLocation(shader, "lights[0].position"),
.targetLoc = rl.GetShaderLocation(shader, "lights[0].target"),
.colorLoc = rl.GetShaderLocation(shader, "lights[0].color"),
};
light.update_values(shader);
return light;
}
fn update_values(light: *Light, shader: rl.Shader) void {
const enabled: i32 = @intFromBool(light.enabled);
rl.SetShaderValue(shader, light.enabledLoc, &enabled, rl.ShaderUniformDataType.SHADER_UNIFORM_INT);
const lightType: i32 = @intFromEnum(light.type);
rl.SetShaderValue(shader, light.typeLoc, &lightType, rl.ShaderUniformDataType.SHADER_UNIFORM_INT);
const position = [3]f32{ light.position.x, light.position.y, light.position.z };
rl.SetShaderValue(shader, light.positionLoc, &position, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC3);
const target = [3]f32{ light.target.x, light.target.y, light.target.z };
rl.SetShaderValue(shader, light.targetLoc, &target, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC3);
const color = [4]f32{
@as(f32, @floatFromInt(light.color.r)) / 255.0,
@as(f32, @floatFromInt(light.color.g)) / 255.0,
@as(f32, @floatFromInt(light.color.b)) / 255.0,
@as(f32, @floatFromInt(light.color.a)) / 255.0,
};
rl.SetShaderValue(shader, light.colorLoc, &color, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC4);
}
}; };
fn get_camera_projection(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 get_screen_direction_from_camera(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 get_preffered_distance_to_box(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 = get_camera_projection(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 angle = top_middle_dir.angleBetween(middle_dir);
const distance = 1/@tan(angle) * (box_size.y/2) + box_size.z/4;
return distance;
}
pub fn main() anyerror!void { pub fn main() anyerror!void {
var program_memory = try std.heap.page_allocator.alloc(u8, megabytes(2)); const memory = try std.heap.page_allocator.alloc(u8, megabytes(5));
var fba = std.heap.FixedBufferAllocator.init(program_memory); var fba = std.heap.FixedBufferAllocator.init(memory);
const allocator = fba.allocator(); const allocator = fba.allocator();
var chip = try ChipContext.init(&allocator);
defer chip.deinit();
chip.set_memory(0x200, @embedFile("ROMs/br8kout.ch8"));
// const file = try std.fs.cwd().openFile("ROMs/morse_demo.ch8", .{ .mode = .read_only });
// defer file.close();
// try chip.set_memory_from_file(0x200, file);
const pixel_size = 20;
const initial_screen_width: i32 = @as(i32, chip.display_width) * pixel_size;
const initial_screen_height: i32 = @as(i32, chip.display_height) * pixel_size;
rl.SetConfigFlags(rl.ConfigFlags{ .FLAG_WINDOW_RESIZABLE = true }); rl.SetConfigFlags(rl.ConfigFlags{ .FLAG_WINDOW_RESIZABLE = true });
rl.InitWindow(initial_screen_width, initial_screen_height, "CHIP-8"); rl.InitWindow(1024, 720, "CHIP-8");
defer rl.CloseWindow(); defer rl.CloseWindow();
rl.InitAudioDevice(); rl.InitAudioDevice();
@ -175,243 +32,45 @@ pub fn main() anyerror!void {
rl.SetTargetFPS(60); rl.SetTargetFPS(60);
var ctx = GlobalContext.init();
var main_scene = try MainScene.init(allocator, &ctx);
defer main_scene.deinit();
comptime var roms = [1]ROM{ undefined } ** options.roms.len;
comptime {
var i = 0;
for (options.roms) |file| {
roms[i] = ROM{
.name = file,
.data = @embedFile(file)
};
i += 1;
}
}
main_scene.chip.set_memory(0x200, roms[3].data);
const font_size = 24; const font_size = 24;
const font_ttf_default_numchars = 95; // TTF font generation default charset: 95 glyphs (ASCII 32..126) const font_ttf_default_numchars = 95; // TTF font generation default charset: 95 glyphs (ASCII 32..126)
const font = rl.LoadFontEx("src/fonts/generic-mono.otf", font_size, null, font_ttf_default_numchars); const font = rl.LoadFontEx("src/assets/fonts/generic-mono.otf", font_size, null, font_ttf_default_numchars);
defer rl.UnloadFont(font); defer rl.UnloadFont(font);
const sample_rate = 44100;
var data = try allocator.alloc(i16, sample_rate);
defer allocator.free(data);
var chip_wave = rl.Wave{
.frameCount = sample_rate,
.sampleRate = sample_rate,
.sampleSize = 16,
.channels = 1,
.data = @ptrCast(data.ptr),
};
gen_sin_wave(&chip_wave, 440);
var chip_sound = rl.LoadSoundFromWave(chip_wave);
defer rl.UnloadSound(chip_sound);
rl.SetSoundVolume(chip_sound, 0.2);
var raylib_chip = RaylibChip.init(&chip, &chip_sound);
// var raylib_chip = RaylibChip.init(&chip, null);
raylib_chip.tick_speed = 500;
raylib_chip.timer_speed = 60;
var edit_mode = false;
var tab: Tab = .MemoryView;
// var temp_mem = [1]u8{0xAA} ** (16*80 + 10);
// var memory_view = MemoryView.init(&temp_mem, &font, 32);
// var memory_view = MemoryView.init(chip.memory, &font, font_size, &allocator);
// var selected_memory = Range{};
// var ui = UI.init();
var camera = rl.Camera3D{
.position = rl.Vector3.new(0.0, 0, 0.0),
.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,
};
var model = rl.LoadModel("src/assets/models/emulator.obj");
var model_bbox = rl.GetModelBoundingBox(model);
var model_position = rl.Vector3{ };
var model_size = model_bbox.max.sub(model_bbox.min);
const shader = rl.LoadShader("src/shaders/lighting.vs", "src/shaders/lighting.fs");
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.2, 0.2, 0.2, 1.0 }, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC4);
for (0..@intCast(model.materialCount)) |i| {
model.materials.?[i].shader = shader;
}
var light = Light.create(Light.LightType.DIRECTIONAL, rl.Vector3.new(0.2, 0, -0.2), rl.Vector3.zero(), rl.WHITE, shader);
std.debug.print("dimensions {}", .{model_size});
var camera_turn_vel = rl.Vector3Zero();
var camera_target_orientation: ?rl.Vector3 = null;
var previous_click_time: f64 = 0.0;
// rl.DisableCursor();
while (!rl.WindowShouldClose()) { while (!rl.WindowShouldClose()) {
var dt = rl.GetFrameTime(); var dt = rl.GetFrameTime();
raylib_chip.update(dt); main_scene.raylib_chip.update_input();
main_scene.raylib_chip.update(dt);
if (rl.IsKeyPressed(rl.KeyboardKey.KEY_TAB)) { main_scene.update(dt);
edit_mode = !edit_mode;
}
if (edit_mode) {
if (rl.IsKeyPressed(rl.KeyboardKey.KEY_ONE)) {
tab = .MemoryView;
}
}
const mouse_delta = rl.GetMouseDelta();
if (rl.IsWindowResized()) {
const distance = get_preffered_distance_to_box(&camera, model_bbox);
const direction = camera.position.sub(model_position).normalize();
camera.position = model_position.add(direction.scale(distance));
}
if (rl.Vector3Equals(camera.position, rl.Vector3Zero()) == 1) {
const distance = get_preffered_distance_to_box(&camera, model_bbox);
camera.target = model_position;
camera.position = model_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) {
camera_turn_vel = camera_turn_vel.scale(0.90); // Holding drag
}
}
if (rl.IsMouseButtonPressed(rl.MouseButton.MOUSE_BUTTON_LEFT)) {
camera_target_orientation = null;
const now = rl.GetTime();
const duration_between_clicks = now - previous_click_time;
if (duration_between_clicks < 0.3) {
const ray = rl.GetMouseRay(rl.GetMousePosition(), camera);
const collision = rl.GetRayCollisionBox(ray, model_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) {
camera_target_orientation = front_face_normal;
} else if (rl.Vector3Equals(collision.normal, back_face_normal) == 1) {
camera_target_orientation = back_face_normal;
}
}
}
previous_click_time = now;
}
if (camera_target_orientation) |target| {
const current_direction = camera.position.sub(model_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) {
camera_turn_vel.x = 0;
camera_target_orientation = null;
} else {
camera_turn_vel.x = diff_angle*3;
}
}
camera_turn_vel = camera_turn_vel.scale(0.95); // Ambient drag
camera_turn_vel = camera_turn_vel.add(camera_turn_acc.scale(dt));
const camera_min_vel = 0;
if (camera_turn_vel.length() > camera_min_vel) {
const rotation = rl.MatrixRotate(camera.up.normalize(), 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);
}
// {
// 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;
// }
const cameraPos = [3]f32{ camera.position.x, camera.position.y, camera.position.z };
rl.SetShaderValue(shader, shader.locs.?[@intFromEnum(rl.ShaderLocationIndex.SHADER_LOC_VECTOR_VIEW)], &cameraPos, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC3);
light.update_values(shader);
rl.BeginDrawing(); rl.BeginDrawing();
{
rl.ClearBackground(.{ .r = 33, .g = 33, .b = 33, .a = 255 }); rl.ClearBackground(.{ .r = 33, .g = 33, .b = 33, .a = 255 });
rl.BeginMode3D(ctx.camera);
rl.BeginMode3D(camera); main_scene.draw();
rl.DrawModel(model, model_position, 1.0, rl.WHITE);
// rl.rlPushMatrix();
// rl.rlSetLineWidth(2);
// rl.rlTranslatef(model_position.x, model_position.y, model_position.z);
// rl.DrawBoundingBox(model_bbox, rl.GREEN);
// rl.rlPopMatrix();
rl.EndMode3D(); rl.EndMode3D();
}
rl.EndDrawing();
rl.EndDrawing();
// if (!edit_mode) {
// rl.ClearBackground(rl.DARKGRAY);
//
// const scale_x = @divFloor(screen_width, chip.display_width);
// const scale_y = @divFloor(screen_height, chip.display_height);
// const min_scale = @min(scale_x, scale_y);
//
// const display_width = chip.display_width * min_scale;
// const display_height = chip.display_height * min_scale;
// const display_x = @divFloor(screen_width - display_width, 2);
// const display_y = @divFloor(screen_height - display_height, 2);
// raylib_chip.render(display_x, display_y, display_width, display_height);
// } else {
// rl.ClearBackground(rl.RAYWHITE);
// ui.update();
//
// if (tab == .MemoryView) {
// try memory_view.show(&ui, 0, 0, @floatFromInt(screen_width), @floatFromInt(screen_height), &selected_memory);
// }
// }
} }
} }

View File

@ -8,12 +8,12 @@ on_color: rl.Color,
off_color: rl.Color, off_color: rl.Color,
timer_speed: f32, timer_speed: f32,
tick_speed: f32, tick_speed: f32,
beep_sound: ?*rl.Sound, beep_sound: ?rl.Sound,
tick_time: f32, tick_time: f32,
timer_time: f32, timer_time: f32,
pub fn init(chip: *ChipContext, beep_sound: ?*rl.Sound) Self { pub fn init(chip: *ChipContext, beep_sound: ?rl.Sound) Self {
return Self{ return Self{
.chip = chip, .chip = chip,
.off_color = rl.BLACK, .off_color = rl.BLACK,
@ -22,7 +22,7 @@ pub fn init(chip: *ChipContext, beep_sound: ?*rl.Sound) Self {
.tick_speed = 500, .tick_speed = 500,
.tick_time = 0, .tick_time = 0,
.timer_time = 0, .timer_time = 0,
.beep_sound = beep_sound .beep_sound = beep_sound,
}; };
} }
@ -72,29 +72,24 @@ pub fn update(self: *Self, dt: f32) void {
if (self.beep_sound) |beep_sound| { if (self.beep_sound) |beep_sound| {
if (self.chip.ST > 0) { if (self.chip.ST > 0) {
if (!rl.IsSoundPlaying(beep_sound.*)) { if (!rl.IsSoundPlaying(beep_sound)) {
rl.PlaySound(beep_sound.*); rl.PlaySound(beep_sound);
} }
} else { } else {
if (rl.IsSoundPlaying(beep_sound.*)) { if (rl.IsSoundPlaying(beep_sound)) {
rl.StopSound(beep_sound.*); rl.StopSound(beep_sound);
} }
} }
} }
} }
pub fn render(self: *Self, x: i32, y: i32, width: i32, height: i32) void { pub fn render(self: *Self) void {
const pixel_width = @divFloor(width, self.chip.display_width); rl.DrawRectangle(0, 0, self.chip.display_width, self.chip.display_height, self.off_color);
const pixel_height = @divFloor(height, self.chip.display_height);
rl.DrawRectangle(x, y, width, height, self.off_color); for (0..self.chip.display_height) |y| {
for (0..self.chip.display_width) |x| {
for (0..self.chip.display_height) |oy| { if (self.chip.display_get(@intCast(x), @intCast(y))) {
for (0..self.chip.display_width) |ox| { rl.DrawPixel(@intCast(x), @intCast(y), self.on_color);
if (self.chip.display_get(@intCast(ox), @intCast(oy))) {
const ix = x + @as(i32, @intCast(ox)) * pixel_width;
const iy = y + @as(i32, @intCast(oy)) * pixel_height;
rl.DrawRectangle(ix, iy, pixel_width, pixel_height, self.on_color);
} }
} }
} }

View File

@ -75,7 +75,6 @@ void main()
} }
finalColor = (texelColor*((colDiffuse + vec4(specular, 1.0))*vec4(lightDot, 1.0))); finalColor = (texelColor*((colDiffuse + vec4(specular, 1.0))*vec4(lightDot, 1.0)));
finalColor += texelColor*(ambient/10.0)*colDiffuse;
// Gamma correction // Gamma correction
finalColor = pow(finalColor, vec4(1.0/1.9)); finalColor = pow(finalColor, vec4(1.0/1.9));