diff --git a/engine/core/src/engine.zig b/engine/core/src/engine.zig index 1b3e667..1ca11d5 100644 --- a/engine/core/src/engine.zig +++ b/engine/core/src/engine.zig @@ -1,6 +1,5 @@ const std = @import("std"); const logging = @import("logging.zig"); -const input = @import("input.zig"); const engineObject = @import("engineObject.zig"); const time = @import("engineTime.zig"); const core = @import("core.zig"); diff --git a/engine/core/src/engineObject.zig b/engine/core/src/engineObject.zig index 7c6405c..c095a63 100644 --- a/engine/core/src/engineObject.zig +++ b/engine/core/src/engineObject.zig @@ -1,6 +1,5 @@ const std = @import("std"); const logging = @import("logging.zig"); -const input = @import("input.zig"); const p2 = @import("p2"); const engine_logs = logging.engine_logs; diff --git a/engine/core/src/input.zig b/engine/core/src/input.zig deleted file mode 100644 index 3ed28f9..0000000 --- a/engine/core/src/input.zig +++ /dev/null @@ -1,20 +0,0 @@ -// procedural functions to describe input primitives with which -// we can build on the rest of the engine. - -const std = @import("std"); -const engineObject = @import("engineObject.zig"); - -pub const InputSubsystem = struct { - const Self = @This(); - pub var NeonObjectTable: engineObject.EngineObjectVTable = engineObject.EngineObjectVTable.from(Self); - - allocator: std.mem.Allocator, - - pub fn init(allocator: std.mem.Allocator) Self { - const self = Self{ - .allocator = allocator, - }; - - return self; - } -}; diff --git a/engine/core/src/inputs/inputStack.zig b/engine/core/src/inputs/inputStack.zig index 9e5fd7e..c79e482 100644 --- a/engine/core/src/inputs/inputStack.zig +++ b/engine/core/src/inputs/inputStack.zig @@ -11,6 +11,10 @@ pub fn convertEvent(event: *const sdl3.Event) ?IOEvent { sdl3.events.key_down, sdl3.events.key_up => { const scan: Key = @enumFromInt(event.key.scancode); + if (event.key.repeat) { + return null; + } + return IOEvent{ .key = .{ .key = scan, @@ -32,6 +36,11 @@ pub fn convertEvent(event: *const sdl3.Event) ?IOEvent { }, }; }, + sdl3.events.mouse_motion => { + const motion = core.Vector2f{ .x = event.motion.xrel, .y = event.motion.yrel }; + + return .{ .mouseRelative = motion }; + }, else => { return null; }, @@ -45,6 +54,7 @@ pub const IOEvent = union(enum(u8)) { mousePosition: struct { x: f64, y: f64 }, scroll: struct { xoffset: f64, yoffset: f64 }, key: struct { key: Key, scancode: Key, action: ActionEvent, mods: c_int }, + mouseRelative: core.Vector2f, windowResize: struct { newSize: core.Vector2f }, codepoint: c_uint, }; @@ -105,7 +115,6 @@ fn BindingData(Listener: type, Func: type) type { } pub fn setKeyHeld(self: *@This(), key: Key) void { - // core.engine_log("repeating key {any}", .{key}); self.keysDown.push(.{ .key = key, .first = false }) catch unreachable; } @@ -175,6 +184,8 @@ pub const Axis1dBinding = struct { keys: std.ArrayListUnmanaged(AxisBindingKeyMagnitude) = .{}, magnitude: f32 = 0.0, + clampValue: ?f32 = 1.0, + pub const Listener = struct { id: u32, ctx: ?*anyopaque, @@ -197,6 +208,10 @@ pub const Axis1dBinding = struct { gInputStack.active.addBindingByName(self.data.name, self) catch unreachable; } + pub fn setClamp(self: *@This(), clampValue: ?f32) void { + self.clampValue = clampValue; + } + pub fn deactivate(self: *@This()) void { gInputStack.active.removeBindingByName(self.data.name); } @@ -220,8 +235,14 @@ pub const Axis2dBinding = struct { yKeys: std.ArrayListUnmanaged(AxisBindingKeyMagnitude) = .{}, xKeys: std.ArrayListUnmanaged(AxisBindingKeyMagnitude) = .{}, + // if mouseRelative is set to true then relative mouse movement values shall be passed to the listener + // + mouseRelative: bool = false, + magnitude: core.Vector2f = core.Vector2f.Zeroes, + clampValue: ?f32 = 1.0, + pub const Listener = struct { id: u32, ctx: ?*anyopaque, @@ -239,6 +260,19 @@ pub const Axis2dBinding = struct { return self; } + pub fn disableMouseRelative(self: *@This()) void { + self.mouseRelative = false; + } + + pub fn enableMouseRelative(self: *@This()) void { + self.mouseRelative = true; + self.clampValue = null; + } + + pub fn setClamp(self: *@This(), clampValue: ?f32) void { + self.clampValue = clampValue; + } + // pushes this binding to the active layer pub fn activate(self: *@This()) void { gInputStack.active.addBindingByName(self.data.name, self) catch unreachable; @@ -364,7 +398,9 @@ pub const Binding = union(BindingType) { } } - p.magnitude = std.math.clamp(p.magnitude, -1.0, 1.0); + if (p.clampValue) |clampValue| { + p.magnitude = std.math.clamp(p.magnitude, -clampValue, clampValue); + } p.data.routed = routed; // for (p.data.listeners.items) |listener| { @@ -388,8 +424,10 @@ pub const Binding = union(BindingType) { } } - p.magnitude.x = std.math.clamp(p.magnitude.x, -1.0, 1.0); - p.magnitude.y = std.math.clamp(p.magnitude.y, -1.0, 1.0); + if (p.clampValue) |clampValue| { + p.magnitude.x = std.math.clamp(p.magnitude.x, -clampValue, clampValue); + p.magnitude.y = std.math.clamp(p.magnitude.y, -clampValue, clampValue); + } // core.engine_log(" >> {any} > routeBindingEvent magnitude {d} {d}", .{ key, p.magnitude.x, p.magnitude.y }); @@ -583,11 +621,9 @@ pub const InputStack = struct { if (event == .keyDown) { binding.setKeyDown(key); - } - if (event == .keyHeld) { + } else if (event == .keyHeld) { binding.setKeyHeld(key); - } - if (event == .keyUp) { + } else if (event == .keyUp) { binding.setKeyUp(key); } @@ -597,15 +633,39 @@ pub const InputStack = struct { } } + fn routeMouseRelative(self: *@This(), move: core.Vector2f) void { + if (self.active.bindingStack.items.len == 0) { + return; + } + var i: i32 = @intCast(self.active.bindingStack.items.len - 1); + // core.engine_log(" >> {any} > routeKeyEvent 2 binding count : {d}", .{ key, i }); + while (i >= 0) : (i -= 1) { + const binding = self.active.bindingStack.items[@intCast(i)]; + + switch (binding) { + // only axis2d handles mouse relative right now, + .axis2d => |axis| { + if (axis.mouseRelative) { + axis.magnitude = axis.magnitude.add(move); + } + }, + else => {}, + } + } + } + pub fn routeEvent(self: *@This(), eventToRoute: IOEvent) void { switch (eventToRoute) { .key => |key| { const keyEvent: ActionEvent = key.action; // @enumFromInt(@as(u8, @intCast(key.action))); if (keyEvent != .keyHeld) { // core.engine_log("converted: {any}", .{keyEvent}); - self.routeKeyEvent(key.key, keyEvent); + self.routeKeyEvent(key.key, keyEvent); // do not route keyHelds } }, + .mouseRelative => |rel| { + self.routeMouseRelative(rel); + }, else => {}, } } diff --git a/engine/imgui/src/sgpu/backend_impl.zig b/engine/imgui/src/sgpu/backend_impl.zig index d83772f..c9c6f01 100644 --- a/engine/imgui/src/sgpu/backend_impl.zig +++ b/engine/imgui/src/sgpu/backend_impl.zig @@ -48,7 +48,7 @@ pub const Impl = struct { //const renderpass = cmd.beginGPURenderPass(color_target_infos: [*c]const GPUColorTargetInfo, num_color_targets: u32, depth_stencil_target_info: [*c]const GPUDepthStencilTargetInfo); var targetInfo: gpu.GPUColorTargetInfo = std.mem.zeroes(gpu.GPUColorTargetInfo); - targetInfo.texture = rend.context().swapchainTexture; + targetInfo.texture = rend.context().swapchainTexture.?; targetInfo.clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 0.0 }; targetInfo.load_op = .loadopLoad; targetInfo.store_op = .storeopStore; diff --git a/engine/platform/src/platform.zig b/engine/platform/src/platform.zig index 975f23e..90e3da0 100644 --- a/engine/platform/src/platform.zig +++ b/engine/platform/src/platform.zig @@ -15,12 +15,28 @@ pub const Module: core.ModuleDescription = .{ var gPlatformInstance: *windowing.PlatformInstance = undefined; var gStartupParams: windowing.PlatformParams = .{}; +pub fn context() *windowing.PlatformInstance { + return gPlatformInstance; +} + pub fn setWindowSettings(params: windowing.PlatformParams) void { gStartupParams = params; } -pub fn getCursorPosition() core.Vector2u { - gPlatformInstance.getCursorPosition(); +pub fn getCursorPosition() core.Vector2f { + return gPlatformInstance.getCursorPosition(); +} + +pub fn setCursorVisible(visible: bool) void { + return gPlatformInstance.setCursorVisible(visible); +} + +pub fn setMousePosition(pos: core.Vector2f) void { + gPlatformInstance.setMousePosition(pos); +} + +pub fn setMouseRelativeMode(relativeMode: bool) void { + gPlatformInstance.setMouseRelativeMode(relativeMode); } pub fn start_module(comptime programSpec: anytype, args: anytype, allocator: std.mem.Allocator) !void { diff --git a/engine/platform/src/windowing.zig b/engine/platform/src/windowing.zig index 064fbeb..a6e2af9 100644 --- a/engine/platform/src/windowing.zig +++ b/engine/platform/src/windowing.zig @@ -69,6 +69,7 @@ pub const PlatformInstance = struct { window: *sdl3.Window = undefined, windowExtent: core.Vector2c, + extent: core.Vector2f, windowName: [:0]u8, iconPath: [:0]u8, @@ -104,12 +105,19 @@ pub const PlatformInstance = struct { .windowName = try allocator.dupeZ(u8, params.windowName), .iconPath = try allocator.dupeZ(u8, params.icon), .windowExtent = params.extent, + .extent = .{ .x = @floatFromInt(params.extent.x), .y = @floatFromInt(params.extent.y) }, .hasVideo = params.hasVideo, }; return self; } + pub fn setMouseRelativeMode(self: *@This(), relativeMode: bool) void { + // _ = sdl3.c.SDL_SetHint(sdl3.c.SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, "1"); + _ = sdl3.c.SDL_SetWindowRelativeMouseMode(self.window, relativeMode); + _ = sdl3.c.SDL_SetWindowMouseRect(self.window, null); + } + pub fn addSDLProcessFunction(self: *@This(), processFunc: *const fn (*sdl3.Event) void) !void { try self.processFuncs.append(self.allocator, processFunc); } @@ -117,6 +125,10 @@ pub const PlatformInstance = struct { pub fn setupWindow(self: *@This()) core.EngineDataEventError!void { sdl3.init(.{ .video = true, .gamepad = true }) catch return error.BadInit; self.window = sdl3.c.SDL_CreateWindow(self.windowName, self.windowExtent.x, self.windowExtent.y, 0).?; // resizeable + self.extent = .{ + .x = @floatFromInt(self.windowExtent.x), + .y = @floatFromInt(self.windowExtent.y), + }; } pub fn registerFuncs(self: *@This()) !void { @@ -136,6 +148,10 @@ pub const PlatformInstance = struct { try self.listeners.append(RawInputObjectRef.from(listener)); } + pub fn setMousePosition(self: *@This(), pos: core.Vector2f) void { + sdl3.c.SDL_WarpMouseInWindow(self.window, pos.x, pos.y); + } + pub fn processEvents(ptr: *anyopaque, frameNumber: u64) core.EngineDataEventError!void { _ = frameNumber; @@ -182,6 +198,15 @@ pub const PlatformInstance = struct { pub fn isCursorEnabled(self: @This()) bool { return self.cursorEnabled; } + + pub fn setCursorVisible(self: @This(), visible: bool) void { + _ = self; + if (visible) { + sdl3.showCursor(); + } else { + sdl3.hideCursor(); + } + } }; pub const IOEvent = core.IOEvent; diff --git a/engine/rend/src/sgpu/renderer.zig b/engine/rend/src/sgpu/renderer.zig index a38f5cc..0cfa55d 100644 --- a/engine/rend/src/sgpu/renderer.zig +++ b/engine/rend/src/sgpu/renderer.zig @@ -46,7 +46,7 @@ pub const Renderer = struct { debugDrawSys: *DebugDrawSystem = undefined, // transients DO NOT TOUCH - swapchainTexture: *gpu.GPUTexture = undefined, + swapchainTexture: ?*gpu.GPUTexture = undefined, pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This()); @@ -419,9 +419,14 @@ pub const Renderer = struct { interface.func(interface.ptr, cmd); } - if (cmd.waitAndAcquireGPUSwapchainTexture(self.window, &self.swapchainTexture, null, null)) { + if (cmd.waitAndAcquireGPUSwapchainTexture(self.window, @ptrCast(&self.swapchainTexture), null, null)) { + if (self.swapchainTexture == null) { + _ = cmd.submitGPUCommandBuffer(); + return; + } + var targetInfo: gpu.GPUColorTargetInfo = std.mem.zeroes(gpu.GPUColorTargetInfo); - targetInfo.texture = self.swapchainTexture; + targetInfo.texture = self.swapchainTexture.?; targetInfo.clear_color = .{ .r = 0.1, .g = 0.1, .b = 0.1, .a = 1.0 }; targetInfo.load_op = .loadopClear; targetInfo.store_op = .storeopStore; diff --git a/lib/sdl3/src/sdl3.zig b/lib/sdl3/src/sdl3.zig index 6555922..403bb06 100644 --- a/lib/sdl3/src/sdl3.zig +++ b/lib/sdl3/src/sdl3.zig @@ -52,6 +52,14 @@ pub fn getError() [*c]const u8 { return c.SDL_GetError(); } +pub fn hideCursor() void { + _ = c.SDL_HideCursor(); +} + +pub fn showCursor() void { + _ = c.SDL_ShowCursor(); +} + pub const gpu = @import("gpu.zig"); pub const Scancode = @import("scancode.zig").Scancode; pub const shaderTypes = @import("shaderTypes"); diff --git a/projects/sampleGame/main.zig b/projects/sampleGame/main.zig index c0501f0..7946de8 100644 --- a/projects/sampleGame/main.zig +++ b/projects/sampleGame/main.zig @@ -7,14 +7,19 @@ cameraSpeed: f32 = 10.0, moveInput: *core.Axis2dBinding = undefined, moveVector: core.Vectorf = .{}, +mouseMove: core.Vector2f = .{}, rotateAxis: f32 = 0.0, rotateAxisPitch: f32 = 0.0, +sensitivity: f32 = 5.0, verticalMove: f32 = 0.0, + yaw: f32 = 0.0, pitch: f32 = 0.0, +mouseLook: bool = true, + pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This()); pub fn init(allocator: std.mem.Allocator) !*@This() { @@ -62,6 +67,7 @@ pub fn prepare(self: *@This()) !void { try script.runScriptFile("scripts/prepare.lua"); try assets.loadList(assetReferences); + platform.setMouseRelativeMode(true); const exitInput = try core.ActionBinding.create(core.MakeName("exit")); exitInput.addKey(.escape, .keyDown); @@ -140,19 +146,54 @@ pub fn prepare(self: *@This()) !void { i.activate(); } - const verticalInput = try core.Axis1dBinding.create(core.MakeName("movementVertical")); - verticalInput.addKey(.e, 1.0); - verticalInput.addKey(.q, -1.0); + { + const verticalInput = try core.Axis1dBinding.create(core.MakeName("movementVertical")); + verticalInput.addKey(.e, 1.0); + verticalInput.addKey(.q, -1.0); - _ = verticalInput.data.addListener(self, verticalMovement); - verticalInput.activate(); + _ = verticalInput.data.addListener(self, verticalMovement); + verticalInput.activate(); + } + + { + const mouseLook = try core.Axis2dBinding.create(core.MakeName("mouseLook")); + mouseLook.enableMouseRelative(); + _ = mouseLook.data.addListener(self, onMouseLook); + mouseLook.activate(); + } { const shaderReload = try core.ActionBinding.create(core.MakeName("shaderReload")); shaderReload.addKey(.r, .keyDown); - _ = shaderReload.data.addListener(null, onShaderReload); + _ = shaderReload.data.addListener(self, onShaderReload); shaderReload.activate(); } + + { + const input = try core.ActionBinding.create(core.MakeName("toggleMouseLook")); + input.addKey(.t, .keyDown); + _ = input.data.addListener(self, toggleMouseLook); + input.activate(); + } +} + +fn onMouseLook(ctx: ?*anyopaque, axis: core.Vector2f) void { + const self: *@This() = @ptrCast(@alignCast(ctx)); + + self.mouseMove = axis; +} + +fn toggleMouseLook(ctx: ?*anyopaque, action: core.ActionEvent) void { + const self: *@This() = @ptrCast(@alignCast(ctx)); + _ = action; + + self.mouseLook = !self.mouseLook; + self.updateMouseLook(); +} + +fn updateMouseLook(self: *@This()) void { + core.engine_log("{s}", .{if (self.mouseLook) "true" else "false"}); + platform.setCursorVisible(self.mouseLook); } fn onShaderReload(ctx: ?*anyopaque, action: core.ActionEvent) void { @@ -201,17 +242,28 @@ pub fn onExit(ctx: ?*anyopaque, action: core.ActionEvent) void { core.exitNow(); } +pub fn getMouseMovement(self: *@This()) core.Vector2f { + const rv = self.mouseMove; + self.mouseMove = .{}; + + return rv; +} + pub fn tick(self: *@This(), dt: f64) void { const fdt: f32 = @floatCast(dt); if (self.camera.fetch(core.Scene)) |scene| { const vector = core.Vectorf{ .x = self.moveVector.x, .z = self.moveVector.z, .y = self.verticalMove }; - const posRot = scene.getPosRot(); self.yaw += self.rotateAxis * fdt * 20; self.pitch += self.rotateAxisPitch * fdt * 20; + const mouseMovement = self.getMouseMovement(); + + self.yaw += core.radians(mouseMovement.x * self.sensitivity); + self.pitch -= core.radians(mouseMovement.y * self.sensitivity); + self.cameraComponent.pitch = std.math.clamp(core.radians(self.pitch), core.radians(-90.0), core.radians(90.0)); posRot.rotation = core.Rotation.eulerY(core.radians(self.yaw)); const movement = scene.getPosRot().rotation.rotateVector(vector.fmul(self.cameraSpeed * fdt)); @@ -247,6 +299,7 @@ pub fn main() anyerror!void { const std = @import("std"); const backlog = @import("Backlog"); const assets = backlog.assets; +const platform = backlog.platform; const core = backlog.core; const ig = backlog.imgui.api; const rend = backlog.rend;