import Foundation import SDL2 import Maths public class GamePad { public enum Axes { case leftStickX, leftStickY case rightStickX, rightStickY case leftTrigger, rightTrigger } public enum Buttons { case east, south, north, west case select, start, guide, mic, touchPad case leftStick, rightStick case leftBumper, rightBumper case dpadLeft, dpadRight, dpadUp, dpadDown case paddle1, paddle2, paddle3, paddle4 } public struct State { public func down(_ btn: Buttons) -> Bool { btnState(btn) & GamePad._DOWN == GamePad._DOWN } public func pressed(_ btn: Buttons) -> Bool { btnState(btn) == GamePad._PRESS } public func released(_ btn: Buttons) -> Bool { btnState(btn) == GamePad._RELEASE } public func axis(_ axis: Axes) -> Float { let axisRescale = 1.0 / Float(Int16.max) return Float(rawAxis(axis)) * axisRescale } @inline(__always) func rawAxis(_ axis: Axes) -> Int16 { _axes[Int(axis.sdlEnum.rawValue)] } @inline(__always) private func btnState(_ btn: Buttons) -> UInt8 { _btns[Int(btn.sdlEnum.rawValue)] } private let _btns: [UInt8], _axes: [Int16] internal init(btns: [UInt8], axes: [Int16]) { self._btns = btns self._axes = axes } } public var state: State { .init(btns: _btns, axes: _axes) } @inline(__always) public static var current: GamePad? { let input = Input.instance return if let id = input.firstPadID { input.getPad(id: id) } else { nil } } private static let _UP = UInt8(0b00), _DOWN = UInt8(0b10), _IMPULSE = UInt8(0b01) private static let _PRESS = _DOWN | _IMPULSE private static let _RELEASE = _UP | _IMPULSE private var _btns = [UInt8](repeating: 0, count: Int(SDL_CONTROLLER_BUTTON_MAX.rawValue)) private var _axes = [Int16](repeating: 0, count: Int(SDL_CONTROLLER_AXIS_MAX.rawValue)) private var _joyID: SDL_JoystickID, _sdlPad: OpaquePointer internal var instanceID: SDL_JoystickID { _joyID } public var name: String { String(cString: SDL_GameControllerName(_sdlPad)) } internal static func open(device: Int32) -> GamePad? { guard let sdlPad = SDL_GameControllerOpen(device) else { return nil } let joyID = SDL_JoystickGetDeviceInstanceID(device) return GamePad(instance: joyID, pad: sdlPad) } private init(instance: SDL_JoystickID, pad: OpaquePointer) { self._joyID = instance self._sdlPad = pad } internal func newTick() { _btns = _btns.map({ $0 & ~Self._IMPULSE }) } internal func close() { SDL_GameControllerClose(_sdlPad) } internal func buttonPressEvent(_ btn: UInt8) { _btns[Int(btn)] = Self._PRESS } internal func buttonReleaseEvent(_ btn: UInt8) { _btns[Int(btn)] = Self._RELEASE } internal func axisEvent(_ axis: UInt8, _ value: Int16) { _axes[Int(axis)] = value } } public extension GamePad.State { var leftStick: Vec2f { Vec2f(axis(.leftStickX), axis(.leftStickY)) } var rightStick: Vec2f { Vec2f(axis(.rightStickX), axis(.rightStickY)) } } internal extension GamePad.Axes { var sdlEnum: SDL_GameControllerAxis { switch self { case .leftStickX: SDL_CONTROLLER_AXIS_LEFTX case .leftStickY: SDL_CONTROLLER_AXIS_LEFTY case .rightStickX: SDL_CONTROLLER_AXIS_RIGHTX case .rightStickY: SDL_CONTROLLER_AXIS_RIGHTY case .leftTrigger: SDL_CONTROLLER_AXIS_TRIGGERLEFT case .rightTrigger: SDL_CONTROLLER_AXIS_TRIGGERRIGHT } } } internal extension GamePad.Buttons { var sdlEnum: SDL_GameControllerButton { switch self { case .east: SDL_CONTROLLER_BUTTON_B case .south: SDL_CONTROLLER_BUTTON_A case .north: SDL_CONTROLLER_BUTTON_Y case .west: SDL_CONTROLLER_BUTTON_X case .start: SDL_CONTROLLER_BUTTON_START case .select: SDL_CONTROLLER_BUTTON_BACK case .guide: SDL_CONTROLLER_BUTTON_GUIDE case .mic: SDL_CONTROLLER_BUTTON_MISC1 case .touchPad: SDL_CONTROLLER_BUTTON_TOUCHPAD case .leftStick: SDL_CONTROLLER_BUTTON_LEFTSTICK case .rightStick: SDL_CONTROLLER_BUTTON_RIGHTSTICK case .leftBumper: SDL_CONTROLLER_BUTTON_LEFTSHOULDER case .rightBumper: SDL_CONTROLLER_BUTTON_RIGHTSHOULDER case .dpadUp: SDL_CONTROLLER_BUTTON_DPAD_UP case .dpadDown: SDL_CONTROLLER_BUTTON_DPAD_DOWN case .dpadLeft: SDL_CONTROLLER_BUTTON_DPAD_LEFT case .dpadRight: SDL_CONTROLLER_BUTTON_DPAD_RIGHT case .paddle1: SDL_CONTROLLER_BUTTON_PADDLE1 case .paddle2: SDL_CONTROLLER_BUTTON_PADDLE2 case .paddle3: SDL_CONTROLLER_BUTTON_PADDLE3 case .paddle4: SDL_CONTROLLER_BUTTON_PADDLE4 } } } public extension Vector2 where Scalar: FloatingPoint { func cardinalDeadzone(min: Scalar, max: Scalar) -> Self { Self(self.x.axisDeadzone(min, max), self.y.axisDeadzone(min, max)) } func radialDeadzone(min: Scalar, max: Scalar) -> Self { let magnitude = self.len if magnitude == .zero || magnitude < min { return Self.zero } if magnitude > max { return self / magnitude } let rescale = (magnitude - min) / (max - min) return self / magnitude * rescale } } fileprivate extension FloatingPoint { @inline(__always) internal func axisDeadzone(_ min: Self, _ max: Self) -> Self { let xabs = abs(self) return if xabs <= min { 0 } else if xabs >= max { Self(signOf: self, magnitudeOf: 1) } else { Self(signOf: self, magnitudeOf: xabs - min) / (max - min) } } }