From 7a417e0701fed6c69d00f4d75673f193447c050c Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Sat, 24 Aug 2024 13:52:32 +1000 Subject: [PATCH] add mouse support --- Sources/Voxelotl/Application.swift | 12 +++ Sources/Voxelotl/CMakeLists.txt | 8 +- .../Voxelotl/{ => Input}/GameController.swift | 0 Sources/Voxelotl/{ => Input}/Keyboard.swift | 2 +- Sources/Voxelotl/Input/Mouse.swift | 81 +++++++++++++++++++ Sources/Voxelotl/Player.swift | 19 +++-- 6 files changed, 111 insertions(+), 11 deletions(-) rename Sources/Voxelotl/{ => Input}/GameController.swift (100%) rename Sources/Voxelotl/{ => Input}/Keyboard.swift (99%) create mode 100644 Sources/Voxelotl/Input/Mouse.swift diff --git a/Sources/Voxelotl/Application.swift b/Sources/Voxelotl/Application.swift index 2eb604c..163d642 100644 --- a/Sources/Voxelotl/Application.swift +++ b/Sources/Voxelotl/Application.swift @@ -71,6 +71,7 @@ public class Application { private func beginHandleEvents() { Keyboard.instance.newFrame() GameController.instance.newFrame() + Mouse.instance.newFrame(window!) } private func handleEvent(_ event: SDL_Event) -> ApplicationExecutionState { @@ -108,6 +109,17 @@ public class Application { btn: SDL_GamepadButton(Int32(event.gbutton.button)), state: event.gbutton.state) return .running + case SDL_EVENT_MOUSE_BUTTON_DOWN, SDL_EVENT_MOUSE_BUTTON_UP: + Mouse.instance.buttonEvent( + btn: UInt32(event.button.button), + state: event.button.state) + return .running + case SDL_EVENT_MOUSE_MOTION: + Mouse.instance.motionEvent( + absolute: SIMD2(event.motion.x, event.motion.y), + relative: SIMD2(event.motion.xrel, event.motion.yrel)) + return .running + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: let backBufferSize = Size(Int(event.window.data1), Int(event.window.data2)) self.renderer!.resize(size: backBufferSize) diff --git a/Sources/Voxelotl/CMakeLists.txt b/Sources/Voxelotl/CMakeLists.txt index afffc60..fefc8e4 100644 --- a/Sources/Voxelotl/CMakeLists.txt +++ b/Sources/Voxelotl/CMakeLists.txt @@ -26,12 +26,15 @@ add_executable(Voxelotl MACOSX_BUNDLE # Resource classes NSImageLoader.swift + # Input wrappers + Input/Keyboard.swift + Input/GameController.swift + Input/Mouse.swift + # Core utility classes Color.swift Camera.swift Renderer.swift - Keyboard.swift - GameController.swift FPSCalculator.swift GameDelegate.swift Application.swift @@ -81,3 +84,4 @@ source_group("Resources" FILES Assets.xcassets test.png) source_group("Source Files" REGULAR_EXPRESSION "\\.(swift|metal)$") source_group("Source Files\\Random" REGULAR_EXPRESSION "Random/") source_group("Source Files\\Math" REGULAR_EXPRESSION "Math/") +source_group("Source Files\\Input" REGULAR_EXPRESSION "Input/") diff --git a/Sources/Voxelotl/GameController.swift b/Sources/Voxelotl/Input/GameController.swift similarity index 100% rename from Sources/Voxelotl/GameController.swift rename to Sources/Voxelotl/Input/GameController.swift diff --git a/Sources/Voxelotl/Keyboard.swift b/Sources/Voxelotl/Input/Keyboard.swift similarity index 99% rename from Sources/Voxelotl/Keyboard.swift rename to Sources/Voxelotl/Input/Keyboard.swift index 5c37ae6..1f76a7c 100644 --- a/Sources/Voxelotl/Keyboard.swift +++ b/Sources/Voxelotl/Input/Keyboard.swift @@ -36,7 +36,7 @@ public class Keyboard { private static let _REPEAT = UInt8(0b100) private static let _PRESS: UInt8 = _DOWN | _IMPULSE private static let _RELEASE: UInt8 = _UP | _IMPULSE - + private var _state = [UInt8](repeating: _UP, count: Int(SDL_NUM_SCANCODES.rawValue)) internal func keyDownEvent(scan: SDL_Scancode, repeat rep: Bool) { diff --git a/Sources/Voxelotl/Input/Mouse.swift b/Sources/Voxelotl/Input/Mouse.swift new file mode 100644 index 0000000..b1fe84e --- /dev/null +++ b/Sources/Voxelotl/Input/Mouse.swift @@ -0,0 +1,81 @@ +import SDL3 + +public class Mouse { + public struct Buttons: OptionSet { + public let rawValue: UInt32 + public init(rawValue: UInt32) { self.rawValue = rawValue } + + static let left = Self(rawValue: UInt32(SDL_BUTTON_LEFT).buttonMask) + static let middle = Self(rawValue: UInt32(SDL_BUTTON_MIDDLE).buttonMask) + static let right = Self(rawValue: UInt32(SDL_BUTTON_RIGHT).buttonMask) + static let button4 = Self(rawValue: UInt32(SDL_BUTTON_X1).buttonMask) + static let button5 = Self(rawValue: UInt32(SDL_BUTTON_X2).buttonMask) + } + + public static var capture: Bool { + get { self._instance.getCapture() } + set { self._instance.setCapture(newValue) } + } + + public static var position: SIMD2 { self._instance.getAbsolute() } + public static var relative: SIMD2 { self._instance.getDelta() } + + public static func down(_ btn: Buttons) -> Bool { + btn.isSubset(of: self._instance._btns) + } + public static func pressed(_ btn: Buttons) -> Bool { + btn.isSubset(of: self._instance._btns.intersection(self._instance._btnImpulse)) + } + public static func released(_ btn: Buttons) -> Bool { + btn.isSubset(of: self._instance._btnImpulse.subtracting(self._instance._btns)) + } + + //MARK: - Private + + private static let _instance = Mouse() + public static var instance: Mouse { self._instance } + + private var _window: OpaquePointer! + private var _captured: Bool = false + private var _abs: SIMD2 = .zero, _delta: SIMD2 = .zero + private var _btns: Buttons = [], _btnImpulse: Buttons = [] + + private func getCapture() -> Bool { self._captured } + private func setCapture(_ toggle: Bool) { + let sdlBool = toggle ? SDL_TRUE : SDL_FALSE + if SDL_SetRelativeMouseMode(sdlBool) >= 0 && SDL_SetWindowMouseGrab(self._window, sdlBool) >= 0 { + self._captured = toggle + } + } + + private func getAbsolute() -> SIMD2 { self._abs } + private func getDelta() -> SIMD2 { self._delta } + + internal func buttonEvent(btn: UInt32, state: UInt8) { + if state == SDL_PRESSED { + self._btns.formUnion(.init(rawValue: btn.buttonMask)) + } else { + self._btns.subtract(.init(rawValue: btn.buttonMask)) + } + self._btnImpulse.formUnion(.init(rawValue: btn.buttonMask)) + } + + internal func motionEvent(absolute: SIMD2, relative: SIMD2) { + self._abs = absolute + self._delta += relative + } + + internal func newFrame(_ window: OpaquePointer) { + self._window = window + + let grabbedFlag = SDL_WindowFlags(SDL_WINDOW_MOUSE_GRABBED) + self._captured = (SDL_GetWindowFlags(window) & grabbedFlag) == grabbedFlag + + self._delta = .zero + self._btnImpulse = [] + } +} + +fileprivate extension UInt32 { + var buttonMask: UInt32 { 1 &<< (self &- 1) } +} diff --git a/Sources/Voxelotl/Player.swift b/Sources/Voxelotl/Player.swift index 72a1cd6..9fe16b4 100644 --- a/Sources/Voxelotl/Player.swift +++ b/Sources/Voxelotl/Player.swift @@ -50,7 +50,7 @@ struct Player { // Read controller input (if one is plugged in) if let pad = GameController.current?.state { turning += pad.rightStick.radialDeadzone(min: 0.1, max: 1) - movement += pad.leftStick.cardinalDeadzone(min: 0.1, max: 1) + movement = pad.leftStick.cardinalDeadzone(min: 0.1, max: 1) flying += (pad.down(.rightBumper) ? 1 : 0) - (pad.down(.leftBumper) ? 1 : 0) if pad.pressed(.east) { jumpInput = .press @@ -72,19 +72,22 @@ struct Player { if Keyboard.down(.s) { movement.y += 1 } if Keyboard.down(.a) { movement.x -= 1 } if Keyboard.down(.d) { movement.x += 1 } - if Keyboard.down(.up) { turning.y -= 1 } - if Keyboard.down(.down) { turning.y += 1 } - if Keyboard.down(.left) { turning.x -= 1 } - if Keyboard.down(.right) { turning.x += 1 } - if Keyboard.down(.tab) { flying += 1 } - if Keyboard.pressed(.q) { place = true } - if Keyboard.pressed(.e) { destroy = true } + if Keyboard.down(.q) { flying += 1 } + if Keyboard.down(.e) { flying -= 1 } + if Keyboard.pressed(.tab) { Mouse.capture = !Mouse.capture } if Keyboard.pressed(.space) { jumpInput = .press } else if jumpInput != .press && Keyboard.down(.space) { jumpInput = .held } + // Read mouse input + if Mouse.pressed(.left) { destroy = true } + if Mouse.pressed(.right) { place = true } + if Mouse.capture { + self._rotation += Mouse.relative / 2048 * Float.pi + } + // Turning input self._rotation += turning * deltaTime * 3.0 if self._rotation.x < 0.0 {