diff --git a/src/engine/Control.zig b/src/engine/Control.zig index 2744c5e..fc097a6 100644 --- a/src/engine/Control.zig +++ b/src/engine/Control.zig @@ -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(¤t_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); } diff --git a/src/engine/Debug.zig b/src/engine/Debug.zig new file mode 100644 index 0000000..79edefe --- /dev/null +++ b/src/engine/Debug.zig @@ -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; + } + } +}; + diff --git a/src/engine/Entity.zig b/src/engine/Entity.zig index 80f8b6d..a39c69b 100644 --- a/src/engine/Entity.zig +++ b/src/engine/Entity.zig @@ -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); } diff --git a/src/engine/Layer.zig b/src/engine/Layer.zig deleted file mode 100644 index 497aa77..0000000 --- a/src/engine/Layer.zig +++ /dev/null @@ -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); diff --git a/src/engine/MouseListener.zig b/src/engine/MouseListener.zig new file mode 100644 index 0000000..98b5ca1 --- /dev/null +++ b/src/engine/MouseListener.zig @@ -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); +} diff --git a/src/engine/Scene.zig b/src/engine/Scene.zig index 60a0f19..40f0e3d 100644 --- a/src/engine/Scene.zig +++ b/src/engine/Scene.zig @@ -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); - } -}; diff --git a/src/engine/Sprite.zig b/src/engine/Sprite.zig index d4f26ff..1a1842f 100644 --- a/src/engine/Sprite.zig +++ b/src/engine/Sprite.zig @@ -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 }); } diff --git a/src/engine/Tag.zig b/src/engine/Tag.zig deleted file mode 100644 index 68fa822..0000000 --- a/src/engine/Tag.zig +++ /dev/null @@ -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; -} diff --git a/src/engine/geometry/Recti.zig b/src/engine/geometry/Recti.zig index cf7e14a..1322066 100644 --- a/src/engine/geometry/Recti.zig +++ b/src/engine/geometry/Recti.zig @@ -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()); } diff --git a/src/engine/geometry/Vec2f.zig b/src/engine/geometry/Vec2f.zig index 3a675eb..09699f4 100644 --- a/src/engine/geometry/Vec2f.zig +++ b/src/engine/geometry/Vec2f.zig @@ -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); diff --git a/src/engine/geometry/Vec2i.zig b/src/engine/geometry/Vec2i.zig index 6c34a0e..ace83dc 100644 --- a/src/engine/geometry/Vec2i.zig +++ b/src/engine/geometry/Vec2i.zig @@ -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 { diff --git a/src/engine/root.zig b/src/engine/root.zig index aa9ee12..30262d8 100644 --- a/src/engine/root.zig +++ b/src/engine/root.zig @@ -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; +// } diff --git a/src/hadean/Camera.zig b/src/hadean/Camera.zig index 95a23b7..5bb1d16 100644 --- a/src/hadean/Camera.zig +++ b/src/hadean/Camera.zig @@ -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), + }; + } + } } diff --git a/src/hadean/Terrain.zig b/src/hadean/Terrain.zig index 9d62281..9b6bd53 100644 --- a/src/hadean/Terrain.zig +++ b/src/hadean/Terrain.zig @@ -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 => {}, } } diff --git a/src/hadean/main.zig b/src/hadean/main.zig index bc28a5e..dfb0357 100644 --- a/src/hadean/main.zig +++ b/src/hadean/main.zig @@ -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(); } diff --git a/src/hadean/scenes.zig b/src/hadean/scenes.zig index bce07a5..c35a7d7 100644 --- a/src/hadean/scenes.zig +++ b/src/hadean/scenes.zig @@ -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; }