328 lines
8.4 KiB
Zig
328 lines
8.4 KiB
Zig
const std = @import("std");
|
|
const build_options = @import("build_options");
|
|
const log = std.log.scoped(.engine);
|
|
|
|
const InputSystem = @import("./input.zig");
|
|
const KeyCode = InputSystem.KeyCode;
|
|
const MouseButton = InputSystem.MouseButton;
|
|
|
|
const AudioSystem = @import("./audio/root.zig");
|
|
const AudioData = AudioSystem.Data;
|
|
const AudioCommand = AudioSystem.Command;
|
|
|
|
const GraphicsSystem = @import("./graphics.zig");
|
|
const TextureId = GraphicsSystem.TextureId;
|
|
const GraphicsCommand = GraphicsSystem.Command;
|
|
const Font = GraphicsSystem.Font;
|
|
const Sprite = GraphicsSystem.Sprite;
|
|
|
|
const Math = @import("./math.zig");
|
|
const Rect = Math.Rect;
|
|
const Vec4 = Math.Vec4;
|
|
const Vec2 = Math.Vec2;
|
|
const rgb = Math.rgb;
|
|
|
|
pub const Nanoseconds = u64;
|
|
|
|
const Frame = @This();
|
|
|
|
pub const Input = struct {
|
|
keyboard: InputSystem.ButtonStateSet(KeyCode),
|
|
mouse_button: InputSystem.ButtonStateSet(MouseButton),
|
|
mouse_position: ?Vec2,
|
|
mouse_delta: Vec2,
|
|
|
|
pub const empty = Input{
|
|
.keyboard = .empty,
|
|
.mouse_button = .empty,
|
|
.mouse_position = null,
|
|
.mouse_delta = .zero,
|
|
};
|
|
};
|
|
|
|
pub const KeyState = struct {
|
|
down: bool,
|
|
pressed: bool,
|
|
released: bool,
|
|
down_duration: ?f64,
|
|
|
|
pub const RepeatOptions = struct {
|
|
first_at: f64 = 0,
|
|
period: f64
|
|
};
|
|
|
|
pub fn repeat(self: KeyState, last_repeat_at: *?f64, opts: RepeatOptions) bool {
|
|
if (!self.down) {
|
|
last_repeat_at.* = null;
|
|
return false;
|
|
}
|
|
|
|
const down_duration = self.down_duration.?;
|
|
if (last_repeat_at.* != null) {
|
|
if (down_duration >= last_repeat_at.*.? + opts.period) {
|
|
last_repeat_at.* = last_repeat_at.*.? + opts.period;
|
|
return true;
|
|
}
|
|
} else {
|
|
if (down_duration >= opts.first_at) {
|
|
last_repeat_at.* = opts.first_at;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
pub const Audio = struct {
|
|
commands: std.ArrayList(AudioCommand),
|
|
|
|
pub const empty = Audio{
|
|
.commands = .empty
|
|
};
|
|
};
|
|
|
|
pub const Graphics = struct {
|
|
clear_color: Vec4,
|
|
screen_size: Vec2,
|
|
canvas_size: ?Vec2,
|
|
|
|
scissor_stack: std.ArrayList(Rect),
|
|
commands: std.ArrayList(GraphicsCommand),
|
|
|
|
pub const empty = Graphics{
|
|
.clear_color = rgb(0, 0, 0),
|
|
.screen_size = .init(0, 0),
|
|
.canvas_size = null,
|
|
.scissor_stack = .empty,
|
|
.commands = .empty
|
|
};
|
|
};
|
|
|
|
arena: std.heap.ArenaAllocator,
|
|
|
|
time_ns: Nanoseconds,
|
|
dt_ns: Nanoseconds,
|
|
input: Input,
|
|
audio: Audio,
|
|
graphics: Graphics,
|
|
|
|
show_debug: bool,
|
|
hide_cursor: bool,
|
|
|
|
pub fn init(self: *Frame, gpa: std.mem.Allocator) void {
|
|
self.* = Frame{
|
|
.arena = std.heap.ArenaAllocator.init(gpa),
|
|
.time_ns = 0,
|
|
.dt_ns = 0,
|
|
.input = .empty,
|
|
.audio = .empty,
|
|
.graphics = .empty,
|
|
.show_debug = false,
|
|
.hide_cursor = false
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *Frame) void {
|
|
self.arena.deinit();
|
|
}
|
|
|
|
pub fn deltaTime(self: Frame) f32 {
|
|
return @as(f32, @floatFromInt(self.dt_ns)) / std.time.ns_per_s;
|
|
}
|
|
|
|
pub fn time(self: Frame) f64 {
|
|
return @as(f64, @floatFromInt(self.time_ns)) / std.time.ns_per_s;
|
|
}
|
|
|
|
pub fn isKeyDown(self: Frame, key_code: KeyCode) bool {
|
|
const keyboard = &self.input.keyboard;
|
|
return keyboard.down.contains(key_code);
|
|
}
|
|
|
|
pub fn getKeyDownDuration(self: Frame, key_code: KeyCode) ?f32 {
|
|
if (!self.isKeyDown(key_code)) {
|
|
return null;
|
|
}
|
|
|
|
const keyboard = &self.input.keyboard;
|
|
const pressed_at_ns = keyboard.pressed_at.get(key_code).?;
|
|
const duration_ns = self.time_ns - pressed_at_ns;
|
|
|
|
return @as(f32, @floatFromInt(duration_ns)) / std.time.ns_per_s;
|
|
}
|
|
|
|
pub fn isKeyPressed(self: Frame, key_code: KeyCode) bool {
|
|
const keyboard = &self.input.keyboard;
|
|
return keyboard.pressed.contains(key_code);
|
|
}
|
|
|
|
pub fn isKeyReleased(self: Frame, key_code: KeyCode) bool {
|
|
const keyboard = &self.input.keyboard;
|
|
return keyboard.released.contains(key_code);
|
|
}
|
|
|
|
pub fn getKeyState(self: Frame, key_code: KeyCode) KeyState {
|
|
return KeyState{
|
|
.down = self.isKeyDown(key_code),
|
|
.released = self.isKeyReleased(key_code),
|
|
.pressed = self.isKeyPressed(key_code),
|
|
.down_duration = self.getKeyDownDuration(key_code)
|
|
};
|
|
}
|
|
|
|
pub fn isMousePressed(self: Frame, button: MouseButton) bool {
|
|
return self.input.mouse_button.pressed.contains(button);
|
|
}
|
|
|
|
pub fn isMouseDown(self: Frame, button: MouseButton) bool {
|
|
return self.input.mouse_button.down.contains(button);
|
|
}
|
|
|
|
fn pushAudioCommand(self: *Frame, command: AudioCommand) void {
|
|
const arena = self.arena.allocator();
|
|
|
|
self.audio.commands.append(arena, command) catch |e| {
|
|
log.warn("Failed to play audio: {}", .{e});
|
|
};
|
|
}
|
|
|
|
pub fn pushGraphicsCommand(self: *Frame, command: GraphicsCommand) void {
|
|
const arena = self.arena.allocator();
|
|
|
|
self.graphics.commands.append(arena, command) catch |e|{
|
|
log.warn("Failed to push graphics command: {}", .{e});
|
|
};
|
|
}
|
|
|
|
pub fn prependGraphicsCommand(self: *Frame, command: GraphicsCommand) void {
|
|
const arena = self.arena.allocator();
|
|
|
|
self.graphics.commands.insert(arena, 0, command) catch |e|{
|
|
log.warn("Failed to push graphics command: {}", .{e});
|
|
};
|
|
}
|
|
|
|
pub fn playAudio(self: *Frame, options: AudioCommand.Play) void {
|
|
self.pushAudioCommand(.{
|
|
.play = options,
|
|
});
|
|
}
|
|
|
|
pub fn pushScissor(self: *Frame, rect: Rect) void {
|
|
const arena = self.arena.allocator();
|
|
self.graphics.scissor_stack.append(arena, rect) catch |e| {
|
|
log.warn("Failed to push scissor region: {}", .{e});
|
|
return;
|
|
};
|
|
|
|
self.pushGraphicsCommand(.{
|
|
.set_scissor = rect
|
|
});
|
|
}
|
|
|
|
pub fn popScissor(self: *Frame) void {
|
|
_ = self.graphics.scissor_stack.pop().?;
|
|
const rect = self.graphics.scissor_stack.getLast();
|
|
|
|
self.pushGraphicsCommand(.{
|
|
.set_scissor = rect
|
|
});
|
|
}
|
|
|
|
pub fn drawCircle(self: *Frame, center: Vec2, radius: f32, color: Vec4) void {
|
|
self.pushGraphicsCommand(.{
|
|
.draw_circle = .{
|
|
.center = center,
|
|
.radius = radius,
|
|
.color = color,
|
|
}
|
|
});
|
|
}
|
|
|
|
pub fn drawRectangle(self: *Frame, opts: GraphicsCommand.DrawRectangle) void {
|
|
self.pushGraphicsCommand(.{ .draw_rectangle = opts });
|
|
}
|
|
|
|
pub fn drawLine(self: *Frame, pos1: Vec2, pos2: Vec2, color: Vec4, width: f32) void {
|
|
self.pushGraphicsCommand(.{
|
|
.draw_line = .{
|
|
.pos1 = pos1,
|
|
.pos2 = pos2,
|
|
.width = width,
|
|
.color = color
|
|
}
|
|
});
|
|
}
|
|
|
|
pub fn drawRectanglOutline(self: *Frame, pos: Vec2, size: Vec2, color: Vec4, width: f32) void {
|
|
// TODO: Don't use line segments
|
|
self.drawLine(pos, pos.add(.{ .x = size.x, .y = 0 }), color, width);
|
|
self.drawLine(pos, pos.add(.{ .x = 0, .y = size.y }), color, width);
|
|
self.drawLine(pos.add(.{ .x = 0, .y = size.y }), pos.add(size), color, width);
|
|
self.drawLine(pos.add(.{ .x = size.x, .y = 0 }), pos.add(size), color, width);
|
|
}
|
|
|
|
pub const DrawTextOptions = struct {
|
|
font: Font.Id,
|
|
size: f32 = 16,
|
|
color: Vec4 = rgb(255, 255, 255),
|
|
};
|
|
|
|
pub fn drawText(self: *Frame, position: Vec2, opts: DrawTextOptions, text: []const u8) void {
|
|
const arena = self.arena.allocator();
|
|
const text_dupe = arena.dupe(u8, text) catch |e| {
|
|
log.warn("Failed to draw text: {}", .{e});
|
|
return;
|
|
};
|
|
|
|
self.pushGraphicsCommand(.{
|
|
.draw_text = .{
|
|
.pos = position,
|
|
.text = text_dupe,
|
|
.size = opts.size,
|
|
.font = opts.font,
|
|
.color = opts.color,
|
|
}
|
|
});
|
|
}
|
|
|
|
pub fn drawTextFormat(
|
|
self: *Frame,
|
|
position: Vec2,
|
|
opts: DrawTextOptions,
|
|
comptime fmt: []const u8,
|
|
args: anytype
|
|
) void {
|
|
const arena = self.arena.allocator();
|
|
const text = std.fmt.allocPrint(arena, fmt, args) catch |e| {
|
|
log.warn("Failed to draw text: {}", .{e});
|
|
return;
|
|
};
|
|
|
|
self.pushGraphicsCommand(.{
|
|
.draw_text = .{
|
|
.pos = position,
|
|
.text = text,
|
|
.size = opts.size,
|
|
.font = opts.font,
|
|
.color = opts.color,
|
|
}
|
|
});
|
|
}
|
|
|
|
pub fn pushTransform(self: *Frame, translation: Vec2, scale: Vec2) void {
|
|
self.pushGraphicsCommand(.{
|
|
.push_transformation = .{
|
|
.translation = translation,
|
|
.scale = scale
|
|
}
|
|
});
|
|
}
|
|
|
|
pub fn popTransform(self: *Frame) void {
|
|
self.pushGraphicsCommand(.{
|
|
.pop_transformation = {}
|
|
});
|
|
}
|