mirror of
https://github.com/GayPizzaSpecifications/voxelotl-engine.git
synced 2025-08-02 13:00:53 +00:00
use controller for moving around a test plane
This commit is contained in:
parent
fbff9b77fd
commit
477ce10e68
@ -17,13 +17,13 @@ public class Application {
|
||||
}
|
||||
|
||||
private func initialize() -> ApplicationExecutionState {
|
||||
guard SDL_Init(SDL_INIT_VIDEO) >= 0 else {
|
||||
guard SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD) >= 0 else {
|
||||
printErr("SDL_Init() error: \(String(cString: SDL_GetError()))")
|
||||
return .exitFailure
|
||||
}
|
||||
|
||||
// Create SDL window
|
||||
var windowFlags = SDL_WindowFlags(0)
|
||||
var windowFlags = SDL_WindowFlags(SDL_WINDOW_METAL)
|
||||
if cfg.flags.contains(.resizable) {
|
||||
windowFlags |= SDL_WindowFlags(SDL_WINDOW_RESIZABLE)
|
||||
}
|
||||
@ -67,6 +67,10 @@ public class Application {
|
||||
SDL_Quit()
|
||||
}
|
||||
|
||||
private func beginHandleEvents() {
|
||||
GameController.instance.newFrame()
|
||||
}
|
||||
|
||||
private func handleEvent(_ event: SDL_Event) -> ApplicationExecutionState {
|
||||
switch SDL_EventType(event.type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
@ -81,6 +85,23 @@ public class Application {
|
||||
}
|
||||
return .running
|
||||
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
if SDL_IsGamepad(event.gdevice.which) != SDL_FALSE {
|
||||
GameController.instance.connectedEvent(id: event.gdevice.which)
|
||||
}
|
||||
return .running
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
GameController.instance.removedEvent(id: event.gdevice.which)
|
||||
return .running
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
GameController.instance.axisEvent(id: event.gaxis.which,
|
||||
axis: SDL_GamepadAxis(Int32(event.gaxis.axis)), value: event.gaxis.value)
|
||||
return .running
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN, SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
GameController.instance.buttonEvent(id: event.gbutton.which,
|
||||
btn: SDL_GamepadButton(Int32(event.gbutton.button)), state: event.gbutton.state)
|
||||
return .running
|
||||
|
||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||
let backBufferSize = SIMD2(Int(event.window.data1), Int(event.window.data2))
|
||||
renderer!.resize(size: backBufferSize)
|
||||
@ -120,6 +141,7 @@ public class Application {
|
||||
var res = initialize()
|
||||
|
||||
quit: while res == .running {
|
||||
beginHandleEvents()
|
||||
var event = SDL_Event()
|
||||
while SDL_PollEvent(&event) > 0 {
|
||||
res = handleEvent(event)
|
||||
|
@ -11,6 +11,8 @@ add_executable(Voxelotl MACOSX_BUNDLE
|
||||
|
||||
NSImageLoader.swift
|
||||
Renderer.swift
|
||||
GameController.swift
|
||||
Camera.swift
|
||||
FPSCalculator.swift
|
||||
Application.swift
|
||||
main.swift)
|
||||
|
37
Sources/Voxelotl/Camera.swift
Normal file
37
Sources/Voxelotl/Camera.swift
Normal file
@ -0,0 +1,37 @@
|
||||
import simd
|
||||
|
||||
struct Camera {
|
||||
private var position = SIMD3<Float>.zero
|
||||
private var rotation = SIMD2<Float>.zero
|
||||
|
||||
var view: matrix_float4x4 {
|
||||
.rotate(yawPitch: rotation) * .translate(-position)
|
||||
}
|
||||
|
||||
mutating func update(deltaTime: Float) {
|
||||
if let pad = GameController.current?.state {
|
||||
let turning = pad.rightStick.radialDeadzone(min: 0.1, max: 1)
|
||||
rotation += turning * deltaTime
|
||||
if rotation.x < 0.0 {
|
||||
rotation.x += .pi * 2
|
||||
} else if rotation.x > .pi * 2 {
|
||||
rotation.x -= .pi * 2
|
||||
}
|
||||
rotation.y = rotation.y.clamp(-.pi * 0.5, .pi * 0.5)
|
||||
|
||||
let movement = pad.leftStick.cardinalDeadzone(min: 0.1, max: 1)
|
||||
|
||||
let rotc = cos(rotation.x), rots = sin(rotation.x)
|
||||
position += .init(
|
||||
movement.x * rotc - movement.y * rots,
|
||||
0,
|
||||
movement.y * rotc + movement.x * rots
|
||||
) * deltaTime
|
||||
|
||||
if pad.pressed(.back) {
|
||||
position = .zero
|
||||
rotation = .zero
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,4 +4,7 @@ public extension FloatingPoint {
|
||||
|
||||
@inline(__always) func lerp(_ a: Self, _ b: Self) -> Self { b * self + a * (1 - self) }
|
||||
@inline(__always) func mlerp(_ a: Self, _ b: Self) -> Self { a + (b - a) * self }
|
||||
|
||||
@inline(__always) func clamp(_ a: Self, _ b: Self) -> Self { min(max(self, a), b) }
|
||||
@inline(__always) func saturate() -> Self { self.clamp(0, 1) }
|
||||
}
|
||||
|
224
Sources/Voxelotl/GameController.swift
Normal file
224
Sources/Voxelotl/GameController.swift
Normal file
@ -0,0 +1,224 @@
|
||||
import SDL3
|
||||
|
||||
public class GameController {
|
||||
public struct Pad {
|
||||
public enum Axes {
|
||||
case leftStickX, leftStickY
|
||||
case rightStickX, rightStickY
|
||||
case leftTrigger, rightTrigger
|
||||
|
||||
internal var sdlEnum: SDL_GamepadAxis {
|
||||
switch self {
|
||||
case .leftStickX: SDL_GAMEPAD_AXIS_LEFTX
|
||||
case .leftStickY: SDL_GAMEPAD_AXIS_LEFTY
|
||||
case .rightStickX: SDL_GAMEPAD_AXIS_RIGHTX
|
||||
case .rightStickY: SDL_GAMEPAD_AXIS_RIGHTY
|
||||
case .leftTrigger: SDL_GAMEPAD_AXIS_LEFT_TRIGGER
|
||||
case .rightTrigger: SDL_GAMEPAD_AXIS_RIGHT_TRIGGER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Buttons: OptionSet {
|
||||
public let rawValue: Int
|
||||
public init(rawValue: Int) { self.rawValue = rawValue }
|
||||
|
||||
static let east = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_EAST.rawValue)
|
||||
static let south = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_SOUTH.rawValue)
|
||||
static let north = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_NORTH.rawValue)
|
||||
static let west = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_WEST.rawValue)
|
||||
static let back = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_BACK.rawValue)
|
||||
static let start = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_START.rawValue)
|
||||
static let guide = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_GUIDE.rawValue)
|
||||
static let leftStick = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_LEFT_STICK.rawValue)
|
||||
static let rightStick = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK.rawValue)
|
||||
static let leftBumper = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER.rawValue)
|
||||
static let rightBumper = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER.rawValue)
|
||||
static let dpadLeft = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT.rawValue)
|
||||
static let dpadRight = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT.rawValue)
|
||||
static let dpadUp = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_DPAD_UP.rawValue)
|
||||
static let dpadDown = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN.rawValue)
|
||||
static let misc1 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_MISC1.rawValue)
|
||||
static let misc2 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_MISC2.rawValue)
|
||||
static let misc3 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_MISC3.rawValue)
|
||||
static let misc4 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_MISC4.rawValue)
|
||||
static let misc5 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_MISC5.rawValue)
|
||||
static let misc6 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_MISC6.rawValue)
|
||||
static let paddle1 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1.rawValue)
|
||||
static let paddle2 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE1.rawValue)
|
||||
static let paddle3 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2.rawValue)
|
||||
static let paddle4 = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE2.rawValue)
|
||||
static let touchPad = Self(rawValue: 1 << SDL_GAMEPAD_BUTTON_TOUCHPAD.rawValue)
|
||||
}
|
||||
|
||||
public struct State {
|
||||
private let _axes: [Int16]
|
||||
private let _btns: Buttons, _btnImpulse: Buttons
|
||||
|
||||
internal init(axes: [Int16], btns: Buttons, btnImpulse: Buttons) {
|
||||
self._axes = axes
|
||||
self._btns = btns
|
||||
self._btnImpulse = btnImpulse
|
||||
}
|
||||
|
||||
public func axis(_ axis: Axes) -> Float {
|
||||
let raw = rawAxis(axis)
|
||||
let rescale = raw < 0 ? 1 / Float(-Int(Int16.min)) : 1 / Float(Int16.max)
|
||||
return Float(raw) * rescale
|
||||
}
|
||||
@inline(__always) func rawAxis(_ axis: Axes) -> Int16 {
|
||||
_axes[Int(axis.sdlEnum.rawValue)]
|
||||
}
|
||||
|
||||
public func down(_ btn: Buttons) -> Bool {
|
||||
btn.isSubset(of: _btns)
|
||||
}
|
||||
public func pressed(_ btn: Buttons) -> Bool {
|
||||
btn.isSubset(of: _btns.intersection(_btnImpulse))
|
||||
}
|
||||
public func released(_ btn: Buttons) -> Bool {
|
||||
btn.isSubset(of: _btnImpulse.subtracting(_btns))
|
||||
}
|
||||
}
|
||||
|
||||
public var name: String { String(cString: SDL_GetGamepadName(_sdlPad)) }
|
||||
public var state: State {
|
||||
.init(
|
||||
axes: self._axes,
|
||||
btns: self._btnCur,
|
||||
btnImpulse: self._btnCur.symmetricDifference(self._btnPrv))
|
||||
}
|
||||
|
||||
//MARK: - Private
|
||||
|
||||
private var _joyInstance: SDL_JoystickID, _sdlPad: OpaquePointer
|
||||
private var _axes = [Int16](repeating: 0, count: Int(SDL_GAMEPAD_AXIS_MAX.rawValue))
|
||||
private var _btnCur: Buttons = [], _btnPrv: Buttons = []
|
||||
|
||||
internal var instanceID: SDL_JoystickID { _joyInstance }
|
||||
|
||||
private init(instance: SDL_JoystickID, pad: OpaquePointer) {
|
||||
self._joyInstance = instance
|
||||
self._sdlPad = pad
|
||||
}
|
||||
|
||||
internal static func open(joystickID: SDL_JoystickID) -> Self? {
|
||||
return if let sdlPad = SDL_OpenGamepad(joystickID) {
|
||||
.init(instance: joystickID, pad: sdlPad)
|
||||
} else { nil }
|
||||
}
|
||||
|
||||
internal func close() {
|
||||
SDL_CloseGamepad(self._sdlPad)
|
||||
}
|
||||
|
||||
internal mutating func buttonEvent(_ btn: SDL_GamepadButton, _ down: Bool) {
|
||||
if down {
|
||||
self._btnCur.formUnion(.init(rawValue: 1 << btn.rawValue))
|
||||
} else {
|
||||
self._btnCur.subtract(.init(rawValue: 1 << btn.rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
internal mutating func axisEvent(_ axis: SDL_GamepadAxis, _ value: Int16) {
|
||||
self._axes[Int(axis.rawValue)] = value
|
||||
}
|
||||
|
||||
internal mutating func newTick() {
|
||||
self._btnPrv = self._btnCur
|
||||
}
|
||||
}
|
||||
|
||||
public static func getPad(id: Int32) -> Pad? {
|
||||
_instance._pads[SDL_JoystickID(id)] ?? nil
|
||||
}
|
||||
|
||||
@inline(__always) public static var current: Pad? {
|
||||
getPad(id: Int32(_instance._firstID))
|
||||
}
|
||||
|
||||
//MARK: - Private
|
||||
|
||||
private static let _instance = GameController()
|
||||
public static var instance: GameController { _instance }
|
||||
|
||||
private var _pads = Dictionary<SDL_JoystickID, Pad>()
|
||||
private var _firstID: SDL_JoystickID = 0
|
||||
|
||||
internal func connectedEvent(id: SDL_JoystickID) {
|
||||
if _pads.keys.contains(id) {
|
||||
return
|
||||
}
|
||||
if let pad = Pad.open(joystickID: id) {
|
||||
printErr("Using gamepad #\(pad.instanceID), \"\(pad.name)\"")
|
||||
if self._firstID == 0 {
|
||||
self._firstID = id
|
||||
}
|
||||
self._pads[id] = pad
|
||||
}
|
||||
}
|
||||
|
||||
internal func removedEvent(id: SDL_JoystickID) {
|
||||
if let pad = self._pads.removeValue(forKey: id) {
|
||||
pad.close()
|
||||
}
|
||||
if id == _firstID {
|
||||
_firstID = _pads.keys.first ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
internal func buttonEvent(id: SDL_JoystickID, btn: SDL_GamepadButton, state: UInt8) {
|
||||
_pads[id]?.buttonEvent(btn, state == SDL_PRESSED)
|
||||
}
|
||||
|
||||
internal func axisEvent(id: SDL_JoystickID, axis: SDL_GamepadAxis, value: Int16) {
|
||||
_pads[id]?.axisEvent(axis, value)
|
||||
}
|
||||
|
||||
internal func newFrame() {
|
||||
for idx in _pads.values.indices {
|
||||
_pads.values[idx].newTick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//MARK: - Stick convenience functions
|
||||
|
||||
public extension GameController.Pad.State {
|
||||
var leftStick: SIMD2<Float> {
|
||||
.init(axis(.leftStickX), axis(.leftStickY))
|
||||
}
|
||||
var rightStick: SIMD2<Float> {
|
||||
.init(axis(.rightStickX), axis(.rightStickY))
|
||||
}
|
||||
}
|
||||
|
||||
public extension FloatingPoint {
|
||||
@inline(__always) internal func axisDeadzone(_ min: Self, _ max: Self) -> Self {
|
||||
let x = abs(self)
|
||||
return if x <= min { 0 } else if x >= max {
|
||||
.init(signOf: self, magnitudeOf: 1)
|
||||
} else {
|
||||
.init(signOf: self, magnitudeOf: x - min) / (max - min)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension SIMD2 where Scalar: FloatingPoint {
|
||||
func cardinalDeadzone(min: Scalar, max: Scalar) -> Self {
|
||||
.init(self.x.axisDeadzone(min, max), self.y.axisDeadzone(min, max))
|
||||
}
|
||||
|
||||
func radialDeadzone(min: Scalar, max: Scalar) -> Self {
|
||||
let magnitude = (x * x + y * y).squareRoot()
|
||||
if magnitude == .zero || magnitude < min {
|
||||
return .zero
|
||||
} else if magnitude > max {
|
||||
return self / magnitude
|
||||
} else {
|
||||
let rescale = (magnitude - min) / (max - min)
|
||||
return self / magnitude * rescale
|
||||
}
|
||||
}
|
||||
}
|
@ -43,6 +43,19 @@ public extension simd_float4x4 {
|
||||
.init(0, 0, 0, 1))
|
||||
}
|
||||
|
||||
@inline(__always) static func rotate(yawPitch yp: SIMD2<T>) -> Self { rotate(yaw: yp.x, pitch: yp.y) }
|
||||
|
||||
static func rotate(yaw ytheta: T, pitch xtheta: T) -> Self {
|
||||
let xc = cos(xtheta), xs = sin(xtheta)
|
||||
let yc = cos(ytheta), ys = sin(ytheta)
|
||||
|
||||
return .init(
|
||||
.init(yc, ys * xs, -ys * xc, 0),
|
||||
.init( 0, xc, xs, 0),
|
||||
.init(ys, yc * -xs, yc * xc, 0),
|
||||
.init( 0, 0, 0, 1))
|
||||
}
|
||||
|
||||
static func orthographic(left: T, right: T, bottom: T, top: T, near: T, far: T) -> Self {
|
||||
let
|
||||
invWidth = 1 / (right - left),
|
||||
|
@ -280,26 +280,30 @@ class Renderer {
|
||||
zfar: -1.0)
|
||||
}
|
||||
|
||||
var time: Float = 0 //FIXME: temp
|
||||
//FIXME: temp
|
||||
var camera = Camera()
|
||||
var time: Float = 0
|
||||
|
||||
func paint() throws {
|
||||
camera.update(deltaTime: 0.025)
|
||||
#if true
|
||||
let projection = matrix_float4x4.perspective(
|
||||
verticalFov: Float(90.0).radians,
|
||||
verticalFov: Float(60.0).radians,
|
||||
aspect: aspectRatio,
|
||||
near: 0.003,
|
||||
far: 4)
|
||||
far: 100)
|
||||
#else
|
||||
let projection = matrix_float4x4.orthographic(
|
||||
left: -aspectRatio, right: aspectRatio,
|
||||
bottom: -1, top: 1,
|
||||
near: 0, far: -4)
|
||||
#endif
|
||||
let view = matrix_float4x4.identity
|
||||
let view = camera.view
|
||||
let model: matrix_float4x4 =
|
||||
.translate(.init(0, sin(time * 0.5) * 0.75, -2)) *
|
||||
.scale(0.5) *
|
||||
.rotate(y: time)
|
||||
.translate(.init(0, -1, 0)) * .scale(.init(10, 0.1, 10))
|
||||
//.translate(.init(0, sin(time * 0.5) * 0.75, -2)) *
|
||||
//.scale(0.5) *
|
||||
//.rotate(y: time)
|
||||
|
||||
time += 0.025
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user