mouse stuff, mostly

stable
Ivory 2025-01-09 11:02:29 -05:00
parent 07fb08b35e
commit 7a54564597
16 changed files with 751 additions and 718 deletions

View File

@ -1,11 +1,11 @@
const std = @import("std");
const Recti = @import("geometry/Recti.zig");
const Matrix4f = @import("geometry/Matrix4f.zig");
const Layer = @import("Layer.zig");
const Color = @import("Color.zig");
const Scene = @import("Scene.zig");
const Vec2i = @import("geometry/Vec2i.zig");
const c = @import("c");
const engine = @import("engine");
const Vec2i = engine.geometry.Vec2i;
const Scene = engine.Scene;
const ArenaAllocator = std.heap.ArenaAllocator;
const Allocator = std.mem.Allocator;
const MouseEvent = engine.MouseListener.Event;
const SDL_GetError = c.SDL_GetError;
const SDL_Log = c.SDL_Log;
@ -34,13 +34,17 @@ pub var sdl_data: SDL_Data = .{
.window = undefined,
};
var hovered: bool = true;
var window_width: i32 = 0;
var window_height: i32 = 0;
pub var mouse_pos: ?Vec2i = null;
var gpa = std.heap.GeneralPurposeAllocator(.{});
var allocator = gpa.allocator();
pub var window_width: i32 = 800;
pub var window_height: i32 = 600;
const SceneLoadFunction = *const fn (*Scene) Allocator.Error!void;
var scene_arena = ArenaAllocator.init(std.heap.page_allocator);
var current_scene: ?Scene = null;
var next_scene: ?Scene = null;
var next_scene_fn: ?SceneLoadFunction = null;
var running: bool = false;
const LoaderFn = *const fn (*c.SDL_Window, *c.SDL_Renderer) void;
@ -54,7 +58,7 @@ pub fn setup() !void {
}
var temp_window: ?*c.SDL_Window = null;
var temp_renderer: ?*c.SDL_Renderer = null;
if (!c.SDL_CreateWindowAndRenderer("SDL3", 800, 600, 0, &temp_window, &temp_renderer)) {
if (!c.SDL_CreateWindowAndRenderer("SDL3", window_width, window_height, 0, &temp_window, &temp_renderer)) {
std.debug.panic("SDL failed to initialize window or renderer with error: {s}", .{ SDL_GetError() });
}
sdl_data.window = temp_window.?;
@ -76,34 +80,48 @@ pub fn destroy() void {
SDL_Quit();
}
pub fn set_scene(scene: Scene) void {
next_scene = scene;
pub fn set_scene(scene_fn: SceneLoadFunction) !void {
next_scene_fn = scene_fn;
if(!running) try switch_scene();
}
fn switch_scene() !void {
if (next_scene_fn == null) return error.NoNextScene;
if (current_scene != null) {
current_scene.?.destroy(scene_arena.allocator());
// bool successful, always true when free all...
_ = scene_arena.reset(.free_all);
current_scene = null;
}
const create_scene = next_scene_fn.?;
current_scene = try Scene.init(scene_arena.allocator());
try create_scene(&current_scene.?);
try current_scene.?.start();
next_scene_fn = null;
// TODO give the scene the events it needs to contextualize it
}
pub fn run() !void {
if (!sdl_data.ready) return error.WindowOrRendererNotInitialized;
if (next_scene == null) return error.NoScene;
if (current_scene == null) return error.NoScene;
running = true;
var event: SDL_Event = undefined;
while (running) {
_ = c.SDL_SetRenderDrawColor(sdl_data.renderer, 10, 10, 10, 255);
_ = c.SDL_RenderClear(sdl_data.renderer);
if(next_scene_fn != null) try switch_scene();
if (next_scene != null) {
if (current_scene != null) current_scene.?.destroy();
current_scene = next_scene;
next_scene = null;
// start our scene
try current_scene.?.start();
// and send it the appropriate resize.
current_scene.?.resize(window_width, window_height);
if (hovered) current_scene.?.mouse_enter();
if (mouse_pos) |pos| current_scene.?.mouse_move(pos.x, pos.y);
}
engine.Debug.Display.reset();
current_scene.?.draw();
engine.Debug.Display.render(sdl_data.renderer);
_ = c.SDL_RenderPresent(sdl_data.renderer);
while(c.SDL_PollEvent(&event)) {
@ -111,17 +129,18 @@ pub fn run() !void {
c.SDL_EVENT_QUIT => running = false, // ignore close requested, unless doing autosaves.
c.SDL_EVENT_WINDOW_CLOSE_REQUESTED => {},
c.SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED => set_window_size(event.window.data1, event.window.data2),
c.SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED => window_resized(event.window.data1, event.window.data2),
c.SDL_EVENT_WINDOW_RESIZED => {}, // ignore resized bc it doesnt happen on startup.
c.SDL_EVENT_WINDOW_SAFE_AREA_CHANGED => {}, // same with safe area, might have
c.SDL_EVENT_WINDOW_MOVED => {},
c.SDL_EVENT_WINDOW_MOUSE_ENTER => mouse_enter(),
c.SDL_EVENT_WINDOW_MOUSE_LEAVE => mouse_leave(),
c.SDL_EVENT_WINDOW_MOUSE_ENTER => {},
c.SDL_EVENT_WINDOW_MOUSE_LEAVE => mouse_leave(), // only triggers when no buttons are held.
c.SDL_EVENT_MOUSE_MOTION => mouse_move(event.motion.x, event.motion.y),
c.SDL_EVENT_MOUSE_BUTTON_DOWN => mouse_button_down(event.button.button),
c.SDL_EVENT_MOUSE_BUTTON_UP => mouse_button_up(event.button.button),
c.SDL_EVENT_MOUSE_BUTTON_DOWN => mouse_button_down(event.button.button, event.motion.x, event.motion.y),
c.SDL_EVENT_MOUSE_BUTTON_UP => mouse_button_up(event.button.button, event.motion.x, event.motion.y),
c.SDL_EVENT_MOUSE_WHEEL => {},
c.SDL_EVENT_KEY_DOWN => {},
c.SDL_EVENT_KEY_UP => {},
@ -139,38 +158,34 @@ pub fn run() !void {
}
}
fn set_window_size(width: i32, height: i32) void {
fn window_resized(width: i32, height: i32) void {
window_width = width;
window_height = height;
std.debug.print("[Engine:set_window_size] {d} x {d}\n", .{ width, height });
if (current_scene != null) current_scene.?.resize(width, height);
// if (current_scene != null) current_scene.?.resize(width, height);
}
fn mouse_leave() void {
if (current_scene == null) return;
current_scene.?.mouse_leave();
}
fn mouse_move(fx: f32, fy: f32) void {
const x: i32 = @intFromFloat(std.math.round(fx));
const y: i32 = @intFromFloat(std.math.round(fy));
mouse_pos = Vec2i { .x = x, .y = y };
if (current_scene != null and hovered) {
current_scene.?.mouse_move(x, y);
}
}
fn mouse_enter() void {
hovered = true;
}
fn mouse_leave() void {
hovered = false;
mouse_pos = null;
}
fn mouse_button_down(button: i32) void {
if (current_scene == null) return;
current_scene.?.mouse_down(button);
current_scene.?.mouse_move(x, y);
}
fn mouse_button_up(button: i32) void {
fn mouse_button_down(button: c_int, fx: f32, fy: f32) void {
const x: i32 = @intFromFloat(std.math.round(fx));
const y: i32 = @intFromFloat(std.math.round(fy));
if (current_scene == null) return;
current_scene.?.mouse_up(button);
current_scene.?.mouse_down(button, x, y);
}
fn mouse_button_up(button: c_int, fx: f32, fy: f32) void {
const x: i32 = @intFromFloat(std.math.round(fx));
const y: i32 = @intFromFloat(std.math.round(fy));
if (current_scene == null) return;
current_scene.?.mouse_up(button, x, y);
}

View File

@ -0,0 +1,47 @@
const std = @import("std");
const c = @import("c");
const engine = @import("engine");
const Control = engine.Control;
pub const camera = true;
pub const mouse = true;
// later make this expandable in some way using an allocator.
pub const Display = struct {
var debug_buffer: [10][200]u8 = undefined;
pub fn reset() void {
inline for (0..debug_buffer.len) |idx| {
debug_buffer[idx][0] = 0;
}
}
fn empty_line() !usize {
for (0..debug_buffer.len) |idx| {
if (debug_buffer[idx][0] == 0) {
return idx;
}
}
return error.DebugBufferOverflow;
}
pub fn add_line(comptime fmt: []const u8, args: anytype) void {
const line_idx: usize = empty_line()
catch @panic("debug buffer overflow (too many lines)");
_ = std.fmt.bufPrintZ(&debug_buffer[line_idx], fmt, args)
catch @panic("debug buffer overflow (line too long)");
}
pub fn render(renderer: *c.SDL_Renderer) void {
_ = c.SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
const x = 10;
var y: f32 = 10;
const line_height = 10;
inline for (0..debug_buffer.len) |idx| {
_ = c.SDL_RenderDebugText(renderer, x, y, &debug_buffer[idx]);
y = y + line_height;
}
}
};

View File

@ -7,191 +7,40 @@ const Scene = engine.Scene;
const Recti = engine.geometry.Recti;
const Layer = engine.Layer;
const EntityOptions = struct {
pub const Options = struct {
tag: Tag = Tag.NONE,
layer: Layer,
};
const VTable = struct {
_start: *const fn(*anyopaque, *Scene) void,
_update: *const fn(*anyopaque, f32) void,
_draw: *const fn(*const anyopaque, layer: Layer) void,
_destroy: *const fn(*const anyopaque) void,
};
ptr: *anyopaque,
tag: Tag,
layer: Layer,
name: []const u8,
vtable: VTable,
_name: *const fn(*const anyopaque) []const u8,
_destroy: *const fn(*const anyopaque) void,
_update: *const fn(*anyopaque, f32) void,
_draw: *const fn(*const anyopaque) void,
_draw_opacity: *const fn(*const anyopaque) void,
_start: *const fn(*anyopaque, *Scene) void,
_resize: *const fn(*anyopaque, i32, i32) void,
// mouse functions
_mouse_ready: *const fn(*const anyopaque) bool,
_mouse_enabled: *const fn(*const anyopaque) bool,
_mouse_area: *const fn(*const anyopaque) Recti,
_mouse_layer: *const fn(*const anyopaque) Layer,
_mouse_move: *const fn(*anyopaque, i32, i32) void,
_mouse_in: *const fn(*anyopaque) void,
_mouse_out: *const fn(*anyopaque) void,
_mouse_down: *const fn(*anyopaque, i32) bool,
_mouse_up: *const fn(*anyopaque, i32) bool,
const ArrayTruthState = enum {
TRUE,
MIXED,
FALSE,
EMPTY,
};
fn bool_array_state(arr: []const bool) ArrayTruthState {
var any = false;
var all = true;
for (arr) |item| {
any = any or item;
all = all and item;
}
if (any and all) return .TRUE;
if (any and !all) return .MIXED;
if (!any and all) return .EMPTY;
if (!any and !all) return .FALSE;
return .FALSE;
}
pub fn init(ptr: anytype, options: EntityOptions) Entity {
// std.debug.print("[Entity:init] creating entity {s} with tag \"{s}\"\n", .{
// @typeName(@typeInfo(@TypeOf(ptr)).Pointer.child),
// options.tag.type
// });
pub fn from(ptr: anytype, options: Options) Entity {
const MutablePointer = @TypeOf(ptr);
// @compileLog("Compiling Entity type for " ++ @typeName(MutablePointer));
const type_info = @typeInfo(MutablePointer);
// ensure the passed type is a mutable pointer
if(type_info != .Pointer) @compileError("Entity implementation must be a pointer");
// to a single thing
if(type_info.Pointer.size != .One) @compileError("Entity pointer must be a single item pointer");
// where that thing is a struct or opaque
switch (@typeInfo(type_info.Pointer.child)) {
.Struct => {},
else => @compileError("Entity pointer must point to a struct or opaque type"),
}
// where that thing is a struct
if (@typeInfo(type_info.Pointer.child) != .Struct) @compileError("Entity pointer must point to a struct");
comptime var input_ready = ArrayTruthState.FALSE;
// comptime {
input_ready = comptime bool_array_state(&[_]bool {
@hasDecl(type_info.Pointer.child, "mouse_enabled"),
@hasDecl(type_info.Pointer.child, "mouse_area"),
@hasDecl(type_info.Pointer.child, "mouse_layer"),
@hasDecl(type_info.Pointer.child, "mouse_in"),
@hasDecl(type_info.Pointer.child, "mouse_out"),
@hasDecl(type_info.Pointer.child, "mouse_move"),
});
// switch(input_ready) {
// .TRUE => {},
// .FALSE => {},
// .EMPTY => {},
// .MIXED => @compileError(@typeName(type_info.Pointer.child) ++ " does not define all methods required for input focus"),
// }
// }
// @compileLog(MutablePointer);
// @compileLog(input_ready);
// also take the const pointer type, as we will use both mutable an immutable pointers.
// some entities mutate and some do not
const ConstPointer = *const @typeInfo(MutablePointer).Pointer.child;
// @compileLog("const pointer type: " ++ @typeName(ConstPointer));
const ConstPointer = *const type_info.Pointer.child;
const gen = struct {
// mouse functions
pub fn _mouse_ready(_: *const anyopaque) bool {
return input_ready != ArrayTruthState.FALSE;
}
pub fn _mouse_enabled(pointer: *const anyopaque) bool {
const self: ConstPointer = @ptrCast(@alignCast(pointer));
if (@hasDecl(type_info.Pointer.child, "mouse_enabled")) {
type_info.Pointer.child.mouse_enabled(self);
} else {
return true;
}
}
pub fn _mouse_layer(pointer: *const anyopaque) Layer {
const self: ConstPointer = @ptrCast(@alignCast(pointer));
if (@hasDecl(type_info.Pointer.child, "mouse_layer")) {
return type_info.Pointer.child.mouse_layer(self);
} else {
return Layer.HIDDEN;
}
}
pub fn _mouse_area(pointer: *const anyopaque) Recti {
const self: ConstPointer = @ptrCast(@alignCast(pointer));
if (@hasDecl(type_info.Pointer.child, "mouse_area")) {
return type_info.Pointer.child.mouse_area(self);
} else {
return Recti.from_xywh(-10, -10, 0, 0);
}
}
pub fn _mouse_move(pointer: *anyopaque, x: i32, y: i32) void {
const self: MutablePointer = @ptrCast(@alignCast(pointer));
if (@hasDecl(type_info.Pointer.child, "mouse_move")) {
type_info.Pointer.child.mouse_move(self, x, y);
}
}
pub fn _mouse_in(pointer: *anyopaque) void {
const self: MutablePointer = @ptrCast(@alignCast(pointer));
if (@hasDecl(type_info.Pointer.child, "mouse_in")) {
type_info.Pointer.child.mouse_in(self);
}
}
pub fn _mouse_out(pointer: *anyopaque) void {
const self: MutablePointer = @ptrCast(@alignCast(pointer));
if (@hasDecl(type_info.Pointer.child, "mouse_out")) {
type_info.Pointer.child.mouse_out(self);
}
}
pub fn _mouse_down(pointer: *anyopaque, button: i32) bool {
const self: MutablePointer = @ptrCast(@alignCast(pointer));
if (@hasDecl(type_info.Pointer.child, "mouse_down")) {
return type_info.Pointer.child.mouse_down(self, button);
} else return false;
}
pub fn _mouse_up(pointer: *anyopaque, button: i32) bool {
const self: MutablePointer = @ptrCast(@alignCast(pointer));
if (@hasDecl(type_info.Pointer.child, "mouse_up")) {
return type_info.Pointer.child.mouse_up(self, button);
} else return false;
}
// general methods
pub fn _name(_: *const anyopaque) []const u8 {
// const self: ConstPointer = @ptrCast(@alignCast(pointer));
return @typeName(type_info.Pointer.child);
}
pub fn _destroy(pointer: *const anyopaque) void {
const self: ConstPointer = @ptrCast(@alignCast(pointer));
type_info.Pointer.child.destroy(self);
}
pub fn _start(pointer: *anyopaque, scene: *Scene) void {
const self: MutablePointer = @ptrCast(@alignCast(pointer));
if(@hasDecl(type_info.Pointer.child, "start")) {
const return_type = @typeInfo(@typeInfo(@TypeOf(type_info.Pointer.child.start)).Fn.return_type.?);
// @compileLog(return_type);
switch (return_type) {
.Void => type_info.Pointer.child.start(self, scene),
.ErrorUnion => {
type_info.Pointer.child.start(self, scene) catch @panic("fuck");
},
else => {}
}
}
}
pub fn _resize(pointer: *anyopaque, width: i32, height: i32) void {
const self: MutablePointer = @ptrCast(@alignCast(pointer));
if(@hasDecl(type_info.Pointer.child, "resize")) {
type_info.Pointer.child.resize(self, width, height);
type_info.Pointer.child.start(self, scene);
}
}
pub fn _update(pointer: *anyopaque, dt: f32) void {
@ -200,16 +49,16 @@ pub fn init(ptr: anytype, options: EntityOptions) Entity {
type_info.Pointer.child.update(self, dt);
}
}
pub fn _draw(pointer: *const anyopaque) void {
pub fn _draw(pointer: *const anyopaque, layer: Layer) void {
const self: ConstPointer = @ptrCast(@alignCast(pointer));
if(@hasDecl(type_info.Pointer.child, "draw")) {
type_info.Pointer.child.draw(self);
type_info.Pointer.child.draw(self, layer);
}
}
pub fn _draw_opacity(pointer: *const anyopaque) void {
pub fn _destroy(pointer: *const anyopaque) void {
const self: ConstPointer = @ptrCast(@alignCast(pointer));
if(@hasDecl(type_info.Pointer.child, "draw_opacity")) {
type_info.Pointer.child.draw_opacity(self);
if(@hasDecl(type_info.Pointer.child, "destroy")) {
type_info.Pointer.child.destroy(self);
}
}
};
@ -217,101 +66,28 @@ pub fn init(ptr: anytype, options: EntityOptions) Entity {
return Entity {
.ptr = ptr,
.tag = options.tag,
.layer = options.layer,
._name = gen._name,
._destroy = gen._destroy,
._start = gen._start,
._resize = gen._resize,
._update = gen._update,
._draw = gen._draw,
._draw_opacity = gen._draw_opacity,
// mouse stuff
._mouse_ready = gen._mouse_ready,
._mouse_enabled = gen._mouse_enabled,
._mouse_layer = gen._mouse_layer,
._mouse_area = gen._mouse_area,
._mouse_move = gen._mouse_move,
._mouse_in = gen._mouse_in,
._mouse_out = gen._mouse_out,
._mouse_down = gen._mouse_down,
._mouse_up = gen._mouse_up,
.name = @typeName(type_info.Pointer.child),
.vtable = VTable {
._destroy = gen._destroy,
._start = gen._start,
._update = gen._update,
._draw = gen._draw,
}
};
}
pub fn start(self: *const Entity, scene: *Scene) void {
self.vtable._start(self.ptr, scene);
}
pub fn update(self: *const Entity, dt: f32) void {
self._update(self.ptr, dt);
self.vtable._update(self.ptr, dt);
}
pub fn draw(self: *const Entity) void {
self._draw(self.ptr);
}
pub fn draw_opacity(self: *const Entity) void {
self._draw_opacity(self.ptr);
pub fn draw(self: *const Entity, layer: Layer) void {
self.vtable._draw(self.ptr, layer);
}
pub fn destroy(self: *const Entity) void {
self._destroy(self.ptr);
}
pub fn start(self: *const Entity, scene: *Scene) void {
self._start(self.ptr, scene);
}
pub fn resize(self: *const Entity, width: i32, height: i32) void {
self._resize(self.ptr, width, height);
}
pub fn name(self: *const Entity) []const u8 {
return self._name(self.ptr);
}
// -=- mouse related items
pub fn mouse_ready(self: *const Entity) bool {
return self._mouse_ready(self.ptr);
}
pub fn mouse_enabled(self: *const Entity) bool {
return self._mouse_enabled(self.ptr);
}
pub fn mouse_layer(self: *const Entity) Layer {
return self._mouse_layer(self.ptr);
}
pub fn mouse_area(self: *const Entity) Recti {
return self._mouse_area(self.ptr);
}
pub fn mouse_move(self: *const Entity, x: i32, y: i32) void {
return self._mouse_move(self.ptr, x, y);
}
pub fn mouse_in(self: *const Entity) void {
self._mouse_in(self.ptr);
}
pub fn mouse_out(self: *const Entity) void {
self._mouse_out(self.ptr);
}
pub fn mouse_down(self: *const Entity, button: i32) bool {
return self._mouse_down(self.ptr, button);
}
pub fn mouse_up(self: *const Entity, button: i32) bool {
return self._mouse_up(self.ptr, button);
}
pub fn allocate_array(comptime T: type, size: usize, default: T) ![]T {
const ptr = try std.heap.page_allocator.alloc(T, size);
for (0..size) |idx| {
ptr[idx] = default;
}
return ptr;
}
pub fn free_array(ptr: anytype) void {
std.heap.page_allocator.free(ptr);
self.vtable._destroy(self.ptr);
}

View File

@ -1,15 +0,0 @@
const Layer = @This();
z_index: i32,
fn layer(z_index: i32) Layer {
return .{
.z_index = z_index
};
}
pub const HIDDEN = layer(-1);
pub const FLOOR = layer(0);
pub const ENTITIES = layer(1);
pub const CAMERA = layer(2);
pub const SELECTION = layer(3);

View File

@ -0,0 +1,135 @@
const MouseListener = @This();
const engine = @import("engine");
const std = @import("std");
const c = @import("c");
const Scene = engine.Scene;
const Recti = engine.geometry.Recti;
const Layer = engine.Layer;
const Vec2i = engine.geometry.Vec2i;
pub const Button = enum(c_int) {
Left = @intCast(c.SDL_BUTTON_LEFT),
Right = @intCast(c.SDL_BUTTON_RIGHT),
Middle = @intCast(c.SDL_BUTTON_MIDDLE),
pub fn count() comptime_int {
return engine.enum_to_array(Button).len;
}
pub fn to_idx(button: Button) usize {
return switch(button) {
.Left => 0,
.Right => 1,
.Middle => 2,
};
}
pub fn from_idx(idx: usize) Button {
return switch(idx) {
0 => .Left,
1 => .Right,
2 => .Middle,
else => std.debug.panic("Cannot convert {d} to Button", .{ idx }),
};
}
};
pub const Properties = struct {
area: Recti,
layer: Layer,
button: Button,
click_through: bool,
};
pub const Event = union(enum) {
MouseIn: struct { pos: Vec2i },
MouseOut: void,
MouseMove: struct { pos: Vec2i },
MouseDown: struct { button: Button, pos: Vec2i },
MouseUp: struct { button: Button, pos: Vec2i },
};
const VTable = struct {
_mouse_properties: *const fn(*const anyopaque) Properties,
_mouse_event: *const fn(*anyopaque, Event) void,
_name: *const fn() []const u8,
};
ptr: *anyopaque,
vtable: VTable,
pub fn is_capable(T: type) bool {
const type_info = @typeInfo(T);
if (type_info != .Pointer) return false;
if (type_info.Pointer.size != .One) return false;
if (type_info.Pointer.is_const) return false;
if (type_info.Pointer.is_allowzero) return false;
if (!@hasDecl(type_info.Pointer.child, "mouse_properties")) return false;
if (!@hasDecl(type_info.Pointer.child, "mouse_event")) return false;
return true;
}
pub fn from(impl: anytype) MouseListener {
if (!comptime is_capable(@TypeOf(impl)))
@compileError(@typeName(@TypeOf(impl)) ++ " could not be made a MouseListener");
const StructType = @typeInfo(@TypeOf(impl)).Pointer.child;
const MutablePointer = *StructType;
const ConstPointer = *const StructType;
const gen = struct {
pub fn _mouse_properties(ptr: *const anyopaque) Properties {
const self: ConstPointer = @ptrCast(@alignCast(ptr));
return StructType.mouse_properties(self);
}
pub fn _mouse_event(ptr: *anyopaque, event: Event) void {
const self: MutablePointer = @ptrCast(@alignCast(ptr));
return StructType.mouse_event(self, event);
}
pub fn _name() []const u8 {
return @typeName(StructType);
}
};
return MouseListener {
.ptr = impl,
.vtable = VTable {
._mouse_properties = gen._mouse_properties,
._mouse_event = gen._mouse_event,
._name = gen._name,
}
};
}
pub fn mouse_event(self: MouseListener, event: Event) void {
self.vtable._mouse_event(self.ptr, event);
}
pub fn mouse_properties(self: MouseListener) Properties {
return self.vtable._mouse_properties(self.ptr);
}
pub fn name(self: MouseListener) []const u8 {
return self.vtable._name();
}
pub fn mouse_in(self: MouseListener, pos: Vec2i) void {
self.vtable._mouse_event(self.ptr, Event {
.MouseIn = .{
.pos = pos
}
});
}
pub fn mouse_out(self: MouseListener) void {
self.vtable._mouse_event(self.ptr, Event {
.MouseOut = undefined,
});
}
pub fn asc(_: void, a: MouseListener, b: MouseListener) bool {
return @intFromEnum(a.mouse_properties().layer) < @intFromEnum(b.mouse_properties().layer);
}

View File

@ -1,44 +1,79 @@
const Scene = @This();
const Recti = @import("geometry/Recti.zig");
const Layer = @import("Layer.zig");
const Color = @import("Color.zig");
const engine = @import("engine");
const Entity = engine.Entity;
const Vec2i = @import("geometry/Vec2i.zig");
const Tag = @import("Tag.zig");
const std = @import("std");
const c = @import("c");
const ArrayList = std.ArrayList;
const AutoHashMap = std.AutoHashMap;
const ArrayListUnmanaged = std.ArrayListUnmanaged;
const AutoHashMapUnmanaged = std.AutoHashMapUnmanaged;
const Entity = engine.Entity;
const Tag = engine.Tag;
const Vec2i = engine.geometry.Vec2i;
const MouseListener = engine.MouseListener;
const Allocator = std.mem.Allocator;
const Color = engine.Color;
// cache and list bothe store entities. treat entities as fat pointers.
// Pointers into an arraylist WILL become invalid.
mouse_ready_entities: ArrayList(Entity),
entities: ArrayList(Entity),
entities_to_add: ArrayList(Entity),
tagged_entities: AutoHashMap(Tag.ID, Entity),
mouse_inside: bool = false,
current_hover_item: ?Entity = null,
mouse_listeners: ArrayListUnmanaged(MouseListener) = undefined,
entities: ArrayListUnmanaged(Entity) = undefined,
entities_to_add: ArrayListUnmanaged(Entity) = undefined,
tagged_entities: AutoHashMapUnmanaged(engine.Tag, Entity) = undefined,
started: bool = false,
arena: *std.heap.ArenaAllocator,
active_mouse_listeners: [MouseListener.Button.count()] ?MouseListener = blk: {
const len = MouseListener.Button.count();
var arr: [len]?MouseListener = undefined;
for (0..len) |idx| {
arr[idx] = null;
}
break :blk arr;
},
mouse_listener_locks: [MouseListener.Button.count()]bool = blk: {
var arr: [MouseListener.Button.count()]bool = undefined;
for(0..arr.len) |idx| arr[idx] = false;
break :blk arr;
},
mouse_pos: ?Vec2i = null,
started: bool = false,
pub fn create() Scene {
const self = Scene {
.entities = ArrayList(Entity).init(std.heap.page_allocator),
.entities_to_add = ArrayList(Entity).init(std.heap.page_allocator),
.mouse_ready_entities = ArrayList(Entity).init(std.heap.page_allocator),
.tagged_entities = AutoHashMap(Tag.ID, Entity).init(std.heap.page_allocator),
pub fn init(allocator: Allocator) !Scene {
// const self: = allocator.create(Scene);
const arena = try allocator.create(std.heap.ArenaAllocator);
arena.* = std.heap.ArenaAllocator.init(allocator);
// var self = Scene { .arena = std.heap.ArenaAllocator.init(std.heap.page_allocator) };
// const allocator = self.arena.allocator();
//
// self.entities = ArrayList(Entity).init(allocator);
// self.entities_to_add = ArrayList(Entity).init(allocator);
// self.mouse_listeners = ArrayList(MouseListener).init(allocator);
// self.tagged_entities = AutoHashMap(Tag, Entity).init(allocator);
//
return Scene {
.arena = arena,
.entities = try ArrayListUnmanaged(Entity).initCapacity(arena.allocator(), 10),
.entities_to_add = try ArrayListUnmanaged(Entity).initCapacity(arena.allocator(), 10),
.mouse_listeners = try ArrayListUnmanaged(MouseListener).initCapacity(arena.allocator(), 10),
.tagged_entities = std.AutoHashMapUnmanaged(Tag, Entity) {},
};
return self;
}
pub fn destroy(self: *Scene) void {
// the allocator parameter needs to be the same allocator
// that was given on init. this is because the arenaallocator
// itself is stored in it. everything else is stored in the
// arena, which is getting cleared in one fell swoop.
pub fn destroy(self: *Scene, allocator: Allocator) void {
// this should effectively be a no-op as most entities
// should be allocating with the scene's arena allocator
// but in case theyre not, we make sure to call destroy
// on them.
for (self.entities.items) |entity| {
entity.destroy();
}
self.entities.deinit();
// first we clear everything in the arena (entity data and
// managed caches of entities / Mouse Listeners / etc)
self.arena.deinit();
// then we free the arena state from the parent allocator.
allocator.destroy(self.arena);
}
pub fn start(self: *Scene) !void {
@ -48,92 +83,54 @@ pub fn start(self: *Scene) !void {
}
pub fn get(self: *const Scene, tag: Tag, comptime T: type) *T {
if (tag.id == Tag.NONE.id) @panic("Cannot find by Tag.NONE");
const tag_id = tag.id;
const entity: Entity = self.tagged_entities.get(tag_id) orelse @panic("Could not find entity");
if (tag == Tag.NONE) @panic("Cannot find by Tag.NONE");
const entity: Entity = self.tagged_entities.get(tag) orelse @panic("Could not find entity");
const opaque_ptr = entity.ptr;
const real_ptr: *T = @ptrCast(@alignCast(opaque_ptr));
return real_ptr;
}
pub fn add(self: *Scene, instance_ptr: anytype) !void {
const instance_ptr_type = @TypeOf(instance_ptr);
const instance_ptr_type_info = @typeInfo(instance_ptr_type);
if (instance_ptr_type_info != .Pointer)
@compileError(
"Cannot add to scene type " ++
@typeName(instance_ptr_type) ++
". Must be a mutable pointer\n"
);
if (instance_ptr_type_info.Pointer.size != .One)
@compileError("Pointer size must be One");
if (instance_ptr_type_info.Pointer.is_const)
@compileError("Pointer must be mutable");
const instance_type = instance_ptr_type_info.Pointer.child;
if (!@hasDecl(instance_type, "entity"))
@compileError("Pointer must be to a struct with fn entity() Entity");
// again, entities are fat pointers. they are okay to be copied
const entity: Entity = instance_ptr.entity();
pub fn add(self: *Scene, entity: Entity) !void {
std.debug.print("[Scene:add] Queueing {s}\n", .{
entity.name()
entity.name
});
try self.entities_to_add.append(entity);
try self.entities_to_add.append(self.arena.allocator(), entity);
}
pub fn register_mouse_listener(self: *Scene, mouse_listener: MouseListener) void {
self.mouse_listeners.append(self.arena.allocator(), mouse_listener) catch @panic("OOM");
std.mem.sort(MouseListener, self.mouse_listeners.items, {}, MouseListener.asc);
}
fn ingest_queued_entities(self: *Scene) !void {
// TODO make this a while loop so entities that add entities add them on the same frame.
var temp_entities: ArrayList(Entity) = try ArrayList(Entity).initCapacity(std.heap.page_allocator, self.entities_to_add.items.len);
var temp_entities = try ArrayListUnmanaged(Entity)
.initCapacity(self.arena.allocator(), self.entities_to_add.items.len);
for(self.entities_to_add.items) |entity| {
std.debug.print("[Scene:ingest_queued_entities] Adding {s}\n", .{
entity.name()
entity.name
});
try temp_entities.append(entity);
try self.entities.append(entity);
try temp_entities.append(self.arena.allocator(), entity);
try self.entities.append(self.arena.allocator(), entity);
if (entity.tag.id != Tag.NONE.id) {
try self.tagged_entities.put(entity.tag.id, entity);
}
if (entity.mouse_ready()) {
try self.mouse_ready_entities.append(entity);
if (entity.tag != Tag.NONE) {
try self.tagged_entities.put(self.arena.allocator(), entity.tag, entity);
}
}
self.entities_to_add.clearAndFree();
std.mem.sort(Entity, self.mouse_ready_entities.items, {}, entity_layer_less_than);
std.mem.sort(Entity, self.entities.items, {}, entity_draw_layer_less_than);
// std.debug.print("[Scene.ingest_queued_entities] Sorted:\n", .{});
// for(self.entities.items) |*entity| {
// std.debug.print("[Scene:ingest_queued_entities] {d}: {s}\n", .{ entity.layer.z_index, entity.name() });
// }
self.entities_to_add.clearAndFree(self.arena.allocator());
std.debug.print("[Scene:ingest_queued_entities] Starting {} entities\n", .{ temp_entities.items.len });
for(temp_entities.items) |entity| {
entity.start(self);
if (entity.tag.id == Tag.NONE.id) {
std.debug.print("[Scene:ingest_queued_entities] - {s}\n", .{ entity.name() });
if (entity.tag == Tag.NONE) {
std.debug.print("[Scene:ingest_queued_entities] - {s}\n", .{ entity.name });
} else {
std.debug.print("[Scene:ingest_queued_entities] - {s} [{s}]\n", .{ entity.name(), entity.tag.type });
std.debug.print("[Scene:ingest_queued_entities] - {s} [{s}]\n", .{ entity.name, @tagName(entity.tag) });
}
}
}
fn entity_draw_layer_less_than(_: void, lhs: Entity, rhs: Entity) bool {
return lhs.layer.z_index < rhs.layer.z_index;
}
fn entity_layer_less_than(_: void, lhs: Entity, rhs: Entity) bool {
return lhs.mouse_layer().z_index < rhs.mouse_layer().z_index;
}
pub fn update(self: *Scene, dt: f32) !void {
@ -146,113 +143,144 @@ pub fn update(self: *Scene, dt: f32) !void {
entity.update(dt);
}
// this whole operation is wildly stupid and could be amde more efficient.
if (self.mouse_pos) |mouse| {
var top_hovered: ?Entity = null;
for (self.mouse_ready_entities.items) |item| {
if (item.mouse_ready() and item.mouse_area().contains(mouse)) {
top_hovered = item;
continue;
}
}
if (top_hovered != null) {
if (self.current_hover_item != null) {
// if we're changing hover target from something to something
if (self.current_hover_item.?.ptr != top_hovered.?.ptr) {
self.current_hover_item.?.mouse_out();
self.current_hover_item = top_hovered;
self.current_hover_item.?.mouse_in();
}
} else {
// we are hovering something, and werent before.
self.current_hover_item = top_hovered.?;
self.current_hover_item.?.mouse_in();
}
} else if (self.current_hover_item != null) {
// here we were hovering something, but now are not.
self.current_hover_item.?.mouse_out();
self.current_hover_item = null;
}
} else if (self.current_hover_item != null) {
self.current_hover_item.?.mouse_out();
self.current_hover_item = null;
}
self.update_hover_context();
}
pub fn draw(self: *Scene) void {
for (self.entities.items) |entity| {
entity.draw();
for (engine.enum_to_array(engine.Layer)) |layer| {
for (self.entities.items) |entity| {
entity.draw(layer);
}
}
for (self.entities.items) |entity| {
entity.draw_opacity();
}
for (self.mouse_ready_entities.items) |item| {
if (self.current_hover_item != null and item.ptr == self.current_hover_item.?.ptr) {
// item.mouse_area().draw(item.mouse_layer(), Color.LIME.with_opacity(0.3));
// for (self.mouse_listeners.items) |item| {
// if (self.current_hover_item != null and item.ptr == self.current_hover_item.?.ptr) {
// item.mouse_properties().area.draw(Color.LIME.with_opacity(0.3));
// } else {
// item.mouse_properties().area.draw(Color.WHITE.with_opacity(0.3));
// }
// }
if (engine.Debug.mouse) {
if (self.mouse_pos) |mouse| {
engine.Debug.Display.add_line("Mouse Pos: ({}, {})", .{ mouse.x, mouse.y });
} else {
// item.mouse_area().draw(item.mouse_layer(), Color.WHITE.with_opacity(0.3));
engine.Debug.Display.add_line("Mouse Pos: Unknown", .{});
}
engine.Debug.Display.add_line("Mouse Listeners: {}", .{ self.active_listener_count() });
for(&self.active_mouse_listeners, 0..) |*listener, button_idx| {
if(listener.* == null) continue;
const button = MouseListener.Button.from_idx(button_idx);
engine.Debug.Display.add_line(" {s}: {s}{s}", .{
@tagName(button),
listener.*.?.name(),
if (self.mouse_listener_locks[button_idx]) " (locked)" else ""
});
}
}
}
pub fn resize(self: *Scene, width: i32, height: i32) void {
for (self.entities.items) |entity| {
entity.resize(width, height);
fn active_listener_count(self: *Scene) u32 {
var count: u32 = 0;
for(&self.active_mouse_listeners) |*listener| {
if(listener.* == null) continue;
count = count + 1;
}
return count;
}
fn get_hovered_mouse_listener_for_button(self: *Scene, button: MouseListener.Button) ?MouseListener {
if (self.mouse_pos == null) return null;
for (self.mouse_listeners.items) |listener| {
const props = listener.mouse_properties();
if (!props.area.contains(self.mouse_pos.?)) continue;
if (button == props.button) return listener;
if (props.click_through) continue;
return null;
}
return null;
}
fn update_hover_context(self: *Scene) void {
for (0..self.mouse_listener_locks.len) |button_idx| {
// if this mouse listener is locked then forget updating it.
if (self.mouse_listener_locks[button_idx]) continue;
// otherwise, grab the button enum and current / previous hovered
const button = MouseListener.Button.from_idx(button_idx);
const current_hovered = self.get_hovered_mouse_listener_for_button(button);
const previous_hovered = self.active_mouse_listeners[button_idx];
// if they are equal, we dont care.
if (current_hovered == null and previous_hovered == null) continue;
if (current_hovered != null and previous_hovered != null
and current_hovered.?.ptr == previous_hovered.?.ptr) continue;
if (previous_hovered) |previous| {
previous.mouse_out();
}
if (current_hovered) |current| {
// mouse pos MUST exist here, because get_hovered_listener_for_button
// will return null if we have no position.
current.mouse_in(self.mouse_pos.?);
}
// we dont unwrap the optional here to propogate a null value
self.active_mouse_listeners[button_idx] = current_hovered;
}
}
pub fn mouse_move(self: *Scene, x: i32, y: i32) void {
// std.debug.print("[Scene:mouse_move] {} {} {}\n", .{ self, x, y });
self.mouse_pos = Vec2i { .x = x, .y = y };
self.mouse_inside = true;
for (self.mouse_ready_entities.items) |item| {
item.mouse_move(x, y);
self.mouse_pos = Vec2i.create(x, y);
// this may need to be made to go to EVERYONE,
// but for now its just going to go to the active
// listeners.
for (self.active_mouse_listeners) |mouse_listener| {
if(mouse_listener) |listener| {
listener.mouse_event(MouseListener.Event {
.MouseMove = .{
.pos = Vec2i.create(x, y),
}
});
}
}
}
pub fn mouse_enter(self: *Scene) void {
self.mouse_inside = true;
pub fn mouse_down(self: *Scene, sdl_button: c_int, x: i32, y: i32) void {
if (self.mouse_pos == null) return;
const button: MouseListener.Button = @enumFromInt(sdl_button);
const index = button.to_idx();
self.mouse_listener_locks[index] = true;
if (self.active_mouse_listeners[index]) |listener| {
listener.mouse_event(MouseListener.Event {
.MouseDown = .{
.button = @enumFromInt(sdl_button),
.pos = Vec2i.create(x, y),
}
});
}
}
pub fn mouse_up(self: *Scene, sdl_button: c_int, x: i32, y: i32) void {
if (self.mouse_pos == null) return;
const button: MouseListener.Button = @enumFromInt(sdl_button);
const index = button.to_idx();
self.mouse_listener_locks[index] = false;
if (self.active_mouse_listeners[index]) |listener| {
listener.mouse_event(MouseListener.Event {
.MouseUp = .{
.button = @enumFromInt(sdl_button),
.pos = Vec2i.create(x, y),
}
});
}
}
pub fn mouse_leave(self: *Scene) void {
self.mouse_inside = false;
self.mouse_pos = null;
}
pub fn mouse_down(self: *Scene, button: i32) void {
// std.debug.print("[Scene:mouse_down] {?}\n", .{ self.mouse_pos });
if (self.mouse_pos == null) return;
for (self.mouse_ready_entities.items) |entity| {
if (entity.mouse_area().contains(self.mouse_pos.?)) {
if (entity.mouse_down(button)) return;
}
}
}
pub fn mouse_up(self: *Scene, button: i32) void {
if (self.mouse_pos == null) return;
for (self.mouse_ready_entities.items) |entity| {
if (entity.mouse_area().contains(self.mouse_pos.?)) {
if (entity.mouse_up(button)) return;
}
}
}
// --- helpfer functions for entities themselves
pub const EntityPool = struct {
// an entity can choose to allocate itself however it pleases, but these
// are good default functions that just throw things on the heap.
pub fn allocate(instance: anytype) !*@TypeOf(instance) {
const ptr = try std.heap.page_allocator.create(@TypeOf(instance));
ptr.* = instance;
return ptr;
}
pub fn deallocate(instance: anytype) void {
std.heap.page_allocator.destroy(instance);
}
};

View File

@ -1,15 +1,16 @@
const Sprite = @This();
const Rectf = @import("geometry/Rectf.zig");
const Recti = @import("geometry/Recti.zig");
const Vec2f = @import("geometry/Vec2f.zig");
const Color = @import("Color.zig");
const Texture = @import("Texture.zig");
const Layer = @import("Layer.zig");
const engine = @import("engine");
const std = @import("std");
const c = @import("c");
const Recti = engine.geometry.Recti;
const Rectf = engine.geometry.Rectf;
const Vec2f = engine.geometry.Vec2f;
const Layer = engine.Layer;
const Color = engine.Color;
const Texture = engine.Texture;
texture_area: Recti = undefined,
texture: *const Texture = undefined,
@ -27,10 +28,7 @@ pub fn get_src_uv(self: *const Sprite) Rectf {
));
}
pub fn draw(self: *const Sprite, screen_pos: Recti, layer: Layer, color: Color) void {
// _ = screen_pos;
_ = layer;
// _ = color;
pub fn draw(self: *const Sprite, screen_pos: Recti, color: Color) void {
_ = c.SDL_SetRenderDrawColor(engine.Control.sdl_data.renderer, color.u.r, color.u.g, color.u.b, color.u.a);
_ = c.SDL_SetTextureColorModFloat(self.texture.handle, color.f.r, color.f.g, color.f.b);
_ = c.SDL_RenderTexture(
@ -39,5 +37,4 @@ pub fn draw(self: *const Sprite, screen_pos: Recti, layer: Layer, color: Color)
&self.texture_area.to_sdl(),
&screen_pos.to_sdl()
);
// std.debug.print("drew: {}\n", .{ drew });
}

View File

@ -1,47 +0,0 @@
const Tag = @This();
const std = @import("std");
pub const ID = *const anyopaque;
id: ID,
type: []const u8,
fn create_id_for_type(comptime T: type) ID {
// create a unique struct for each type this is called with.
const unique_struct = struct {
// this simply gives the struct a size, so the pointer is not
// optimized into oblivion.
var x: u8 = 0;
// capture the type so zig knows not to make this
// struct the same for each call. if the type isnt captured
// zig will create one struct in memory for each unique call.
// ie: the function will be unique, but this struct wont be.
comptime {
_ = T;
}
};
// @compileLog(T);
// @compileLog(&unique_struct.x);
// return the pointer to the only data within the struct,
// again so the compiler doesnt optimize this into non-existence.
return &unique_struct.x;
}
pub const NONE = Tag {
.type = "None",
.id = create_id_for_type(struct {}),
};
pub fn create(comptime t: type) Tag {
// @compileLog(@typeName(t));
return Tag {
.type = @typeName(t),
.id = create_id_for_type(t),
};
}
pub inline fn equals(self: *const Tag, other: Tag) bool {
return self.id == other.id;
}

View File

@ -68,15 +68,12 @@ pub fn scalef(self: *const Recti, scale: Vec2f) Rectf {
);
}
pub fn draw(self: *const Recti, layer: Layer, color: Color) void {
self.draw_filled(layer, color.with_opacity(0.2));
self.draw_outline(layer, color);
pub fn draw(self: *const Recti, color: Color) void {
self.draw_filled(color.with_opacity(0.2));
self.draw_outline(color);
}
pub fn draw_filled(self: *const Recti, layer: Layer, color: Color) void {
// _ = self;
_ = layer;
// _ = color;
pub fn draw_filled(self: *const Recti, color: Color) void {
_ = c.SDL_SetRenderDrawColorFloat(engine.Control.sdl_data.renderer, color.f.r, color.f.g, color.f.b, color.f.a);
_ = c.SDL_RenderFillRect(engine.Control.sdl_data.renderer, &self.to_sdl());
}
@ -90,10 +87,7 @@ pub fn to_sdl(self: *const Recti) c.SDL_FRect {
};
}
pub fn draw_outline(self: *const Recti, layer: Layer, color: Color) void {
_ = self;
_ = layer;
_ = color;
// _ = c.SDL_SetRenderDrawColorFloat(engine.Control.sdl_data.renderer, color.f.r, color.f.g, color.f.b, color.f.a);
// _ = c.SDL_RenderRect(engine.Control.sdl_data.renderer, &self.to_sdl());
pub fn draw_outline(self: *const Recti, color: Color) void {
_ = c.SDL_SetRenderDrawColorFloat(engine.Control.sdl_data.renderer, color.f.r, color.f.g, color.f.b, color.f.a);
_ = c.SDL_RenderRect(engine.Control.sdl_data.renderer, &self.to_sdl());
}

View File

@ -1,7 +1,10 @@
const Vec2f = @This();
const Vec2i = @import("Vec2i.zig");
const Layer = @import("../Layer.zig");
const Color = @import("../Color.zig");
const engine = @import("engine");
const c = @import("c");
const Vec2i = engine.geometry.Vec2i;
const Layer = engine.Layer;
const Color = engine.Color;
const std = @import("std");
x: f32,
@ -21,11 +24,8 @@ pub fn to_vec2i(self: *const Vec2f) Vec2i {
};
}
pub fn draw(self: *const Vec2f, radius: f32, layer: Layer, color: Color) void {
_ = self;
_ = radius;
_ = layer;
_ = color;
pub fn draw(self: *const Vec2f, comptime radius: f32, color: Color) void {
self.to_vec2i().draw(radius, color);
}
pub const ZERO = create(0, 0);

View File

@ -1,10 +1,12 @@
const Vec2i = @This();
const Recti = @import("Recti.zig");
const Vec2f = @import("Vec2f.zig");
const Layer = @import("../Layer.zig");
const Color = @import("../Color.zig");
const std = @import("std");
const engine = @import("engine");
const c = @import("c");
const Recti = engine.geometry.Recti;
const Vec2f = engine.geometry.Vec2f;
const Layer = engine.Layer;
const Color = engine.Color;
x: i32,
y: i32,
@ -34,11 +36,57 @@ pub fn add(self: Vec2i, other: Vec2i) Vec2i {
};
}
pub fn draw(self: *const Vec2i, radius: f32, layer: Layer, color: Color) void {
_ = self;
_ = radius;
_ = layer;
_ = color;
fn round_up_to_multiple_of_8(n: i32) i32 {
// fkn magic. -8 is all 1s, last 3 bits 0 so its a mask.
return (n + (8 - 1)) & -8;
}
// TODO: comptime the entire array and just render with one offset call to render points.
// effectively memoize shit with comptime!!!
pub fn draw(self: *const Vec2i, comptime radius: i32, color: Color) void {
const cx = self.x;
const cy = self.y;
const fradius: f32 = @floatFromInt(radius);
const arr_size: usize = comptime @intCast(
round_up_to_multiple_of_8(
@intFromFloat(std.math.ceil(
fradius * 8.0 * std.math.sqrt1_2
))
)
);
var points: [arr_size]c.SDL_FPoint = undefined;
var draw_count: usize = 0;
const diameter = (radius * 2);
var x: i32 = (radius - 1);
var y: i32 = 0;
var tx: i32 = 1;
var ty: i32 = 1;
var err: i32 = (tx - diameter);
while (x >= y) {
points[draw_count + 0] = c.SDL_FPoint { .x = @floatFromInt(cx + x), .y = @floatFromInt(cy - y) };
points[draw_count + 1] = c.SDL_FPoint { .x = @floatFromInt(cx + x), .y = @floatFromInt(cy + y) };
points[draw_count + 2] = c.SDL_FPoint { .x = @floatFromInt(cx - x), .y = @floatFromInt(cy - y) };
points[draw_count + 3] = c.SDL_FPoint { .x = @floatFromInt(cx - x), .y = @floatFromInt(cy + y) };
points[draw_count + 4] = c.SDL_FPoint { .x = @floatFromInt(cx + y), .y = @floatFromInt(cy - x) };
points[draw_count + 5] = c.SDL_FPoint { .x = @floatFromInt(cx + y), .y = @floatFromInt(cy + x) };
points[draw_count + 6] = c.SDL_FPoint { .x = @floatFromInt(cx - y), .y = @floatFromInt(cy - x) };
points[draw_count + 7] = c.SDL_FPoint { .x = @floatFromInt(cx - y), .y = @floatFromInt(cy + x) };
draw_count = draw_count + 8;
if (err <= 0) {
y = y + 1;
err = err + ty;
ty = ty + 2;
}
if (err > 0) {
x = x - 1;
tx = tx + 2;
err = err + (tx - diameter);
}
}
_ = c.SDL_SetRenderDrawColor(engine.Control.sdl_data.renderer, color.u.r, color.u.g, color.u.b, color.u.a);
_ = c.SDL_RenderPoints(engine.Control.sdl_data.renderer, &points, @intCast(draw_count));
}
pub fn to_vec2f(self: *const Vec2i) Vec2f {

View File

@ -1,17 +1,45 @@
pub const Control = @import("Control.zig");
pub const Scene = @import("Scene.zig");
pub const Tag = @import("Tag.zig");
pub const Layer = @import("Layer.zig");
pub const Interface = @import("Interface.zig");
pub const Entity = @import("Entity.zig");
pub const Color = @import("Color.zig");
pub const Noise = @import("Noise.zig");
pub const Sprite = @import("Sprite.zig");
pub const Texture = @import("Texture.zig");
pub const geometry = @import("geometry/root.zig");
pub const Debug = @import("Debug.zig");
pub const Entity = @import("Entity.zig");
pub const MouseListener = @import("MouseListener.zig");
pub const Debug = struct {
pub const camera = true;
pub const Layer = enum(u8) {
HIDDEN = 0,
FLOOR = 1,
ENTITIES = 2,
CAMERA = 3,
SELECTION = 4,
OVERLAY = 128,
};
pub const Tag = enum(u8) {
NONE = 0,
CAMERA = 1,
TERRAIN = 2,
};
// this will convert an enum type to an array of all enum members
pub fn enum_to_array(comptime e: anytype) [@typeInfo(e).Enum.fields.len]e {
return comptime blk: {
const len = @typeInfo(e).Enum.fields.len;
var arr: [len]e = undefined;
for(0..len) |idx| {
const val: e = @enumFromInt(@typeInfo(e).Enum.fields[idx].value);
// @compileLog(val);
arr[idx] = val;
}
break :blk arr;
};
}
// pub fn enum_length(comptime e: anytype) comptime_int {
// return comptime enum_to_array(e).len;
// }

View File

@ -12,20 +12,26 @@ const Vec2i = engine.geometry.Vec2i;
const Vec2f = engine.geometry.Vec2f;
const Recti = engine.geometry.Recti;
const Rectf = engine.geometry.Rectf;
const MouseListener = engine.MouseListener;
const Allocator = std.mem.Allocator;
pub const TAG = engine.Tag.create(Camera);
const DragData = struct {
mouse_start_pos: Vec2i,
focus_start_pos: Vec2f,
mouse_current_pos: Vec2i,
};
// focus: Vec2i = Vec2i.EAST.scale(100),
focus: Vec2f = Vec2f.ZERO,
tile_size: i32 = 16,
window_size_offset_x: i32 = 0,
window_size_offset_y: i32 = 0,
// window_size_offset_x: i32 = 0,
// window_size_offset_y: i32 = 0,
drag_pos: ?Vec2i = null,
drag_focus: ?Vec2f = null,
// drag_start_mouse_pos: ?Vec2i = null,
// focus_when_drag_started: ?Vec2f = null,
drag_data: ?DragData = null,
mouse_in: bool = false,
fn draw_line_vec2i(a: Vec2i, b: Vec2i, layer: Layer, color: Color) void {
_ = layer;
fn draw_line_vec2i(a: Vec2i, b: Vec2i, color: Color) void {
_ = c.SDL_SetRenderDrawColor(engine.Control.sdl_data.renderer, color.u.r, color.u.g, color.u.b, color.u.a);
_ = c.SDL_RenderLine(
engine.Control.sdl_data.renderer,
@ -44,9 +50,10 @@ fn draw_line_vec2f(a: Vec2f, b: Vec2f, layer: Layer, color: Color) void {
@panic("Not Implemented");
}
pub fn create() !*Camera {
const self = Camera {};
return try Scene.EntityPool.allocate(self);
pub fn create(allocator: Allocator) !*Camera {
const self = try allocator.create(Camera);
self.* = Camera {};
return self;
}
pub fn resize(self: *Camera, width: i32, height: i32) void {
@ -55,44 +62,43 @@ pub fn resize(self: *Camera, width: i32, height: i32) void {
}
pub fn destroy(self: *const Camera) void {
Scene.EntityPool.deallocate(self);
_ = self;
}
pub fn entity(self: *Camera) Entity {
return Entity.init(self, .{
.tag = TAG,
.layer = Layer.CAMERA
});
pub fn start(self: *Camera, scene: *Scene) void {
scene.register_mouse_listener(MouseListener.from(self));
}
pub fn update(self: *Camera, _: f32) void {
if (self.drag_pos != null and self.drag_focus != null and engine.Control.mouse_pos != null) {
// new focus = OG focus + difference between drag start and drag now
const drag_start_world = self.screen_to_world_vec2i(self.drag_pos.?);
const drag_current_world = self.screen_to_world_vec2i(engine.Control.mouse_pos.?);
_ = self;
}
self.focus = Vec2f {
.x = self.drag_focus.?.x + (drag_start_world.x - drag_current_world.x),
.y = self.drag_focus.?.y + (drag_start_world.y - drag_current_world.y),
};
pub fn draw(self: *const Camera, layer: Layer) void {
switch (layer) {
.CAMERA => {
if (engine.Debug.camera and self.drag_data != null) {
draw_line_vec2i(self.drag_data.?.mouse_start_pos, self.drag_data.?.mouse_current_pos, Color.WHITE);
self.drag_data.?.mouse_current_pos.draw(4, Color.CYAN);
self.drag_data.?.mouse_start_pos.draw(4, Color.RED);
const start_focus_screen = self.world_to_screen_vec2f(self.drag_data.?.focus_start_pos);
const current_focus_screen = self.world_to_screen_vec2f(self.focus);
draw_line_vec2i(start_focus_screen, current_focus_screen, Color.BLUE);
start_focus_screen.draw(4, Color.ORANGE);
current_focus_screen.draw(4, Color.LIME);
}
},
else => {},
}
}
pub fn draw(self: *const Camera) void {
if (engine.Debug.camera and self.drag_pos != null and self.drag_focus != null) {
if(engine.Control.mouse_pos != null) {
draw_line_vec2i(self.drag_pos.?, engine.Control.mouse_pos.?, Layer.CAMERA, Color.WHITE);
engine.Control.mouse_pos.?.draw(4, Layer.CAMERA, Color.CYAN);
}
self.drag_pos.?.draw(4, Layer.CAMERA, Color.RED);
fn window_size_offset_x() i32 {
return @divFloor(engine.Control.window_width, 2);
}
const start_focus_screen = self.world_to_screen_vec2f(self.drag_focus.?);
const current_focus_screen = self.world_to_screen_vec2f(self.focus);
draw_line_vec2i(start_focus_screen, current_focus_screen, Layer.CAMERA, Color.BLUE);
start_focus_screen.draw(4, Layer.CAMERA, Color.ORANGE);
current_focus_screen.draw(4, Layer.CAMERA, Color.LIME);
}
fn window_size_offset_y() i32 {
return @divFloor(engine.Control.window_height, 2);
}
inline fn world_to_screen_vec2i(self: *const Camera, coords: Vec2i) Vec2i {
@ -101,8 +107,8 @@ inline fn world_to_screen_vec2i(self: *const Camera, coords: Vec2i) Vec2i {
const focus_y_screen: i32 = @intFromFloat(self.focus.y * tile_size_f);
return Vec2i.create(
@as(i32, @intFromFloat(tile_size_f * coords.x)) - focus_x_screen + self.window_size_offset_x,
@as(i32, @intFromFloat(tile_size_f * coords.y)) - focus_y_screen + self.window_size_offset_y
@as(i32, @intFromFloat(tile_size_f * coords.x)) - focus_x_screen + window_size_offset_x(),
@as(i32, @intFromFloat(tile_size_f * coords.y)) - focus_y_screen + window_size_offset_y()
);
}
@ -112,8 +118,8 @@ inline fn world_to_screen_vec2f(self: *const Camera, coords: Vec2f) Vec2i {
const focus_y_screen: i32 = @intFromFloat(self.focus.y * tile_size_f);
return Vec2i.create(
@as(i32, @intFromFloat(tile_size_f * coords.x)) - focus_x_screen + self.window_size_offset_x,
@as(i32, @intFromFloat(tile_size_f * coords.y)) - focus_y_screen + self.window_size_offset_y
@as(i32, @intFromFloat(tile_size_f * coords.x)) - focus_x_screen + window_size_offset_x(),
@as(i32, @intFromFloat(tile_size_f * coords.y)) - focus_y_screen + window_size_offset_y()
);
}
@ -122,8 +128,8 @@ inline fn screen_to_world_vec2i(self: *const Camera, coords: Vec2i) Vec2f {
const focus_x_screen: i32 = @intFromFloat(self.focus.x * tile_size_f);
const focus_y_screen: i32 = @intFromFloat(self.focus.y * tile_size_f);
const pre_x: f32 = @floatFromInt(coords.x + focus_x_screen - self.window_size_offset_x);
const pre_y: f32 = @floatFromInt(coords.y + focus_y_screen - self.window_size_offset_y);
const pre_x: f32 = @floatFromInt(coords.x + focus_x_screen - window_size_offset_x());
const pre_y: f32 = @floatFromInt(coords.y + focus_y_screen - window_size_offset_y());
return Vec2f {
.x = pre_x / tile_size_f,
@ -138,36 +144,51 @@ inline fn world_to_screen_recti(self: *const Camera, box: Rectf) Recti {
);
}
pub fn draw_sprite_i(self: *const Camera, sprite: *const Sprite, world_pos: Recti, layer: Layer, color: Color) void {
pub fn draw_sprite_i(self: *const Camera, sprite: *const Sprite, world_pos: Recti, color: Color) void {
const screen_pos = self.world_to_screen_recti(world_pos.to_rectf());
sprite.draw(screen_pos, layer, color);
sprite.draw(screen_pos, color);
}
pub fn set_focus(self: *Camera, f: Vec2f) void {
self.focus = f;
}
pub fn mouse_layer(_: *const Camera) Layer {
return Layer.CAMERA;
pub fn mouse_properties(_: *const Camera) MouseListener.Properties {
return MouseListener.Properties {
.area = Recti.from_xywh(0, 0, engine.Control.window_width, engine.Control.window_height),
.layer = .CAMERA,
.button = .Middle,
.click_through = true,
};
}
pub fn mouse_area(_: *const Camera) Recti {
return Recti.from_xywh(0, 0, 10_000, 10_000);
}
pub fn mouse_event(self: *Camera, event: MouseListener.Event) void {
switch(event) {
.MouseDown => {
if (event.MouseDown.button != .Middle) return;
self.drag_data = DragData {
.mouse_start_pos = event.MouseDown.pos,
.focus_start_pos = self.focus,
.mouse_current_pos = event.MouseDown.pos,
};
},
.MouseUp => {
if (event.MouseUp.button != .Middle) return;
self.drag_data = null;
},
.MouseIn => self.mouse_in = true,
.MouseOut => self.mouse_in = false,
.MouseMove => {
if (self.drag_data == null) return;
self.drag_data.?.mouse_current_pos = event.MouseMove.pos;
pub fn mouse_down(self: *Camera, button: i32) bool {
if (button == c.SDL_BUTTON_MIDDLE) {
if (engine.Control.mouse_pos == null) return false;
self.drag_pos = engine.Control.mouse_pos.?;
self.drag_focus = self.focus;
return true;
} else return false;
}
const drag_start_world = self.screen_to_world_vec2i(self.drag_data.?.mouse_start_pos);
const drag_current_world = self.screen_to_world_vec2i(event.MouseMove.pos);
pub fn mouse_up(self: *Camera, button: i32) bool {
if (self.drag_pos == null) return false;
if (button != c.SDL_BUTTON_MIDDLE) return false;
self.drag_pos = null;
self.drag_focus = null;
return true;
self.focus = Vec2f {
.x = self.drag_data.?.focus_start_pos.x + (drag_start_world.x - drag_current_world.x),
.y = self.drag_data.?.focus_start_pos.y + (drag_start_world.y - drag_current_world.y),
};
}
}
}

View File

@ -1,24 +1,23 @@
const Terrain = @This();
const engine = @import("engine");
const std = @import("std");
const c = @import("c");
const Scene = engine.Scene;
const Entity = engine.Entity;
const Vec2i = engine.geometry.Vec2i;
const Vec2f = engine.geometry.Vec2f;
const Color = engine.Color;
const Recti = engine.geometry.Recti;
const Noise = engine.Noise;
const Layer = engine.Layer;
const c = @import("c");
const Allocator = std.mem.Allocator;
const std = @import("std");
const allocator = std.heap.page_allocator;
var prng = std.rand.DefaultPrng.init(0);
const Camera = @import("Camera.zig");
const assets = @import("assets.zig");
const TAG = @import("engine").Tag.create(Terrain);
pub const CHUNK_SIZE = 48;
const CHUNK_LENGTH = CHUNK_SIZE * CHUNK_SIZE;
@ -29,23 +28,16 @@ const GROWTH_RATE: f32 = 0.5;
chunks: std.ArrayList(*Chunk),
generator: WorldGenerator,
camera: *Camera = undefined,
scene: ?*Scene = null,
allocator: Allocator,
pub fn entity(self: *Terrain) Entity {
return Entity.init(self, .{
.tag = TAG,
.layer = Layer.HIDDEN
});
}
pub fn start(self: *Terrain, scene: *Scene) !void {
self.camera = scene.get(Camera.TAG, Camera);
// self.camera.set_focus(Vec2f.create(CHUNK_SIZE / 2, CHUNK_SIZE / 2));
pub fn start(self: *Terrain, scene: *Scene) void {
self.camera = scene.get(engine.Tag.CAMERA, Camera);
self.camera.set_focus(Vec2f.create(CHUNK_SIZE / 2, CHUNK_SIZE / 2));
self.scene = scene;
for (self.chunks.items) |chunk| try scene.add(chunk);
for (self.chunks.items) |chunk| scene.add(Entity.from(chunk, .{})) catch unreachable;
}
fn contrast(n: f32, blend_strength: f32) f32 {
@ -62,16 +54,20 @@ fn contrast(n: f32, blend_strength: f32) f32 {
fn gen_chunk(self: *Terrain, chunk_pos: Vec2i) !void {
std.debug.print("[Terrain:gen_chunk] ({}, {})\n", .{ chunk_pos.x, chunk_pos.y });
const chunk = try Chunk.generate(chunk_pos, &self.generator);
const chunk = try Chunk.generate(self.allocator, chunk_pos, &self.generator);
try self.chunks.append(chunk);
if (self.scene != null) try self.scene.?.add(chunk);
// gen chunk sometimes happens before start
if(self.scene) |scene| try scene.add(Entity.from(chunk, .{}));
}
pub fn create(seed: i64) !*Terrain {
var self: *Terrain = try Scene.EntityPool.allocate(Terrain {
.chunks = std.ArrayList(*Chunk).init(std.heap.page_allocator),
pub fn create(allocator: Allocator, seed: i64) !*Terrain {
var self: *Terrain = try allocator.create(Terrain);
self.* = Terrain {
.chunks = std.ArrayList(*Chunk).init(allocator),
.generator = try WorldGenerator.create(seed),
});
.allocator = allocator,
};
try self.gen_chunk(Vec2i.ZERO);
try self.gen_chunk(Vec2i { .x = -1, .y = 0 });
@ -81,7 +77,7 @@ pub fn create(seed: i64) !*Terrain {
pub fn destroy(self: *const Terrain) void {
self.generator.destroy();
self.chunks.deinit();
Scene.EntityPool.deallocate(self);
// Scene.EntityPool.deallocate(self);
}
// fn chance(percent: f32) bool {
@ -142,7 +138,6 @@ pub fn destroy(self: *const Terrain) void {
// }
// }
const Chunk = struct {
pub const Tile = struct {
texture_idx: usize,
@ -165,18 +160,21 @@ const Chunk = struct {
tiles: []Tile = undefined,
chunk_pos: Vec2i,
chunk_pos_world: Vec2i,
camera: *Camera = undefined,
allocator: Allocator,
pub fn generate(chunk_pos: Vec2i, generator: *const WorldGenerator) !*Chunk {
var self = try Scene.EntityPool.allocate(Chunk {
pub fn generate(allocator: Allocator, chunk_pos: Vec2i, generator: *const WorldGenerator) !*Chunk {
var self = try allocator.create(Chunk);
self.* = Chunk {
.chunk_pos = chunk_pos,
.chunk_pos_world = Vec2i {
.x = chunk_pos.x * CHUNK_SIZE,
.y = chunk_pos.y * CHUNK_SIZE,
},
.tiles = try Entity.allocate_array(Tile, CHUNK_LENGTH, Tile.NULL),
});
.tiles = try allocator.alloc(Tile, CHUNK_LENGTH),
// .tiles = try Entity.allocate_array(Tile, CHUNK_LENGTH, Tile.NULL),
.allocator = allocator,
};
self.generate_tiles(generator);
@ -184,8 +182,7 @@ const Chunk = struct {
}
pub fn destroy(self: *const Chunk) void {
Entity.free_array(self.tiles);
Scene.EntityPool.deallocate(self);
self.allocator.free(self.tiles);
}
pub fn entity(self: *Chunk) Entity {
@ -195,7 +192,7 @@ const Chunk = struct {
}
pub fn start(self: *Chunk, scene: *Scene) void {
self.camera = scene.get(Camera.TAG, Camera);
self.camera = scene.get(engine.Tag.CAMERA, Camera);
}
fn generate_tiles(self: *Chunk, generator: *const WorldGenerator) void {
@ -210,21 +207,26 @@ const Chunk = struct {
var rect_test: c.SDL_FRect = .{};
pub fn draw(self: *const Chunk) void {
for(0..CHUNK_LENGTH) |idx| {
const tile = self.tiles[idx];
const x, const y = index_to_xy(@intCast(idx));
const rect = Recti.from_xywh(x, y, 1, 1);
const sprite = assets.terrain[0][tile.texture_idx];
self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, tile.grass_color);
pub fn draw(self: *const Chunk, layer: Layer) void {
switch (layer) {
.FLOOR => {
for(0..CHUNK_LENGTH) |idx| {
const tile = self.tiles[idx];
const x, const y = index_to_xy(@intCast(idx));
const rect = Recti.from_xywh(x, y, 1, 1);
const sprite = assets.terrain[0][tile.texture_idx];
self.camera.draw_sprite_i(sprite, rect, tile.grass_color);
// switch(tile.texture_idx) {
// 0 => self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, Color.WHITE),
// 1 => self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, Color.RED),
// 2 => self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, Color.GREEN),
// 3 => self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, Color.BLUE),
// else => {}
// }
// switch(tile.texture_idx) {
// 0 => self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, Color.WHITE),
// 1 => self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, Color.RED),
// 2 => self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, Color.GREEN),
// 3 => self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, Color.BLUE),
// else => {}
// }
}
},
else => {},
}
}

View File

@ -10,6 +10,6 @@ pub fn main() !void {
try Engine.Control.setup();
defer Engine.Control.destroy();
Engine.Control.set_scene(try scenes.game());
try Engine.Control.set_scene(scenes.game);
try Engine.Control.run();
}

View File

@ -1,19 +1,23 @@
const std = @import("std");
const engine = @import("engine");
const Scene = engine.Scene;
const Camera = @import("Camera.zig");
const Selection = @import("Selection.zig");
const Terrain = @import("Terrain.zig");
const Entity = engine.Entity;
const Tag = engine.Tag;
const Allocator = std.mem.Allocator;
pub fn game() !Scene {
var scene = Scene.create();
// first try is for allocating more memory in entities
// second try is for allocating terrain on the heap...
try scene.add(try Camera.create());
try scene.add(try Terrain.create(123));
try scene.add(try Selection.create());
pub fn game(scene: *Scene) Allocator.Error!void {
const camera = try Camera.create(scene.arena.allocator());
try scene.add(Entity.from(camera, .{ .tag = Tag.CAMERA }));
const terrain = try Terrain.create(scene.arena.allocator(), 123);
try scene.add(Entity.from(terrain, .{ .tag = Tag.TERRAIN }));
// try scene.add(try Selection.create());
// for (0..5) |_| {
// try scene.add(try Pawn.random());
// }
return scene;
}