update engine
This commit is contained in:
parent
3c0c6d64ca
commit
8434ea6a64
@ -60,5 +60,15 @@ pub const Data = union(enum) {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getSampleRate(self: Data) u32 {
|
||||
return switch (self) {
|
||||
.raw => |opts| opts.sample_rate,
|
||||
.vorbis => |opts| blk: {
|
||||
const info = opts.stb_vorbis.getInfo();
|
||||
break :blk info.sample_rate;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const Id = enum (u16) { _ };
|
||||
};
|
||||
|
||||
@ -13,13 +13,17 @@ const Mixer = @This();
|
||||
|
||||
pub const Instance = struct {
|
||||
data_id: AudioData.Id,
|
||||
volume: f32 = 0,
|
||||
cursor: u32 = 0,
|
||||
};
|
||||
|
||||
pub const Command = union(enum) {
|
||||
play: struct {
|
||||
data_id: AudioData.Id,
|
||||
},
|
||||
pub const Play = struct {
|
||||
id: AudioData.Id,
|
||||
volume: f32 = 1
|
||||
};
|
||||
|
||||
play: Play,
|
||||
|
||||
pub const RingBuffer = struct {
|
||||
// TODO: This ring buffer will work in a single producer single consumer configuration
|
||||
@ -62,11 +66,13 @@ pub const Command = union(enum) {
|
||||
|
||||
instances: std.ArrayList(Instance),
|
||||
commands: Command.RingBuffer,
|
||||
working_buffer: []f32,
|
||||
|
||||
pub fn init(
|
||||
gpa: Allocator,
|
||||
max_instances: u32,
|
||||
max_commands: u32
|
||||
max_commands: u32,
|
||||
working_buffer_size: u32
|
||||
) !Mixer {
|
||||
var instances = try std.ArrayList(Instance).initCapacity(gpa, max_instances);
|
||||
errdefer instances.deinit(gpa);
|
||||
@ -74,7 +80,11 @@ pub fn init(
|
||||
const commands = try gpa.alloc(Command, max_commands);
|
||||
errdefer gpa.free(commands);
|
||||
|
||||
const working_buffer = try gpa.alloc(f32, working_buffer_size);
|
||||
errdefer gpa.free(working_buffer);
|
||||
|
||||
return Mixer{
|
||||
.working_buffer = working_buffer,
|
||||
.instances = instances,
|
||||
.commands = .{
|
||||
.items = commands
|
||||
@ -85,6 +95,7 @@ pub fn init(
|
||||
pub fn deinit(self: *Mixer, gpa: Allocator) void {
|
||||
self.instances.deinit(gpa);
|
||||
gpa.free(self.commands.items);
|
||||
gpa.free(self.working_buffer);
|
||||
}
|
||||
|
||||
pub fn queue(self: *Mixer, command: Command) void {
|
||||
@ -95,8 +106,15 @@ pub fn stream(self: *Mixer, store: Store, buffer: []f32, num_frames: u32, num_ch
|
||||
while (self.commands.pop()) |command| {
|
||||
switch (command) {
|
||||
.play => |opts| {
|
||||
const volume = @max(opts.volume, 0);
|
||||
if (volume == 0) {
|
||||
log.warn("Attempt to play audio with 0 volume", .{});
|
||||
continue;
|
||||
}
|
||||
|
||||
self.instances.appendBounded(.{
|
||||
.data_id = opts.data_id
|
||||
.data_id = opts.id,
|
||||
.volume = volume,
|
||||
}) catch log.warn("Maximum number of audio instances reached!", .{});
|
||||
}
|
||||
}
|
||||
@ -106,11 +124,14 @@ pub fn stream(self: *Mixer, store: Store, buffer: []f32, num_frames: u32, num_ch
|
||||
const sample_rate: u32 = @intCast(saudio.sampleRate());
|
||||
|
||||
@memset(buffer, 0);
|
||||
assert(self.working_buffer.len >= num_frames);
|
||||
|
||||
// var written: u32 = 0;
|
||||
for (self.instances.items) |*instance| {
|
||||
const audio_data = store.get(instance.data_id);
|
||||
const samples = audio_data.streamChannel(buffer[0..num_frames], instance.cursor, 0, sample_rate);
|
||||
const samples = audio_data.streamChannel(self.working_buffer[0..num_frames], instance.cursor, 0, sample_rate);
|
||||
for (0.., samples) |i, sample| {
|
||||
buffer[i] += sample * instance.volume;
|
||||
}
|
||||
instance.cursor += @intCast(samples.len);
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,9 @@ pub const Data = @import("./data.zig").Data;
|
||||
pub const Store = @import("./store.zig");
|
||||
pub const Mixer = @import("./mixer.zig");
|
||||
|
||||
pub const Command = Mixer.Command;
|
||||
|
||||
const Nanoseconds = @import("../root.zig").Nanoseconds;
|
||||
const sokol = @import("sokol");
|
||||
const saudio = sokol.audio;
|
||||
|
||||
@ -36,7 +39,11 @@ pub fn init(opts: Options) !void {
|
||||
.max_vorbis_alloc_buffer_size = opts.max_vorbis_alloc_buffer_size,
|
||||
});
|
||||
|
||||
mixer = try Mixer.init(gpa, opts.max_instances, opts.max_instances);
|
||||
mixer = try Mixer.init(gpa,
|
||||
opts.max_instances,
|
||||
opts.max_instances,
|
||||
opts.buffer_frames
|
||||
);
|
||||
|
||||
saudio.setup(.{
|
||||
.logger = opts.logger,
|
||||
@ -62,17 +69,21 @@ pub fn load(opts: Store.LoadOptions) !Data.Id {
|
||||
return try store.load(opts);
|
||||
}
|
||||
|
||||
pub const PlayOptions = struct {
|
||||
id: Data.Id,
|
||||
delay: f32 = 0
|
||||
const Info = struct {
|
||||
sample_count: u32,
|
||||
sample_rate: u32,
|
||||
|
||||
pub fn getDuration(self: Info) Nanoseconds {
|
||||
return @as(Nanoseconds, self.sample_count) * std.time.ns_per_s / self.sample_rate;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn play(opts: PlayOptions) void {
|
||||
mixer.queue(.{
|
||||
.play = .{
|
||||
.data_id = opts.id
|
||||
}
|
||||
});
|
||||
pub fn getInfo(id: Data.Id) Info {
|
||||
const data = store.get(id);
|
||||
return Info{
|
||||
.sample_count = data.getSampleCount(),
|
||||
.sample_rate = data.getSampleRate(),
|
||||
};
|
||||
}
|
||||
|
||||
fn sokolStreamCallback(buffer: [*c]f32, num_frames: i32, num_channels: i32) callconv(.c) void {
|
||||
|
||||
291
src/engine/frame.zig
Normal file
291
src/engine/frame.zig
Normal file
@ -0,0 +1,291 @@
|
||||
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,
|
||||
|
||||
pub const empty = Input{
|
||||
.keyboard = .empty,
|
||||
.mouse_button = .empty,
|
||||
.mouse_position = null,
|
||||
};
|
||||
};
|
||||
|
||||
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 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, text: []const u8, opts: DrawTextOptions) 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 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 = {}
|
||||
});
|
||||
}
|
||||
@ -21,6 +21,8 @@ const tracy = @import("tracy");
|
||||
const fontstash = @import("./fontstash/root.zig");
|
||||
pub const Font = fontstash.Font;
|
||||
|
||||
const GraphicsFrame = @import("./frame.zig").Graphics;
|
||||
|
||||
// TODO: Seems that there is a vertical jitter bug when resizing a window in OpenGL. Seems like a driver bug.
|
||||
// From other peoples research it seems that disabling vsync when a resize event occurs fixes it.
|
||||
// Maybe a patch for sokol could be made?
|
||||
@ -39,29 +41,75 @@ const Options = struct {
|
||||
imgui_font: ?ImguiFont = null
|
||||
};
|
||||
|
||||
const DrawFrame = struct {
|
||||
screen_size: Vec2 = Vec2.zero,
|
||||
bg_color: Vec4 = rgb(0, 0, 0),
|
||||
pub const Command = union(enum) {
|
||||
pub const DrawRectangle = struct {
|
||||
rect: Rect,
|
||||
color: Vec4,
|
||||
sprite: ?Sprite = null,
|
||||
rotation: f32 = 0,
|
||||
origin: Vec2 = .init(0, 0),
|
||||
};
|
||||
|
||||
scissor_stack_buffer: [32]Rect = undefined,
|
||||
scissor_stack: std.ArrayListUnmanaged(Rect) = .empty,
|
||||
|
||||
fn init(self: *DrawFrame) void {
|
||||
self.* = DrawFrame{
|
||||
.scissor_stack = .initBuffer(&self.scissor_stack_buffer)
|
||||
};
|
||||
}
|
||||
set_scissor: Rect,
|
||||
draw_rectangle: DrawRectangle,
|
||||
draw_line: struct {
|
||||
pos1: Vec2,
|
||||
pos2: Vec2,
|
||||
color: Vec4,
|
||||
width: f32
|
||||
},
|
||||
draw_text: struct {
|
||||
pos: Vec2,
|
||||
text: []const u8,
|
||||
font: Font.Id,
|
||||
size: f32,
|
||||
color: Vec4,
|
||||
},
|
||||
push_transformation: struct {
|
||||
translation: Vec2,
|
||||
scale: Vec2
|
||||
},
|
||||
pop_transformation: void
|
||||
};
|
||||
|
||||
var draw_frame: DrawFrame = undefined;
|
||||
const Texture = struct {
|
||||
image: sg.Image,
|
||||
view: sg.View,
|
||||
info: Info,
|
||||
|
||||
const Info = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
};
|
||||
|
||||
const Id = enum(u32) { _ };
|
||||
const Data = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
rgba: [*]u8
|
||||
};
|
||||
};
|
||||
pub const TextureId = Texture.Id;
|
||||
pub const TextureInfo = Texture.Info;
|
||||
|
||||
pub const Sprite = struct {
|
||||
texture: TextureId,
|
||||
uv: Rect,
|
||||
};
|
||||
|
||||
var gpa: std.mem.Allocator = undefined;
|
||||
|
||||
var main_pipeline: sgl.Pipeline = .{};
|
||||
var linear_sampler: sg.Sampler = .{};
|
||||
var nearest_sampler: sg.Sampler = .{};
|
||||
var font_context: fontstash.Context = undefined;
|
||||
pub var font_resolution_scale: f32 = 1;
|
||||
var textures: std.ArrayList(Texture) = .empty;
|
||||
|
||||
var scale_stack_buffer: [32]Vec2 = undefined;
|
||||
var scale_stack: std.ArrayList(Vec2) = .empty;
|
||||
|
||||
pub fn init(options: Options) !void {
|
||||
draw_frame.init();
|
||||
gpa = options.allocator;
|
||||
|
||||
sg.setup(.{
|
||||
.logger = options.logger,
|
||||
@ -134,49 +182,110 @@ pub fn init(options: Options) !void {
|
||||
}
|
||||
|
||||
pub fn deinit() void {
|
||||
textures.deinit(gpa);
|
||||
imgui.shutdown();
|
||||
font_context.deinit();
|
||||
sgl.shutdown();
|
||||
sg.shutdown();
|
||||
}
|
||||
|
||||
pub fn drawCommand(command: Command) void {
|
||||
switch(command) {
|
||||
.push_transformation => |opts| {
|
||||
pushTransform(opts.translation, opts.scale);
|
||||
// font_resolution_scale = font_resolution_scale.multiply(opts.scale);
|
||||
},
|
||||
.pop_transformation => {
|
||||
popTransform();
|
||||
},
|
||||
.draw_rectangle => |opts| {
|
||||
drawRectangle(opts);
|
||||
},
|
||||
.set_scissor => |opts| {
|
||||
sgl.scissorRectf(
|
||||
opts.pos.x,
|
||||
opts.pos.y,
|
||||
opts.size.x,
|
||||
opts.size.y,
|
||||
true
|
||||
);
|
||||
},
|
||||
.draw_line => |opts| {
|
||||
drawLine(
|
||||
opts.pos1,
|
||||
opts.pos2,
|
||||
opts.color,
|
||||
opts.width
|
||||
);
|
||||
},
|
||||
.draw_text => |opts| {
|
||||
const font_resolution_scale = scale_stack.getLast();
|
||||
|
||||
sgl.pushMatrix();
|
||||
defer sgl.popMatrix();
|
||||
|
||||
sgl.scale(1/font_resolution_scale.x, 1/font_resolution_scale.y, 1);
|
||||
|
||||
font_context.setFont(opts.font);
|
||||
font_context.setSize(opts.size * font_resolution_scale.y);
|
||||
font_context.setAlign(.{ .x = .left, .y = .top });
|
||||
font_context.setSpacing(0);
|
||||
|
||||
const r: u8 = @intFromFloat(opts.color.x * 255);
|
||||
const g: u8 = @intFromFloat(opts.color.y * 255);
|
||||
const b: u8 = @intFromFloat(opts.color.z * 255);
|
||||
const a: u8 = @intFromFloat(opts.color.w * 255);
|
||||
const color: u32 = r | (@as(u32, g) << 8) | (@as(u32, b) << 16) | (@as(u32, a) << 24);
|
||||
font_context.setColor(color);
|
||||
font_context.drawText(
|
||||
opts.pos.x * font_resolution_scale.x,
|
||||
opts.pos.y * font_resolution_scale.y,
|
||||
opts.text
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawCommands(commands: []const Command) void {
|
||||
for (commands) |command| {
|
||||
drawCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn beginFrame() void {
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
draw_frame.init();
|
||||
draw_frame.screen_size = Vec2.init(sapp.widthf(), sapp.heightf());
|
||||
draw_frame.scissor_stack.appendAssumeCapacity(Rect.init(0, 0, sapp.widthf(), sapp.heightf()));
|
||||
|
||||
imgui.newFrame(.{
|
||||
.width = @intFromFloat(draw_frame.screen_size.x),
|
||||
.height = @intFromFloat(draw_frame.screen_size.y),
|
||||
.width = sapp.width(),
|
||||
.height = sapp.height(),
|
||||
.delta_time = sapp.frameDuration(),
|
||||
.dpi_scale = sapp.dpiScale()
|
||||
});
|
||||
|
||||
scale_stack = .initBuffer(&scale_stack_buffer);
|
||||
scale_stack.appendAssumeCapacity(.init(1, 1));
|
||||
|
||||
font_context.clearState();
|
||||
sgl.defaults();
|
||||
sgl.matrixModeProjection();
|
||||
sgl.ortho(0, draw_frame.screen_size.x, draw_frame.screen_size.y, 0, -1, 1);
|
||||
sgl.ortho(0, sapp.widthf(), sapp.heightf(), 0, -1, 1);
|
||||
sgl.loadPipeline(main_pipeline);
|
||||
}
|
||||
|
||||
pub fn endFrame() void {
|
||||
pub fn endFrame(clear_color: Vec4) void {
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
assert(draw_frame.scissor_stack.items.len == 1);
|
||||
|
||||
var pass_action: sg.PassAction = .{};
|
||||
|
||||
pass_action.colors[0] = sg.ColorAttachmentAction{
|
||||
.load_action = .CLEAR,
|
||||
.clear_value = .{
|
||||
.r = draw_frame.bg_color.x,
|
||||
.g = draw_frame.bg_color.y,
|
||||
.b = draw_frame.bg_color.z,
|
||||
.a = draw_frame.bg_color.w
|
||||
.r = clear_color.x,
|
||||
.g = clear_color.y,
|
||||
.b = clear_color.z,
|
||||
.a = clear_color.w
|
||||
}
|
||||
};
|
||||
|
||||
@ -196,53 +305,95 @@ pub fn endFrame() void {
|
||||
sg.commit();
|
||||
}
|
||||
|
||||
pub fn pushTransform(translation: Vec2, scale: f32) void {
|
||||
fn pushTransform(translation: Vec2, scale: Vec2) void {
|
||||
sgl.pushMatrix();
|
||||
sgl.translate(translation.x, translation.y, 0);
|
||||
sgl.scale(scale, scale, 1);
|
||||
sgl.scale(scale.x, scale.y, 1);
|
||||
|
||||
scale_stack.appendAssumeCapacity(scale_stack.getLast().multiply(scale));
|
||||
}
|
||||
|
||||
pub fn popTransform() void {
|
||||
fn popTransform() void {
|
||||
sgl.popMatrix();
|
||||
_ = scale_stack.pop().?;
|
||||
}
|
||||
|
||||
pub fn drawQuad(quad: [4]Vec2, color: Vec4) void {
|
||||
const Vertex = struct {
|
||||
pos: Vec2,
|
||||
uv: Vec2
|
||||
};
|
||||
|
||||
fn drawQuad(quad: [4]Vertex, color: Vec4, texture_id: TextureId) void {
|
||||
sgl.enableTexture();
|
||||
defer sgl.disableTexture();
|
||||
|
||||
const view = textures.items[@intFromEnum(texture_id)].view;
|
||||
// TODO: Make sampler configurable
|
||||
sgl.texture(view, nearest_sampler);
|
||||
|
||||
sgl.beginQuads();
|
||||
defer sgl.end();
|
||||
|
||||
sgl.c4f(color.x, color.y, color.z, color.w);
|
||||
for (quad) |position| {
|
||||
sgl.v2f(position.x, position.y);
|
||||
for (quad) |vertex| {
|
||||
const pos = vertex.pos;
|
||||
const uv = vertex.uv;
|
||||
sgl.v2fT2f(pos.x, pos.y, uv.x, uv.y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawRectangle(pos: Vec2, size: Vec2, color: Vec4) void {
|
||||
drawQuad(
|
||||
.{
|
||||
// Top left
|
||||
pos,
|
||||
// Top right
|
||||
pos.add(.{ .x = size.x, .y = 0 }),
|
||||
// Bottom right
|
||||
pos.add(size),
|
||||
// Bottom left
|
||||
pos.add(.{ .x = 0, .y = size.y }),
|
||||
},
|
||||
color
|
||||
);
|
||||
}
|
||||
|
||||
pub fn drawTriangle(tri: [3]Vec2, color: Vec4) void {
|
||||
sgl.beginTriangles();
|
||||
fn drawQuadNoUVs(quad: [4]Vec2, color: Vec4) void {
|
||||
sgl.beginQuads();
|
||||
defer sgl.end();
|
||||
|
||||
sgl.c4f(color.x, color.y, color.z, color.w);
|
||||
for (tri) |position| {
|
||||
sgl.v2f(position.x, position.y);
|
||||
for (quad) |pos| {
|
||||
sgl.v2f(pos.x, pos.y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
||||
fn drawRectangle(opts: Command.DrawRectangle) void {
|
||||
const pos = opts.rect.pos;
|
||||
const size = opts.rect.size;
|
||||
|
||||
const top_left = Vec2.init(0, 0).rotateAround(opts.rotation, opts.origin);
|
||||
const top_right = Vec2.init(size.x, 0).rotateAround(opts.rotation, opts.origin);
|
||||
const bottom_right = size.rotateAround(opts.rotation, opts.origin);
|
||||
const bottom_left = Vec2.init(0, size.y).rotateAround(opts.rotation, opts.origin);
|
||||
|
||||
if (opts.sprite) |sprite| {
|
||||
const uv = sprite.uv;
|
||||
const quad = [4]Vertex{
|
||||
.{
|
||||
.pos = pos.add(top_left),
|
||||
.uv = .init(uv.left(), uv.top())
|
||||
},
|
||||
.{
|
||||
.pos = pos.add(top_right),
|
||||
.uv = .init(uv.right(), uv.top())
|
||||
},
|
||||
.{
|
||||
.pos = pos.add(bottom_right),
|
||||
.uv = .init(uv.right(), uv.bottom())
|
||||
},
|
||||
.{
|
||||
.pos = pos.add(bottom_left),
|
||||
.uv = .init(uv.left(), uv.bottom())
|
||||
}
|
||||
};
|
||||
drawQuad(quad, opts.color, sprite.texture);
|
||||
} else {
|
||||
const quad = .{
|
||||
pos.add(top_left),
|
||||
pos.add(top_right),
|
||||
pos.add(bottom_right),
|
||||
pos.add(bottom_left)
|
||||
};
|
||||
drawQuadNoUVs(quad, opts.color);
|
||||
}
|
||||
}
|
||||
|
||||
fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
||||
const step = to.sub(from).normalized().multiplyScalar(width/2);
|
||||
|
||||
const top_left = from.add(step.rotateLeft90());
|
||||
@ -250,61 +401,80 @@ pub fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
||||
const top_right = to.add(step.rotateLeft90());
|
||||
const bottom_right = to.add(step.rotateRight90());
|
||||
|
||||
drawQuad(
|
||||
drawQuadNoUVs(
|
||||
.{ top_right, top_left, bottom_left, bottom_right },
|
||||
color
|
||||
);
|
||||
}
|
||||
|
||||
pub fn drawRectanglOutline(pos: Vec2, size: Vec2, color: Vec4, width: f32) void {
|
||||
// TODO: Don't use line segments
|
||||
drawLine(pos, pos.add(.{ .x = size.x, .y = 0 }), color, width);
|
||||
drawLine(pos, pos.add(.{ .x = 0, .y = size.y }), color, width);
|
||||
drawLine(pos.add(.{ .x = 0, .y = size.y }), pos.add(size), color, width);
|
||||
drawLine(pos.add(.{ .x = size.x, .y = 0 }), pos.add(size), color, width);
|
||||
}
|
||||
|
||||
pub fn pushScissor(rect: Rect) void {
|
||||
draw_frame.scissor_stack.appendAssumeCapacity(rect);
|
||||
|
||||
sgl.scissorRectf(rect.pos.x, rect.pos.y, rect.size.x, rect.size.y, true);
|
||||
}
|
||||
|
||||
pub fn popScissor() void {
|
||||
_ = draw_frame.scissor_stack.pop().?;
|
||||
const rect = draw_frame.scissor_stack.getLast();
|
||||
|
||||
sgl.scissorRectf(rect.pos.x, rect.pos.y, rect.size.x, rect.size.y, true);
|
||||
}
|
||||
|
||||
pub fn addFont(name: [*c]const u8, data: []const u8) !Font.Id {
|
||||
return try font_context.addFont(name, data);
|
||||
}
|
||||
|
||||
pub const DrawTextOptions = struct {
|
||||
font: Font.Id,
|
||||
size: f32 = 16,
|
||||
color: Vec4 = rgb(255, 255, 255),
|
||||
};
|
||||
|
||||
pub fn drawText(position: Vec2, text: []const u8, opts: DrawTextOptions) void {
|
||||
pushTransform(.{ .x = 0, .y = 0}, 1/font_resolution_scale);
|
||||
defer popTransform();
|
||||
|
||||
font_context.setFont(opts.font);
|
||||
font_context.setSize(opts.size * font_resolution_scale);
|
||||
font_context.setAlign(.{ .x = .left, .y = .top });
|
||||
font_context.setSpacing(0);
|
||||
|
||||
const r: u8 = @intFromFloat(opts.color.x * 255);
|
||||
const g: u8 = @intFromFloat(opts.color.y * 255);
|
||||
const b: u8 = @intFromFloat(opts.color.z * 255);
|
||||
const a: u8 = @intFromFloat(opts.color.w * 255);
|
||||
const color: u32 = r | (@as(u32, g) << 8) | (@as(u32, b) << 16) | (@as(u32, a) << 24);
|
||||
font_context.setColor(color);
|
||||
font_context.drawText(
|
||||
position.x * font_resolution_scale,
|
||||
position.y * font_resolution_scale,
|
||||
text
|
||||
);
|
||||
fn makeView(image: sg.Image) !sg.View {
|
||||
const image_view = sg.makeView(.{
|
||||
.texture = .{ .image = image }
|
||||
});
|
||||
if (image_view.id == sg.invalid_id) {
|
||||
return error.InvalidView;
|
||||
}
|
||||
return image_view;
|
||||
}
|
||||
|
||||
fn makeImageWithMipMaps(mipmaps: []const Texture.Data) !sg.Image {
|
||||
if (mipmaps.len == 0) {
|
||||
return error.NoMipMaps;
|
||||
}
|
||||
|
||||
var data: sg.ImageData = .{};
|
||||
var mip_levels: std.ArrayListUnmanaged(sg.Range) = .initBuffer(&data.mip_levels);
|
||||
|
||||
for (mipmaps) |mipmap| {
|
||||
try mip_levels.appendBounded(.{
|
||||
.ptr = mipmap.rgba,
|
||||
.size = mipmap.width * mipmap.height * 4
|
||||
});
|
||||
}
|
||||
|
||||
const image = sg.makeImage(.{
|
||||
.width = @intCast(mipmaps[0].width),
|
||||
.height = @intCast(mipmaps[0].height),
|
||||
.pixel_format = .RGBA8,
|
||||
.usage = .{
|
||||
.immutable = true
|
||||
},
|
||||
.num_mipmaps = @intCast(mip_levels.items.len),
|
||||
.data = data
|
||||
});
|
||||
if (image.id == sg.invalid_id) {
|
||||
return error.InvalidImage;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
pub fn addTexture(mipmaps: []const Texture.Data) !TextureId {
|
||||
const image = try makeImageWithMipMaps(mipmaps);
|
||||
errdefer sg.deallocImage(image);
|
||||
|
||||
const view = try makeView(image);
|
||||
errdefer sg.deallocView(view);
|
||||
|
||||
assert(mipmaps.len > 0);
|
||||
const index = textures.items.len;
|
||||
try textures.append(gpa, .{
|
||||
.image = image,
|
||||
.view = view,
|
||||
.info = .{
|
||||
.width = mipmaps[0].width,
|
||||
.height = mipmaps[0].height,
|
||||
}
|
||||
});
|
||||
|
||||
return @enumFromInt(index);
|
||||
}
|
||||
|
||||
pub fn getTextureInfo(id: TextureId) TextureInfo {
|
||||
const texture = textures.items[@intFromEnum(id)];
|
||||
return texture.info;
|
||||
}
|
||||
|
||||
@ -1,239 +1,124 @@
|
||||
const std = @import("std");
|
||||
const sokol = @import("sokol");
|
||||
|
||||
const Engine = @import("./root.zig");
|
||||
const Nanoseconds = Engine.Nanoseconds;
|
||||
const Frame = @import("./frame.zig");
|
||||
const Nanoseconds = Frame.Nanoseconds;
|
||||
|
||||
const Math = @import("./math.zig");
|
||||
const Vec2 = Math.Vec2;
|
||||
|
||||
const Input = @This();
|
||||
|
||||
pub const KeyCode = enum(std.math.IntFittingRange(0, sokol.app.max_keycodes-1)) {
|
||||
SPACE = 32,
|
||||
APOSTROPHE = 39,
|
||||
COMMA = 44,
|
||||
MINUS = 45,
|
||||
PERIOD = 46,
|
||||
SLASH = 47,
|
||||
_0 = 48,
|
||||
_1 = 49,
|
||||
_2 = 50,
|
||||
_3 = 51,
|
||||
_4 = 52,
|
||||
_5 = 53,
|
||||
_6 = 54,
|
||||
_7 = 55,
|
||||
_8 = 56,
|
||||
_9 = 57,
|
||||
SEMICOLON = 59,
|
||||
EQUAL = 61,
|
||||
A = 65,
|
||||
B = 66,
|
||||
C = 67,
|
||||
D = 68,
|
||||
E = 69,
|
||||
F = 70,
|
||||
G = 71,
|
||||
H = 72,
|
||||
I = 73,
|
||||
J = 74,
|
||||
K = 75,
|
||||
L = 76,
|
||||
M = 77,
|
||||
N = 78,
|
||||
O = 79,
|
||||
P = 80,
|
||||
Q = 81,
|
||||
R = 82,
|
||||
S = 83,
|
||||
T = 84,
|
||||
U = 85,
|
||||
V = 86,
|
||||
W = 87,
|
||||
X = 88,
|
||||
Y = 89,
|
||||
Z = 90,
|
||||
LEFT_BRACKET = 91,
|
||||
BACKSLASH = 92,
|
||||
RIGHT_BRACKET = 93,
|
||||
GRAVE_ACCENT = 96,
|
||||
WORLD_1 = 161,
|
||||
WORLD_2 = 162,
|
||||
ESCAPE = 256,
|
||||
ENTER = 257,
|
||||
TAB = 258,
|
||||
BACKSPACE = 259,
|
||||
INSERT = 260,
|
||||
DELETE = 261,
|
||||
RIGHT = 262,
|
||||
LEFT = 263,
|
||||
DOWN = 264,
|
||||
UP = 265,
|
||||
PAGE_UP = 266,
|
||||
PAGE_DOWN = 267,
|
||||
HOME = 268,
|
||||
END = 269,
|
||||
CAPS_LOCK = 280,
|
||||
SCROLL_LOCK = 281,
|
||||
NUM_LOCK = 282,
|
||||
PRINT_SCREEN = 283,
|
||||
PAUSE = 284,
|
||||
F1 = 290,
|
||||
F2 = 291,
|
||||
F3 = 292,
|
||||
F4 = 293,
|
||||
F5 = 294,
|
||||
F6 = 295,
|
||||
F7 = 296,
|
||||
F8 = 297,
|
||||
F9 = 298,
|
||||
F10 = 299,
|
||||
F11 = 300,
|
||||
F12 = 301,
|
||||
F13 = 302,
|
||||
F14 = 303,
|
||||
F15 = 304,
|
||||
F16 = 305,
|
||||
F17 = 306,
|
||||
F18 = 307,
|
||||
F19 = 308,
|
||||
F20 = 309,
|
||||
F21 = 310,
|
||||
F22 = 311,
|
||||
F23 = 312,
|
||||
F24 = 313,
|
||||
F25 = 314,
|
||||
KP_0 = 320,
|
||||
KP_1 = 321,
|
||||
KP_2 = 322,
|
||||
KP_3 = 323,
|
||||
KP_4 = 324,
|
||||
KP_5 = 325,
|
||||
KP_6 = 326,
|
||||
KP_7 = 327,
|
||||
KP_8 = 328,
|
||||
KP_9 = 329,
|
||||
KP_DECIMAL = 330,
|
||||
KP_DIVIDE = 331,
|
||||
KP_MULTIPLY = 332,
|
||||
KP_SUBTRACT = 333,
|
||||
KP_ADD = 334,
|
||||
KP_ENTER = 335,
|
||||
KP_EQUAL = 336,
|
||||
LEFT_SHIFT = 340,
|
||||
LEFT_CONTROL = 341,
|
||||
LEFT_ALT = 342,
|
||||
LEFT_SUPER = 343,
|
||||
RIGHT_SHIFT = 344,
|
||||
RIGHT_CONTROL = 345,
|
||||
RIGHT_ALT = 346,
|
||||
RIGHT_SUPER = 347,
|
||||
MENU = 348,
|
||||
};
|
||||
|
||||
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;
|
||||
const SokolKeyCodeInfo = @typeInfo(sokol.app.Keycode);
|
||||
pub const KeyCode = @Type(.{
|
||||
.@"enum" = .{
|
||||
.tag_type = std.math.IntFittingRange(0, sokol.app.max_keycodes-1),
|
||||
.decls = SokolKeyCodeInfo.@"enum".decls,
|
||||
.fields = SokolKeyCodeInfo.@"enum".fields,
|
||||
.is_exhaustive = false
|
||||
}
|
||||
});
|
||||
|
||||
pub const MouseButton = enum(i3) {
|
||||
left = @intFromEnum(sokol.app.Mousebutton.LEFT),
|
||||
right = @intFromEnum(sokol.app.Mousebutton.RIGHT),
|
||||
middle = @intFromEnum(sokol.app.Mousebutton.MIDDLE),
|
||||
_
|
||||
};
|
||||
|
||||
pub const Mouse = struct {
|
||||
pub const Button = enum {
|
||||
left,
|
||||
right,
|
||||
middle,
|
||||
pub fn ButtonStateSet(E: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
pub fn fromSokol(mouse_button: sokol.app.Mousebutton) ?Button {
|
||||
return switch(mouse_button) {
|
||||
.LEFT => Button.left,
|
||||
.RIGHT => Button.right,
|
||||
.MIDDLE => Button.middle,
|
||||
else => null
|
||||
};
|
||||
down: std.EnumSet(E),
|
||||
pressed: std.EnumSet(E),
|
||||
released: std.EnumSet(E),
|
||||
pressed_at: std.EnumMap(E, Nanoseconds),
|
||||
|
||||
pub const empty = Self{
|
||||
.down = .initEmpty(),
|
||||
.pressed = .initEmpty(),
|
||||
.released = .initEmpty(),
|
||||
.pressed_at = .init(.{}),
|
||||
};
|
||||
|
||||
fn press(self: *Self, button: E, now: Nanoseconds) void {
|
||||
self.pressed_at.put(button, now);
|
||||
self.pressed.insert(button);
|
||||
self.down.insert(button);
|
||||
}
|
||||
|
||||
fn release(self: *Self, button: E) void {
|
||||
self.down.remove(button);
|
||||
self.released.insert(button);
|
||||
self.pressed_at.remove(button);
|
||||
}
|
||||
|
||||
fn releaseAll(self: *Self) void {
|
||||
var iter = self.down.iterator();
|
||||
while (iter.next()) |key_code| {
|
||||
self.released.insert(key_code);
|
||||
}
|
||||
self.down = .initEmpty();
|
||||
self.pressed_at = .init(.{});
|
||||
}
|
||||
};
|
||||
|
||||
position: ?Vec2,
|
||||
buttons: std.EnumSet(Button),
|
||||
|
||||
pub const empty = Mouse{
|
||||
.position = null,
|
||||
.buttons = .initEmpty()
|
||||
};
|
||||
};
|
||||
|
||||
down_keys: std.EnumSet(KeyCode),
|
||||
pressed_keys: std.EnumSet(KeyCode),
|
||||
released_keys: std.EnumSet(KeyCode),
|
||||
pressed_keys_at: std.EnumMap(KeyCode, Nanoseconds),
|
||||
|
||||
mouse: Mouse,
|
||||
|
||||
pub const empty = Input{
|
||||
.down_keys = .initEmpty(),
|
||||
.pressed_keys = .initEmpty(),
|
||||
.released_keys = .initEmpty(),
|
||||
.pressed_keys_at = .init(.{}),
|
||||
.mouse = .empty
|
||||
};
|
||||
|
||||
pub fn isKeyDown(self: *Input, key_code: KeyCode) bool {
|
||||
return self.down_keys.contains(key_code);
|
||||
}
|
||||
|
||||
pub fn getKeyDownDuration(self: *Input, frame: Engine.Frame, key_code: KeyCode) ?f64 {
|
||||
if (!self.isKeyDown(key_code)) {
|
||||
return null;
|
||||
pub const Event = union(enum) {
|
||||
mouse_pressed: struct {
|
||||
button: MouseButton,
|
||||
position: Vec2,
|
||||
},
|
||||
mouse_released: struct {
|
||||
button: MouseButton,
|
||||
position: Vec2,
|
||||
},
|
||||
mouse_move: Vec2,
|
||||
mouse_enter: Vec2,
|
||||
mouse_leave,
|
||||
mouse_scroll: Vec2,
|
||||
key_pressed: struct {
|
||||
code: KeyCode,
|
||||
repeat: bool
|
||||
},
|
||||
key_released: Input.KeyCode,
|
||||
window_resize,
|
||||
char: u21,
|
||||
};
|
||||
|
||||
pub fn processEvent(frame: *Frame, event: Event) void {
|
||||
const input = &frame.input;
|
||||
|
||||
switch (event) {
|
||||
.key_pressed => |opts| {
|
||||
if (!opts.repeat) {
|
||||
input.keyboard.press(opts.code, frame.time_ns);
|
||||
}
|
||||
},
|
||||
.key_released => |key_code| {
|
||||
input.keyboard.release(key_code);
|
||||
},
|
||||
.mouse_leave => {
|
||||
input.keyboard.releaseAll();
|
||||
|
||||
input.mouse_position = null;
|
||||
input.mouse_button = .empty;
|
||||
},
|
||||
.mouse_enter => |pos| {
|
||||
input.mouse_position = pos;
|
||||
},
|
||||
.mouse_move => |pos| {
|
||||
input.mouse_position = pos;
|
||||
},
|
||||
.mouse_pressed => |opts| {
|
||||
input.mouse_position = opts.position;
|
||||
input.mouse_button.press(opts.button, frame.time_ns);
|
||||
},
|
||||
.mouse_released => |opts| {
|
||||
input.mouse_position = opts.position;
|
||||
input.mouse_button.release(opts.button);
|
||||
},
|
||||
else => {}
|
||||
}
|
||||
|
||||
const pressed_at_ns = self.pressed_keys_at.get(key_code).?;
|
||||
const duration_ns = frame.time_ns - pressed_at_ns;
|
||||
|
||||
return @as(f64, @floatFromInt(duration_ns)) / std.time.ns_per_s;
|
||||
}
|
||||
|
||||
pub fn isKeyPressed(self: *Input, key_code: KeyCode) bool {
|
||||
return self.pressed_keys.contains(key_code);
|
||||
}
|
||||
|
||||
pub fn isKeyReleased(self: *Input, key_code: KeyCode) bool {
|
||||
return self.released_keys.contains(key_code);
|
||||
}
|
||||
|
||||
pub fn getKeyState(self: *Input, frame: Engine.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(frame, key_code)
|
||||
};
|
||||
}
|
||||
|
||||
@ -9,6 +9,28 @@ pub const bytes_per_kb = 1000;
|
||||
pub const bytes_per_mb = bytes_per_kb * 1000;
|
||||
pub const bytes_per_gb = bytes_per_mb * 1000;
|
||||
|
||||
pub const Range = struct {
|
||||
from: f32,
|
||||
to: f32,
|
||||
|
||||
pub const zero = init(0, 0);
|
||||
|
||||
pub fn init(from: f32, to: f32) Range {
|
||||
return Range{
|
||||
.from = from,
|
||||
.to = to
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getSize(self: Range) f32 {
|
||||
return @abs(self.from - self.to);
|
||||
}
|
||||
|
||||
pub fn random(self: Range, rng: std.Random) f32 {
|
||||
return self.from + rng.float(f32) * (self.to - self.from);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Vec2 = extern struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
@ -22,6 +44,10 @@ pub const Vec2 = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initFromInt(T: type, x: T, y: T) Vec2 {
|
||||
return .init(@floatFromInt(x), @floatFromInt(y));
|
||||
}
|
||||
|
||||
pub fn initAngle(angle: f32) Vec2 {
|
||||
return Vec2{
|
||||
.x = @cos(angle),
|
||||
@ -37,6 +63,21 @@ pub const Vec2 = extern struct {
|
||||
return Vec2.init(-self.y, self.x);
|
||||
}
|
||||
|
||||
pub fn rotate(self: Vec2, angle: f32) Vec2 {
|
||||
return init(
|
||||
@cos(angle) * self.x - @sin(angle) * self.y,
|
||||
@sin(angle) * self.x + @cos(angle) * self.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn rotateAround(self: Vec2, angle: f32, origin: Vec2) Vec2 {
|
||||
return self.sub(origin).rotate(angle).add(origin);
|
||||
}
|
||||
|
||||
pub fn getAngle(self: Vec2) f32 {
|
||||
return std.math.atan2(self.y, self.x);
|
||||
}
|
||||
|
||||
pub fn flip(self: Vec2) Vec2 {
|
||||
return Vec2.init(-self.x, -self.y);
|
||||
}
|
||||
@ -83,18 +124,29 @@ pub const Vec2 = extern struct {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn lengthSqr(self: Vec2) f32 {
|
||||
return self.x*self.x + self.y*self.y;
|
||||
}
|
||||
|
||||
pub fn length(self: Vec2) f32 {
|
||||
return @sqrt(self.x*self.x + self.y*self.y);
|
||||
return @sqrt(self.lengthSqr());
|
||||
}
|
||||
|
||||
pub fn distance(self: Vec2, other: Vec2) f32 {
|
||||
return self.sub(other).length();
|
||||
}
|
||||
|
||||
pub fn distanceSqr(self: Vec2, other: Vec2) f32 {
|
||||
return self.sub(other).lengthSqr();
|
||||
}
|
||||
|
||||
pub fn limitLength(self: Vec2, max_length: f32) Vec2 {
|
||||
const self_length = self.length();
|
||||
if (self_length > max_length) {
|
||||
return Vec2.init(self.x / self_length * max_length, self.y / self_length * max_length);
|
||||
if (self_length == 0) {
|
||||
return Vec2.init(0, 0);
|
||||
}
|
||||
return self.divideScalar(self_length / max_length);
|
||||
} else {
|
||||
return self;
|
||||
}
|
||||
@ -105,7 +157,7 @@ pub const Vec2 = extern struct {
|
||||
if (self_length == 0) {
|
||||
return Vec2.init(0, 0);
|
||||
}
|
||||
return Vec2.init(self.x / self_length, self.y / self_length);
|
||||
return self.divideScalar(self_length);
|
||||
}
|
||||
|
||||
pub fn initScalar(value: f32) Vec2 {
|
||||
@ -351,6 +403,10 @@ pub const Rect = struct {
|
||||
return self.pos.y + self.size.y;
|
||||
}
|
||||
|
||||
pub fn center(self: Rect) Vec2 {
|
||||
return self.pos.add(self.size.multiplyScalar(0.5));
|
||||
}
|
||||
|
||||
pub fn multiply(self: Rect, xy: Vec2) Rect {
|
||||
return Rect{
|
||||
.pos = self.pos.multiply(xy),
|
||||
|
||||
@ -7,9 +7,12 @@ const sapp = sokol.app;
|
||||
|
||||
pub const Math = @import("./math.zig");
|
||||
pub const Vec2 = Math.Vec2;
|
||||
const rgb = Math.rgb;
|
||||
|
||||
pub const Input = @import("./input.zig");
|
||||
|
||||
pub const Frame = @import("./frame.zig");
|
||||
|
||||
const ScreenScalar = @import("./screen_scaler.zig");
|
||||
pub const imgui = @import("./imgui.zig");
|
||||
pub const Graphics = @import("./graphics.zig");
|
||||
@ -27,43 +30,16 @@ const Engine = @This();
|
||||
|
||||
pub const Nanoseconds = u64;
|
||||
|
||||
pub const Event = union(enum) {
|
||||
mouse_pressed: struct {
|
||||
button: Input.Mouse.Button,
|
||||
position: Vec2,
|
||||
},
|
||||
mouse_released: struct {
|
||||
button: Input.Mouse.Button,
|
||||
position: Vec2,
|
||||
},
|
||||
mouse_move: Vec2,
|
||||
mouse_enter: Vec2,
|
||||
mouse_leave,
|
||||
mouse_scroll: Vec2,
|
||||
key_pressed: struct {
|
||||
code: Input.KeyCode,
|
||||
repeat: bool
|
||||
},
|
||||
key_released: Input.KeyCode,
|
||||
window_resize,
|
||||
char: u21,
|
||||
};
|
||||
|
||||
pub const Frame = struct {
|
||||
time_ns: Nanoseconds,
|
||||
dt_ns: Nanoseconds,
|
||||
dt: f32,
|
||||
input: *Input
|
||||
};
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
started_at: std.time.Instant,
|
||||
mouse_inside: bool,
|
||||
last_frame_at: Nanoseconds,
|
||||
input: Input,
|
||||
|
||||
game: Game,
|
||||
assets: Assets,
|
||||
frame: Frame,
|
||||
|
||||
canvas_size: ?Vec2 = null,
|
||||
|
||||
const RunOptions = struct {
|
||||
window_title: [*:0]const u8 = "Game",
|
||||
@ -75,11 +51,11 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
|
||||
self.* = Engine{
|
||||
.allocator = undefined,
|
||||
.started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"),
|
||||
.input = .empty,
|
||||
.mouse_inside = false,
|
||||
.last_frame_at = 0,
|
||||
.assets = undefined,
|
||||
.game = undefined
|
||||
.game = undefined,
|
||||
.frame = undefined
|
||||
};
|
||||
|
||||
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
|
||||
@ -94,6 +70,8 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
|
||||
self.allocator = std.heap.smp_allocator;
|
||||
}
|
||||
|
||||
self.frame.init(self.allocator);
|
||||
|
||||
tracy.setThreadName("Main");
|
||||
|
||||
if (builtin.os.tag == .linux) {
|
||||
@ -154,14 +132,17 @@ fn sokolInit(self: *Engine) !void {
|
||||
.logger = .{ .func = sokolLogCallback },
|
||||
});
|
||||
|
||||
const seed: u64 = @bitCast(std.time.milliTimestamp());
|
||||
|
||||
self.assets = try Assets.init(self.allocator);
|
||||
self.game = try Game.init(self.allocator, &self.assets);
|
||||
self.game = try Game.init(self.allocator, seed, &self.assets);
|
||||
}
|
||||
|
||||
fn sokolCleanup(self: *Engine) void {
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
self.frame.deinit();
|
||||
Audio.deinit();
|
||||
self.game.deinit();
|
||||
self.assets.deinit(self.allocator);
|
||||
@ -171,84 +152,111 @@ fn sokolCleanup(self: *Engine) void {
|
||||
fn sokolFrame(self: *Engine) !void {
|
||||
tracy.frameMark();
|
||||
|
||||
const time_passed = self.timePassed();
|
||||
defer self.last_frame_at = time_passed;
|
||||
const dt_ns = time_passed - self.last_frame_at;
|
||||
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
Gfx.beginFrame();
|
||||
defer Gfx.endFrame();
|
||||
const now = std.time.Instant.now() catch @panic("Instant.now() unsupported");
|
||||
const time_passed = now.since(self.started_at);
|
||||
defer self.last_frame_at = time_passed;
|
||||
|
||||
const frame = &self.frame;
|
||||
|
||||
const screen_size = Vec2.init(sapp.widthf(), sapp.heightf());
|
||||
|
||||
var revert_mouse_position: ?Vec2 = null;
|
||||
if (self.canvas_size) |canvas_size| {
|
||||
if (self.frame.input.mouse_position) |mouse| {
|
||||
const transform = ScreenScalar.init(screen_size, canvas_size);
|
||||
|
||||
revert_mouse_position = mouse;
|
||||
self.frame.input.mouse_position = mouse.sub(transform.translation).divideScalar(transform.scale);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const window_size: Vec2 = .init(sapp.widthf(), sapp.heightf());
|
||||
const ctx = ScreenScalar.push(window_size, self.game.canvas_size);
|
||||
defer ctx.pop();
|
||||
_ = frame.arena.reset(.retain_capacity);
|
||||
const arena = frame.arena.allocator();
|
||||
|
||||
Graphics.font_resolution_scale = ctx.scale;
|
||||
const audio_commands_capacity = frame.audio.commands.capacity;
|
||||
frame.audio = .empty;
|
||||
try frame.audio.commands.ensureTotalCapacity(arena, audio_commands_capacity);
|
||||
|
||||
try self.game.tick(Frame{
|
||||
.time_ns = time_passed,
|
||||
.dt_ns = dt_ns,
|
||||
.dt = @as(f32, @floatFromInt(dt_ns)) / std.time.ns_per_s,
|
||||
.input = &self.input
|
||||
});
|
||||
const graphics_commands_capacity = frame.graphics.commands.capacity;
|
||||
const scissor_stack_capacity = frame.graphics.scissor_stack.capacity;
|
||||
frame.graphics = .empty;
|
||||
frame.graphics.screen_size = screen_size;
|
||||
try frame.graphics.commands.ensureTotalCapacity(arena, graphics_commands_capacity);
|
||||
try frame.graphics.scissor_stack.ensureTotalCapacity(arena, scissor_stack_capacity);
|
||||
frame.pushScissor(.init(0, 0, sapp.widthf(), sapp.heightf()));
|
||||
|
||||
frame.time_ns = time_passed;
|
||||
frame.dt_ns = time_passed - self.last_frame_at;
|
||||
frame.hide_cursor = false;
|
||||
|
||||
try self.game.tick(&self.frame);
|
||||
|
||||
frame.input.keyboard.pressed = .initEmpty();
|
||||
frame.input.keyboard.released = .initEmpty();
|
||||
frame.input.mouse_button.pressed = .initEmpty();
|
||||
frame.input.mouse_button.released = .initEmpty();
|
||||
}
|
||||
|
||||
try self.game.debug();
|
||||
|
||||
self.input.pressed_keys = .initEmpty();
|
||||
self.input.released_keys = .initEmpty();
|
||||
}
|
||||
|
||||
fn timePassed(self: *Engine) Nanoseconds {
|
||||
const now = std.time.Instant.now() catch @panic("Instant.now() unsupported");
|
||||
return now.since(self.started_at);
|
||||
}
|
||||
|
||||
fn event(self: *Engine, e: Event) !void {
|
||||
const input = &self.input;
|
||||
|
||||
switch (e) {
|
||||
.key_pressed => |opts| {
|
||||
if (!opts.repeat) {
|
||||
input.pressed_keys_at.put(opts.code, self.timePassed());
|
||||
input.pressed_keys.insert(opts.code);
|
||||
input.down_keys.insert(opts.code);
|
||||
}
|
||||
},
|
||||
.key_released => |key_code| {
|
||||
input.down_keys.remove(key_code);
|
||||
input.released_keys.insert(key_code);
|
||||
input.pressed_keys_at.remove(key_code);
|
||||
},
|
||||
.mouse_leave => {
|
||||
var iter = input.down_keys.iterator();
|
||||
while (iter.next()) |key_code| {
|
||||
input.released_keys.insert(key_code);
|
||||
}
|
||||
input.down_keys = .initEmpty();
|
||||
input.pressed_keys_at = .init(.{});
|
||||
|
||||
input.mouse = .empty;
|
||||
},
|
||||
.mouse_enter => |pos| {
|
||||
input.mouse.position = pos;
|
||||
},
|
||||
.mouse_move => |pos| {
|
||||
input.mouse.position = pos;
|
||||
},
|
||||
.mouse_pressed => |opts| {
|
||||
input.mouse.position = opts.position;
|
||||
input.mouse.buttons.insert(opts.button);
|
||||
},
|
||||
.mouse_released => |opts| {
|
||||
input.mouse.position = opts.position;
|
||||
input.mouse.buttons.remove(opts.button);
|
||||
},
|
||||
else => {}
|
||||
if (self.canvas_size) |canvas_size| {
|
||||
const transform = ScreenScalar.init(
|
||||
screen_size,
|
||||
canvas_size
|
||||
);
|
||||
transform.apply(
|
||||
screen_size,
|
||||
&self.frame,
|
||||
rgb(0, 0, 0)
|
||||
);
|
||||
}
|
||||
|
||||
sapp.showMouse(!self.frame.hide_cursor);
|
||||
|
||||
// Canvas size modification must always be applied a frame later.
|
||||
// So that mouse coordinate transformations are consistent.
|
||||
self.canvas_size = self.frame.graphics.canvas_size;
|
||||
|
||||
{
|
||||
Gfx.beginFrame();
|
||||
defer Gfx.endFrame(frame.graphics.clear_color);
|
||||
|
||||
Gfx.drawCommands(frame.graphics.commands.items);
|
||||
|
||||
if (frame.show_debug) {
|
||||
try self.game.debug();
|
||||
try showDebugWindow(&self.frame);
|
||||
}
|
||||
}
|
||||
|
||||
for (frame.audio.commands.items) |command| {
|
||||
try Audio.mixer.commands.push(command);
|
||||
}
|
||||
|
||||
if (revert_mouse_position) |pos| {
|
||||
self.frame.input.mouse_position = pos;
|
||||
}
|
||||
}
|
||||
|
||||
fn showDebugWindow(frame: *Frame) !void {
|
||||
if (!imgui.beginWindow(.{
|
||||
.name = "Engine",
|
||||
.pos = Vec2.init(240, 20),
|
||||
.size = Vec2.init(200, 200),
|
||||
})) {
|
||||
return;
|
||||
}
|
||||
defer imgui.endWindow();
|
||||
|
||||
imgui.textFmt("Draw commands: {}\n", .{
|
||||
frame.graphics.commands.items.len,
|
||||
});
|
||||
imgui.textFmt("Audio instances: {}/{}\n", .{
|
||||
Audio.mixer.instances.items.len,
|
||||
Audio.mixer.instances.capacity
|
||||
});
|
||||
}
|
||||
|
||||
fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
|
||||
@ -256,11 +264,10 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
|
||||
defer zone.deinit();
|
||||
|
||||
const e = e_ptr.*;
|
||||
const MouseButton = Input.Mouse.Button;
|
||||
|
||||
if (imgui.handleEvent(e)) {
|
||||
if (self.mouse_inside) {
|
||||
try self.event(Event{
|
||||
Input.processEvent(&self.frame, .{
|
||||
.mouse_leave = {}
|
||||
});
|
||||
}
|
||||
@ -268,13 +275,11 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
blk: switch (e.type) {
|
||||
switch (e.type) {
|
||||
.MOUSE_DOWN => {
|
||||
const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk;
|
||||
|
||||
try self.event(Event{
|
||||
Input.processEvent(&self.frame, .{
|
||||
.mouse_pressed = .{
|
||||
.button = mouse_button,
|
||||
.button = @enumFromInt(@intFromEnum(e.mouse_button)),
|
||||
.position = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
}
|
||||
});
|
||||
@ -282,11 +287,9 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
|
||||
return true;
|
||||
},
|
||||
.MOUSE_UP => {
|
||||
const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk;
|
||||
|
||||
try self.event(Event{
|
||||
Input.processEvent(&self.frame, .{
|
||||
.mouse_released = .{
|
||||
.button = mouse_button,
|
||||
.button = @enumFromInt(@intFromEnum(e.mouse_button)),
|
||||
.position = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
}
|
||||
});
|
||||
@ -295,11 +298,11 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
|
||||
},
|
||||
.MOUSE_MOVE => {
|
||||
if (!self.mouse_inside) {
|
||||
try self.event(Event{
|
||||
Input.processEvent(&self.frame, .{
|
||||
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
});
|
||||
} else {
|
||||
try self.event(Event{
|
||||
Input.processEvent(&self.frame, .{
|
||||
.mouse_move = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
});
|
||||
}
|
||||
@ -309,7 +312,7 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
|
||||
},
|
||||
.MOUSE_ENTER => {
|
||||
if (!self.mouse_inside) {
|
||||
try self.event(Event{
|
||||
Input.processEvent(&self.frame, .{
|
||||
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
});
|
||||
}
|
||||
@ -319,12 +322,12 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
|
||||
},
|
||||
.RESIZED => {
|
||||
if (self.mouse_inside) {
|
||||
try self.event(Event{
|
||||
Input.processEvent(&self.frame, .{
|
||||
.mouse_leave = {}
|
||||
});
|
||||
}
|
||||
|
||||
try self.event(Event{
|
||||
Input.processEvent(&self.frame, .{
|
||||
.window_resize = {}
|
||||
});
|
||||
|
||||
@ -333,7 +336,7 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
|
||||
},
|
||||
.MOUSE_LEAVE => {
|
||||
if (self.mouse_inside) {
|
||||
try self.event(Event{
|
||||
Input.processEvent(&self.frame, .{
|
||||
.mouse_leave = {}
|
||||
});
|
||||
}
|
||||
@ -342,14 +345,14 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
|
||||
return true;
|
||||
},
|
||||
.MOUSE_SCROLL => {
|
||||
try self.event(Event{
|
||||
Input.processEvent(&self.frame, .{
|
||||
.mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y)
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.KEY_DOWN => {
|
||||
try self.event(Event{
|
||||
Input.processEvent(&self.frame, .{
|
||||
.key_pressed = .{
|
||||
.code = @enumFromInt(@intFromEnum(e.key_code)),
|
||||
.repeat = e.key_repeat
|
||||
@ -359,14 +362,14 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
|
||||
return true;
|
||||
},
|
||||
.KEY_UP => {
|
||||
try self.event(Event{
|
||||
Input.processEvent(&self.frame, .{
|
||||
.key_released = @enumFromInt(@intFromEnum(e.key_code))
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.CHAR => {
|
||||
try self.event(Event{
|
||||
Input.processEvent(&self.frame, .{
|
||||
.char = @intCast(e.char_code)
|
||||
});
|
||||
|
||||
|
||||
@ -2,19 +2,21 @@ const Gfx = @import("./graphics.zig");
|
||||
|
||||
const Math = @import("./math.zig");
|
||||
const Vec2 = Math.Vec2;
|
||||
const Vec4 = Math.Vec4;
|
||||
const rgb = Math.rgb;
|
||||
|
||||
const Frame = @import("./frame.zig");
|
||||
|
||||
const ScreenScalar = @This();
|
||||
|
||||
// TODO: Implement a fractional pixel perfect scalar
|
||||
// Based on this video: https://www.youtube.com/watch?v=d6tp43wZqps
|
||||
// And this blog: https://colececil.dev/blog/2017/scaling-pixel-art-without-destroying-it/
|
||||
|
||||
window_size: Vec2,
|
||||
translation: Vec2,
|
||||
scale: f32,
|
||||
|
||||
pub fn push(window_size: Vec2, canvas_size: Vec2) ScreenScalar {
|
||||
pub fn init(window_size: Vec2, canvas_size: Vec2) ScreenScalar {
|
||||
// TODO: Render to a lower resolution instead of scaling.
|
||||
// To avoid pixel bleeding in spritesheet artifacts
|
||||
const scale = @floor(@min(
|
||||
@ -26,42 +28,53 @@ pub fn push(window_size: Vec2, canvas_size: Vec2) ScreenScalar {
|
||||
translation.x = @round(translation.x);
|
||||
translation.y = @round(translation.y);
|
||||
|
||||
Gfx.pushTransform(translation, scale);
|
||||
|
||||
return ScreenScalar{
|
||||
.window_size = window_size,
|
||||
.translation = translation,
|
||||
.scale = scale
|
||||
};
|
||||
}
|
||||
|
||||
pub fn pop(self: ScreenScalar) void {
|
||||
Gfx.popTransform();
|
||||
pub fn apply(self: ScreenScalar, window_size: Vec2, frame: *Frame, color: Vec4) void {
|
||||
const scale = self.scale;
|
||||
const translation = self.translation;
|
||||
|
||||
const bg_color = rgb(0, 0, 0);
|
||||
const filler_size = self.translation;
|
||||
frame.prependGraphicsCommand(.{
|
||||
.push_transformation = .{
|
||||
.translation = translation,
|
||||
.scale = .init(scale, scale)
|
||||
}
|
||||
});
|
||||
frame.popTransform();
|
||||
|
||||
Gfx.drawRectangle(
|
||||
.init(0, 0),
|
||||
.init(self.window_size.x, filler_size.y),
|
||||
bg_color
|
||||
);
|
||||
frame.drawRectangle(.{
|
||||
.rect = .{
|
||||
.pos = .init(0, 0),
|
||||
.size = .init(window_size.x, translation.y),
|
||||
},
|
||||
.color = color
|
||||
});
|
||||
|
||||
Gfx.drawRectangle(
|
||||
.init(0, self.window_size.y - filler_size.y),
|
||||
.init(self.window_size.x, filler_size.y),
|
||||
bg_color
|
||||
);
|
||||
frame.drawRectangle(.{
|
||||
.rect = .{
|
||||
.pos = .init(0, window_size.y - translation.y),
|
||||
.size = .init(window_size.x, translation.y),
|
||||
},
|
||||
.color = color
|
||||
});
|
||||
|
||||
Gfx.drawRectangle(
|
||||
.init(0, 0),
|
||||
.init(filler_size.x, self.window_size.y),
|
||||
bg_color
|
||||
);
|
||||
frame.drawRectangle(.{
|
||||
.rect = .{
|
||||
.pos = .init(0, 0),
|
||||
.size = .init(translation.x, window_size.y),
|
||||
},
|
||||
.color = color
|
||||
});
|
||||
|
||||
Gfx.drawRectangle(
|
||||
.init(self.window_size.x - filler_size.x, 0),
|
||||
.init(filler_size.x, self.window_size.y),
|
||||
bg_color
|
||||
);
|
||||
frame.drawRectangle(.{
|
||||
.rect = .{
|
||||
.pos = .init(window_size.x - translation.x, 0),
|
||||
.size = .init(translation.x, window_size.y),
|
||||
},
|
||||
.color = color
|
||||
});
|
||||
}
|
||||
|
||||
47
src/game.zig
47
src/game.zig
@ -15,15 +15,14 @@ const Game = @This();
|
||||
|
||||
gpa: Allocator,
|
||||
assets: *Assets,
|
||||
canvas_size: Vec2,
|
||||
|
||||
player: Vec2,
|
||||
|
||||
pub fn init(gpa: Allocator, assets: *Assets) !Game {
|
||||
pub fn init(gpa: Allocator, seed: u64, assets: *Assets) !Game {
|
||||
_ = seed; // autofix
|
||||
return Game{
|
||||
.gpa = gpa,
|
||||
.assets = assets,
|
||||
.canvas_size = Vec2.init(100, 100),
|
||||
.player = .init(50, 50)
|
||||
};
|
||||
}
|
||||
@ -32,41 +31,59 @@ pub fn deinit(self: *Game) void {
|
||||
_ = self; // autofix
|
||||
}
|
||||
|
||||
pub fn tick(self: *Game, frame: Engine.Frame) !void {
|
||||
pub fn tick(self: *Game, frame: *Engine.Frame) !void {
|
||||
const dt = frame.deltaTime();
|
||||
|
||||
const canvas_size = Vec2.init(100, 100);
|
||||
frame.graphics.canvas_size = canvas_size;
|
||||
|
||||
if (frame.isKeyPressed(.F3)) {
|
||||
frame.show_debug = !frame.show_debug;
|
||||
}
|
||||
|
||||
var dir = Vec2.init(0, 0);
|
||||
if (frame.input.isKeyDown(.W)) {
|
||||
if (frame.isKeyDown(.W)) {
|
||||
dir.y -= 1;
|
||||
}
|
||||
if (frame.input.isKeyDown(.S)) {
|
||||
if (frame.isKeyDown(.S)) {
|
||||
dir.y += 1;
|
||||
}
|
||||
if (frame.input.isKeyDown(.A)) {
|
||||
if (frame.isKeyDown(.A)) {
|
||||
dir.x -= 1;
|
||||
}
|
||||
if (frame.input.isKeyDown(.D)) {
|
||||
if (frame.isKeyDown(.D)) {
|
||||
dir.x += 1;
|
||||
}
|
||||
dir = dir.normalized();
|
||||
|
||||
if (dir.x != 0 or dir.y != 0) {
|
||||
Audio.play(.{
|
||||
.id = self.assets.wood01
|
||||
frame.playAudio(.{
|
||||
.id = self.assets.wood01,
|
||||
.volume = 0.1
|
||||
});
|
||||
}
|
||||
|
||||
self.player = self.player.add(dir.multiplyScalar(50 * frame.dt));
|
||||
self.player = self.player.add(dir.multiplyScalar(50 * dt));
|
||||
|
||||
const regular_font = self.assets.font_id.get(.regular);
|
||||
|
||||
Gfx.drawRectangle(.init(0, 0), self.canvas_size, rgb(20, 20, 20));
|
||||
frame.drawRectangle(.{
|
||||
.rect = .{ .pos = .init(0, 0), .size = canvas_size },
|
||||
.color = rgb(20, 20, 20)
|
||||
});
|
||||
const size = Vec2.init(20, 20);
|
||||
Gfx.drawRectangle(self.player.sub(size.divideScalar(2)), size, rgb(200, 20, 20));
|
||||
frame.drawRectangle(.{
|
||||
.rect = .{
|
||||
.pos = self.player.sub(size.divideScalar(2)),
|
||||
.size = size
|
||||
},
|
||||
.color = rgb(200, 20, 20)
|
||||
});
|
||||
if (dir.x != 0 or dir.y != 0) {
|
||||
Gfx.drawRectanglOutline(self.player.sub(size.divideScalar(2)), size, rgb(20, 200, 20), 3);
|
||||
frame.drawRectanglOutline(self.player.sub(size.divideScalar(2)), size, rgb(20, 200, 20), 3);
|
||||
}
|
||||
|
||||
Gfx.drawText(self.player, "Player", .{
|
||||
frame.drawText(self.player, "Player", .{
|
||||
.font = regular_font,
|
||||
.size = 10
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user