The Skung Cave commit

This commit is contained in:
2024-05-09 20:52:01 +10:00
parent 446c444728
commit 06046cd163
36 changed files with 11988 additions and 9252 deletions

View File

@ -23,23 +23,21 @@ public struct ApplicationConfiguration
let vSync: VSyncMode
let windowWidth: Int32
let windowHeight: Int32
let windowTitle: String
let bundle: Bundle
public init(resizable: Bool, vSync: VSyncMode, windowWidth: Int32, windowHeight: Int32, bundle: Bundle)
public init(resizable: Bool, vSync: VSyncMode, windowWidth: Int32, windowHeight: Int32, title: String, bundle: Bundle)
{
self.resizable = resizable
self.vSync = vSync
self.windowWidth = windowWidth
self.windowHeight = windowHeight
self.windowTitle = title
self.bundle = bundle
}
}
//FIXME: Wrap these
public var sdlPad: OpaquePointer? = nil
var joyId: Int32 = -1
public class Application
{
private let implementation: ApplicationImplementation
@ -52,14 +50,6 @@ public class Application
typealias ResizeEvent = (width: Int32, height: Int32)
private var resize: ResizeEvent? = nil
func openController(index: Int32) -> (pad: OpaquePointer, joy: Int32)?
{
guard let sdlPad = SDL_GameControllerOpen(index) else { return nil }
let joyId = SDL_JoystickGetDeviceInstanceID(index)
print("Using gamepad #\(joyId), \"\(String(cString: SDL_GameControllerName(sdlPad)))\"")
return (pad: sdlPad, joy: joyId)
}
public init(application: ApplicationImplementation, config: ApplicationConfiguration)
{
self.implementation = application
@ -77,7 +67,7 @@ public class Application
let winWidth: Int32 = configuration.windowWidth, winHeight: Int32 = configuration.windowHeight
var winFlags = SDL_WINDOW_RESIZABLE.rawValue | SDL_WINDOW_ALLOW_HIGHDPI.rawValue
if render is OpenGL { winFlags |= SDL_WINDOW_OPENGL.rawValue }
window = SDL_CreateWindow("Something", winPos, winPos, winWidth, winHeight, winFlags)
window = SDL_CreateWindow(configuration.windowTitle, winPos, winPos, winWidth, winHeight, winFlags)
guard window != nil else { fatalError("SDL_CreateWindow: \(String(cString: SDL_GetError()))") }
do
@ -91,15 +81,10 @@ public class Application
}
catch { fatalError("piss") }
for i in 0...SDL_NumJoysticks()
for idx in 0...SDL_NumJoysticks()
{
if SDL_IsGameController(i) != SDL_TRUE { continue }
if let open = openController(index: i)
{
sdlPad = open.pad
joyId = open.joy
break
}
if SDL_IsGameController(idx) != SDL_TRUE { continue }
Input.instance.padConnectedEvent(index: idx)
}
do
@ -131,13 +116,6 @@ public class Application
switch SDL_EventType(event.type)
{
case SDL_QUIT: break mainLoop
case SDL_KEYDOWN:
if event.key.repeat == 0
{
Input.instance.pressEvent(scan: event.key.keysym.scancode)
}
case SDL_KEYUP:
Input.instance.releaseEvent(scan: event.key.keysym.scancode)
case SDL_WINDOWEVENT:
switch SDL_WindowEventID(UInt32(event.window.event))
{
@ -145,28 +123,27 @@ public class Application
resize = displaySize
default: break
}
case SDL_KEYDOWN:
Input.instance.pressEvent(scan: event.key.keysym.scancode, repeat: event.key.repeat != 0)
case SDL_KEYUP:
Input.instance.releaseEvent(scan: event.key.keysym.scancode)
case SDL_CONTROLLERDEVICEADDED:
if sdlPad == nil && SDL_IsGameController(event.cdevice.which) == SDL_TRUE
{
if let open = openController(index: event.cdevice.which)
{
sdlPad = open.pad
joyId = open.joy
}
}
if SDL_IsGameController(event.cdevice.which) != SDL_TRUE { break }
Input.instance.padConnectedEvent(index: event.cdevice.which)
case SDL_CONTROLLERDEVICEREMOVED:
if event.cdevice.which == joyId
{
SDL_GameControllerClose(sdlPad)
sdlPad = nil
joyId = -1
}
Input.instance.padRemovedEvent(index: event.cdevice.which)
case SDL_CONTROLLERBUTTONDOWN:
Input.instance.padButtonPressEvent(id: event.cbutton.which, btn: event.cbutton.button)
case SDL_CONTROLLERBUTTONUP:
Input.instance.padButtonReleaseEvent(id: event.cbutton.which, btn: event.cbutton.button)
case SDL_CONTROLLERAXISMOTION:
Input.instance.padAxisEvent(id: event.caxis.which, axis: event.caxis.axis, value: event.caxis.value)
default: break
}
}
let time = SDL_GetPerformanceCounter();
let deltaTime = Float(Double(time - prevTime) * timeMul)
let deltaTime = Float(Double(time &- prevTime) * timeMul)
prevTime = time
implementation.update(deltaTime: min(deltaTime, 1.0 / 15.0))

View File

@ -35,7 +35,7 @@ extension ContentManager
loaders[ext] = loader
}
public mutating func create(mesh: Mesh) throws -> RenderMesh
public mutating func create<V: Vertex>(mesh: Mesh<V>) throws -> RenderMesh<V>
{
let rendMesh = try renderer.createMesh(mesh: mesh)
resources.append(rendMesh)
@ -72,13 +72,18 @@ extension ContentManager
{
let resourceUrl = try getResource(path)
let loader = loaders[resourceUrl.pathExtension]
let loader = loaders[resourceUrl.pathExtension.lowercased()]
guard let resource = loader?.load(url: resourceUrl)
else { throw ContentError.loadFailure }
if T.self == Mesh.self
if T.self == Mesh<VertexPositionNormalTexcoord>.self
{
guard let mesh = resource as? Mesh else { throw ContentError.loadFailure }
guard let mesh = resource as? Mesh<VertexPositionNormalTexcoord> else { throw ContentError.loadFailure }
return mesh as! T
}
if T.self == Mesh<VertexPositionNormalColourTexcoord>.self
{
guard let mesh = resource as? Mesh<VertexPositionNormalTexcoord> else { throw ContentError.loadFailure }
return mesh as! T
}
if T.self == Image.self
@ -100,13 +105,18 @@ extension ContentManager
{
let resourceUrl = try getResource(path)
let loader = loaders[resourceUrl.pathExtension]
let loader = loaders[resourceUrl.pathExtension.lowercased()]
guard let resource = loader?.load(url: resourceUrl)
else { throw ContentError.loadFailure }
if T.self == Mesh.self
if T.self == Mesh<VertexPositionNormalTexcoord>.self
{
guard let mesh = resource as? Mesh else { throw ContentError.loadFailure }
guard let mesh = resource as? Mesh<VertexPositionNormalTexcoord> else { throw ContentError.loadFailure }
return mesh as! T
}
if T.self == Mesh<VertexPositionNormalColourTexcoord>.self
{
guard let mesh = resource as? Mesh<VertexPositionNormalTexcoord> else { throw ContentError.loadFailure }
return mesh as! T
}
if T.self == Image.self
@ -131,9 +141,15 @@ extension ContentManager
guard let resource = loader?.load(url: resourceUrl)
else { throw ContentError.loadFailure }
if T.self == RenderMesh.self
if T.self == RenderMesh<VertexPositionNormalTexcoord>.self
{
guard let mesh = resource as? Mesh else { throw ContentError.loadFailure }
guard let mesh = resource as? Mesh<VertexPositionNormalTexcoord> else { throw ContentError.loadFailure }
let renderResource = try self.create(mesh: mesh)
return renderResource as! T
}
if T.self == RenderMesh<VertexPositionNormalColourTexcoord>.self
{
guard let mesh = resource as? Mesh<VertexPositionNormalColourTexcoord> else { throw ContentError.loadFailure }
let renderResource = try self.create(mesh: mesh)
return renderResource as! T
}
@ -144,9 +160,13 @@ extension ContentManager
{
for resource in resources.reversed()
{
if resource is RenderMesh
if resource is RenderMesh<VertexPositionNormalTexcoord>
{
renderer.deleteMesh(resource as! RenderMesh)
renderer.deleteMesh(resource as! RenderMesh<VertexPositionNormalTexcoord>)
}
else if resource is RenderMesh<VertexPositionNormalColourTexcoord>
{
renderer.deleteMesh(resource as! RenderMesh<VertexPositionNormalColourTexcoord>)
}
else if resource is RenderTexture2D
{

View File

@ -6,4 +6,5 @@ public protocol LoaderProtocol
associatedtype T: Resource
func load(url: URL) -> T?
func load(url: URL, content: inout ContentManager) -> T?
}

View File

@ -1,54 +1,86 @@
import OrderedCollections
public struct Model: Resource
public struct Model<T: Vertex>: Resource
{
public let meshes: [Mesh]
public let meshes: [Mesh<T>]
}
public struct Mesh: Resource
public struct Mesh<T: Vertex>: Resource
{
public typealias Index = UInt16
public struct Vertex: Equatable
{
public let position: Vec3f
public let normal: Vec3f
public let texCoord: Vec2f
public init(position: Vec3f, normal: Vec3f, texCoord: Vec2f)
{
self.position = position
self.normal = normal
self.texCoord = texCoord
}
}
public struct SubMesh
{
public let start, length: Int
public let material: Int //hack
public init(start: Int, length: Int)
public init(start: Int, length: Int, material: Int = -1)
{
self.start = start
self.length = length
self.material = material
}
}
public let vertices: [Vertex]
public let vertices: [T]
public let indices: [Index]
public let subMeshes: OrderedDictionary<String, SubMesh>
public let materials: [Material] // hack
}
public extension Mesh
{
static let empty = Self(vertices: .init(), indices: .init(), subMeshes: .init())
static var empty: Self { Self(vertices: .init(), indices: .init(), subMeshes: .init(), materials: .init()) }
init(vertices: [Vertex], indices: [Index])
init(vertices: [T], indices: [Index])
{
self.init(
vertices: vertices,
indices: indices,
subMeshes: .init())
subMeshes: .init(),
materials: .init())
}
init(vertices: [T], indices: [Index], subMeshes: OrderedDictionary<String, SubMesh>)
{
self.init(
vertices: vertices,
indices: indices,
subMeshes: subMeshes,
materials: .init())
}
}
public protocol Vertex: Equatable {}
public struct VertexPositionNormalTexcoord: Vertex
{
public let position: Vec3f
public let normal: Vec3f
public let texCoord: Vec2f
public init(position: Vec3f, normal: Vec3f, texCoord: Vec2f)
{
self.position = position
self.normal = normal
self.texCoord = texCoord
}
}
public struct VertexPositionNormalColourTexcoord: Vertex
{
public let position: Vec3f
public let normal: Vec3f
public let colour: Colour
public let texCoord: Vec2f
public init(position: Vec3f, colour: Colour, normal: Vec3f, texCoord: Vec2f)
{
self.position = position
self.colour = colour
self.normal = normal
self.texCoord = texCoord
}
}

View File

@ -1,15 +1,18 @@
import Foundation
import OrderedCollections
public struct ObjModel
{
public var positions = [Vec3f]()
public var colours = [Vec3f]()
public var normals = [Vec3f]()
public var texCoords = [Vec2f]()
public var objects = Dictionary<String, Object>()
public struct Object { public var faces = [Face]() }
public var objects = OrderedDictionary<String, Object>()
public var materials = Dictionary<String, ObjMaterial>()
public struct Object { public var meshes = [Mesh]() }
public struct Mesh { public var material = "", faces = [Face]() }
public struct Index { public let p: Int, n: Int, t: Int }
public enum Face
{

View File

@ -1,155 +0,0 @@
import SDL2
public class Input
{
private static let _instance = Input()
public static var instance: Input { _instance }
//private let _UP: UInt8 = 0
//private let _PRESS: UInt8 = 1
//private let _DOWN: UInt8 = 2
//private let _RELEASE: UInt8 = 3
private static let _UP: UInt8 = 0b000
private static let _DOWN: UInt8 = 0b010
private static let _IMPULSE: UInt8 = 0b001
private static let _REPEAT: UInt8 = 0b100
private static let _PRESS: UInt8 = _DOWN | _IMPULSE
private static let _RELEASE: UInt8 = _UP | _IMPULSE
private var _keys = [UInt8](repeating: _UP, count: Int(SDL_NUM_SCANCODES.rawValue))
private init() {}
internal func newTick()
{
//_keys = _keys.map({ i in (i & 0x1) == 0x1 ? ((i &+ 1) & 0x3) : i })
_keys = _keys.map({ $0 & ~Input._IMPULSE })
//for i in 0..<Int(SDL_NUM_SCANCODES.rawValue)
//{
// _keys[i] &= ~0x1
//}
}
internal func pressEvent(scan: SDL_Scancode)
{
_keys[Int(scan.rawValue)] = Input._PRESS
}
internal func releaseEvent(scan: SDL_Scancode)
{
_keys[Int(scan.rawValue)] = Input._RELEASE
}
public func keyDown(_ key: Key) -> Bool
{
//SDL_GetKeyboardState(nil).withMemoryRebound(to: UInt8.self, capacity: Int(SDL_NUM_SCANCODES.rawValue))
//{ state in
// state[Int(key.sdlScancode.rawValue)] != 0
//}
_keys[Int(key.sdlScancode.rawValue)] & Input._DOWN == Input._DOWN
}
public func keyPressed(_ key: Key) -> Bool
{
_keys[Int(key.sdlScancode.rawValue)] == Input._PRESS
}
public func keyReleased(_ key: Key) -> Bool
{
_keys[Int(key.sdlKeycode.rawValue)] == Input._RELEASE
}
}
public enum Key
{
case a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z
case right, left, down, up
}
extension Key
{
internal var sdlKeycode: SDL_KeyCode
{
switch self
{
case .a: SDLK_a
case .b: SDLK_a
case .c: SDLK_a
case .d: SDLK_a
case .e: SDLK_a
case .f: SDLK_a
case .g: SDLK_a
case .h: SDLK_a
case .i: SDLK_a
case .j: SDLK_a
case .k: SDLK_a
case .l: SDLK_a
case .m: SDLK_a
case .n: SDLK_a
case .o: SDLK_a
case .p: SDLK_a
case .q: SDLK_a
case .r: SDLK_a
case .s: SDLK_a
case .t: SDLK_a
case .u: SDLK_a
case .v: SDLK_a
case .w: SDLK_a
case .x: SDLK_a
case .y: SDLK_a
case .z: SDLK_a
case .left: SDLK_LEFT
case .right: SDLK_RIGHT
case .up: SDLK_UP
case .down: SDLK_DOWN
}
}
internal var sdlScancode: SDL_Scancode
{
switch self
{
case .a: SDL_SCANCODE_A
case .b: SDL_SCANCODE_B
case .c: SDL_SCANCODE_C
case .d: SDL_SCANCODE_D
case .e: SDL_SCANCODE_E
case .f: SDL_SCANCODE_F
case .g: SDL_SCANCODE_G
case .h: SDL_SCANCODE_H
case .i: SDL_SCANCODE_I
case .j: SDL_SCANCODE_J
case .k: SDL_SCANCODE_K
case .l: SDL_SCANCODE_L
case .m: SDL_SCANCODE_M
case .n: SDL_SCANCODE_N
case .o: SDL_SCANCODE_O
case .p: SDL_SCANCODE_P
case .q: SDL_SCANCODE_Q
case .r: SDL_SCANCODE_R
case .s: SDL_SCANCODE_S
case .t: SDL_SCANCODE_T
case .u: SDL_SCANCODE_U
case .v: SDL_SCANCODE_V
case .w: SDL_SCANCODE_W
case .x: SDL_SCANCODE_X
case .y: SDL_SCANCODE_Y
case .z: SDL_SCANCODE_Z
case .left: SDL_SCANCODE_LEFT
case .right: SDL_SCANCODE_RIGHT
case .up: SDL_SCANCODE_UP
case .down: SDL_SCANCODE_DOWN
}
}
}
extension SDL_KeyCode
{
internal init(scancode: SDL_Scancode)
{
self.init(scancode.rawValue | UInt32(SDLK_SCANCODE_MASK))
}
}

View File

@ -0,0 +1,157 @@
import Foundation
import SDL2
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 _sdlPad: OpaquePointer
internal init(pad: OpaquePointer)
{
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 .west: SDL_CONTROLLER_BUTTON_Y
case .north: 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
}
}
}

View File

@ -0,0 +1,62 @@
import SDL2
public class Input
{
private static let _instance = Input()
public static var instance: Input { _instance }
public var keyboard: Keyboard { _keyboard }
public func getPad(id: Int) -> GamePad? { _pads[Int32(id)] }
public var firstPadID: Int? { _firstJoyID >= 0 ? Int(_firstJoyID) : nil }
private var _keyboard = Keyboard()
private var _pads = Dictionary<Int32, GamePad>()
private var _firstJoyID: Int32 = -1
private init() {}
internal func pressEvent(scan: SDL_Scancode, repeat r: Bool) { keyboard.pressEvent(scan: scan, repeat: r) }
internal func releaseEvent(scan: SDL_Scancode) { keyboard.releaseEvent(scan: scan) }
internal func padConnectedEvent(index: Int32)
{
guard let sdlPad = SDL_GameControllerOpen(index)
else { return }
let id = SDL_JoystickGetDeviceInstanceID(index)
_pads[id] = GamePad(pad: sdlPad)
print("Using gamepad #\(id), \"\(String(cString: SDL_GameControllerName(sdlPad)))\"")
if _firstJoyID < 0 { _firstJoyID = id }
}
internal func padRemovedEvent(index: Int32)
{
let id = SDL_JoystickGetDeviceInstanceID(index)
_pads.removeValue(forKey: id)
if id == _firstJoyID
{
_firstJoyID = _pads.keys.first ?? -1
}
}
internal func padButtonPressEvent(id: Int32, btn: UInt8)
{
_pads[id]?.buttonPressEvent(btn)
}
internal func padButtonReleaseEvent(id: Int32, btn: UInt8)
{
_pads[id]?.buttonReleaseEvent(btn)
}
internal func padAxisEvent(id: Int32, axis: UInt8, value: Int16)
{
_pads[id]?.axisEvent(axis, value)
}
internal func newTick()
{
_keyboard.newTick()
for pad in _pads.values { pad.newTick() }
}
}

View File

@ -0,0 +1,137 @@
import SDL2
public class Keyboard
{
public enum Keys
{
case a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z
case right, left, down, up
}
public func keyDown(_ key: Keys) -> Bool
{
_keys[Int(key.sdlScancode.rawValue)] & Self._DOWN == Self._DOWN
}
public func keyPressed(_ key: Keys, repeat rep: Bool = false) -> Bool
{
var state = _keys[Int(key.sdlScancode.rawValue)]
if rep { state &= ~Self._REPEAT }
return state == Self._PRESS
}
public func keyReleased(_ key: Keys) -> Bool
{
_keys[Int(key.sdlKeycode.rawValue)] == Self._RELEASE
}
private static let _UP = UInt8(0b000), _DOWN = UInt8(0b010), _IMPULSE = UInt8(0b001)
private static let _REPEAT = UInt8(0b100)
private static let _PRESS: UInt8 = _DOWN | _IMPULSE
private static let _RELEASE: UInt8 = _UP | _IMPULSE
private var _keys = [UInt8](repeating: _UP, count: Int(SDL_NUM_SCANCODES.rawValue))
internal func newTick()
{
_keys = _keys.map({ $0 & ~(Self._IMPULSE | Self._REPEAT) })
}
internal func pressEvent(scan: SDL_Scancode, repeat rep: Bool)
{
var newState = Self._PRESS
if rep { newState |= Self._REPEAT }
_keys[Int(scan.rawValue)] = newState
}
internal func releaseEvent(scan: SDL_Scancode)
{
_keys[Int(scan.rawValue)] = Self._RELEASE
}
}
extension Keyboard.Keys
{
internal var sdlKeycode: SDL_KeyCode
{
switch self
{
case .a: SDLK_a
case .b: SDLK_b
case .c: SDLK_c
case .d: SDLK_d
case .e: SDLK_e
case .f: SDLK_f
case .g: SDLK_g
case .h: SDLK_h
case .i: SDLK_i
case .j: SDLK_j
case .k: SDLK_k
case .l: SDLK_l
case .m: SDLK_m
case .n: SDLK_n
case .o: SDLK_o
case .p: SDLK_p
case .q: SDLK_q
case .r: SDLK_r
case .s: SDLK_s
case .t: SDLK_t
case .u: SDLK_u
case .v: SDLK_v
case .w: SDLK_w
case .x: SDLK_x
case .y: SDLK_y
case .z: SDLK_z
case .left: SDLK_LEFT
case .right: SDLK_RIGHT
case .up: SDLK_UP
case .down: SDLK_DOWN
}
}
internal var sdlScancode: SDL_Scancode
{
switch self
{
case .a: SDL_SCANCODE_A
case .b: SDL_SCANCODE_B
case .c: SDL_SCANCODE_C
case .d: SDL_SCANCODE_D
case .e: SDL_SCANCODE_E
case .f: SDL_SCANCODE_F
case .g: SDL_SCANCODE_G
case .h: SDL_SCANCODE_H
case .i: SDL_SCANCODE_I
case .j: SDL_SCANCODE_J
case .k: SDL_SCANCODE_K
case .l: SDL_SCANCODE_L
case .m: SDL_SCANCODE_M
case .n: SDL_SCANCODE_N
case .o: SDL_SCANCODE_O
case .p: SDL_SCANCODE_P
case .q: SDL_SCANCODE_Q
case .r: SDL_SCANCODE_R
case .s: SDL_SCANCODE_S
case .t: SDL_SCANCODE_T
case .u: SDL_SCANCODE_U
case .v: SDL_SCANCODE_V
case .w: SDL_SCANCODE_W
case .x: SDL_SCANCODE_X
case .y: SDL_SCANCODE_Y
case .z: SDL_SCANCODE_Z
case .left: SDL_SCANCODE_LEFT
case .right: SDL_SCANCODE_RIGHT
case .up: SDL_SCANCODE_UP
case .down: SDL_SCANCODE_DOWN
}
}
}
extension SDL_KeyCode
{
internal init(scancode: SDL_Scancode)
{
self.init(scancode.rawValue | UInt32(SDLK_SCANCODE_MASK))
}
}

View File

@ -4,7 +4,7 @@ import OrderedCollections
class G3DbLoader: LoaderProtocol
{
typealias T = Mesh
typealias T = Mesh<VertexPositionNormalTexcoord>
func load(url: URL) -> T?
{
@ -12,6 +12,8 @@ class G3DbLoader: LoaderProtocol
else { return Optional.none }
return try? reader.read()
}
func load(url: URL, content: inout ContentManager) -> T? { return load(url: url) }
}
@ -27,15 +29,15 @@ fileprivate struct G3DbReader
self.reader = UBJsonReader(file: file)
}
mutating func read() throws -> Mesh
mutating func read() throws -> Mesh<VertexPositionNormalTexcoord>
{
let model = try readModel()
let mesh = model.meshes[0]
var vertices = [Mesh.Vertex]()
var vertices = [VertexPositionNormalTexcoord]()
var indices = [UInt16]()
var subMeshes = OrderedDictionary<String, Mesh.SubMesh>()
var subMeshes = OrderedDictionary<String, Mesh<VertexPositionNormalTexcoord>.SubMesh>()
let attributeSize =
{ (attrib: G3DAttribute) in

View File

@ -16,6 +16,8 @@ struct NSImageLoader: LoaderProtocol
return image
}
func load(url: URL, content: inout ContentManager) -> T? { return load(url: url) }
static func loadImage(url: URL) throws -> Image
{
try autoreleasepool

View File

@ -2,10 +2,9 @@ import Foundation
import OrderedCollections
/*
extension ObjMaterial
{
func convert() -> Material
func convert(content: UnsafeMutablePointer<ContentManager>? = nil) -> Material
{
var m = Material()
m.diffuse = self.diffuse.setAlpha(self.alpha)
@ -14,37 +13,61 @@ extension ObjMaterial
m.specular = self.specular
m.specularExp = self.specularExp
}
if let content = content
{
if let albedo = self.diffuseMap
{
let filter = albedo.flags.contains(.blendHoriz) || albedo.flags.contains(.blendVert)
m.texture = (try? content.pointee.load(albedo.path,
params: Texture2DParameters(
minFilter: filter ? .linear : .point,
magFilter: filter ? .linear : .point,
wrapMode: albedo.flags.contains(.clamp) ? .clampBorder : .repeating,
mipMode: .linear)))?.id ?? RenderTexture2D.empty
}
}
return m
}
}
*/
public struct ObjLoader: LoaderProtocol
{
public typealias T = Mesh
typealias V = VertexPositionNormalColourTexcoord
public typealias T = Mesh<VertexPositionNormalColourTexcoord>
public init() {}
public func load(url: URL) -> T?
{
try? Self.read(url: url)
return try? Self.read(model: try ObjReader.read(url: url), content: nil)
}
private static func read(url: URL) throws -> Mesh
public func load(url: URL, content: inout ContentManager) -> T?
{
return try read(model: try ObjReader.read(url: url))
return try? Self.read(model: try ObjReader.read(url: url), content: &content)
}
public static func read(model: ObjModel) throws -> Mesh
public static func read(model: ObjModel, content: UnsafeMutablePointer<ContentManager>?) throws -> T
{
var subMeshes: OrderedDictionary<String, Mesh.SubMesh> = .init()
var vertices = [Mesh.Vertex]()
var subMeshes = OrderedDictionary<String, T.SubMesh>()
var materials = OrderedDictionary<String, Material>()
var vertices = [V]()
var indices = [UInt16]()
for (key, mtl) in model.materials
{
materials[key] = mtl.convert(content: content)
}
let readIndex =
{ (v: ObjModel.Index) -> UInt16 in
let vertex = Mesh.Vertex(
let colour = model.colours.isEmpty ? .white : Colour(
r: model.colours[v.p][0],
g: model.colours[v.p][1],
b: model.colours[v.p][2]).linear
let vertex = V(
position: model.positions[v.p],
colour: colour,
normal: model.normals[v.n],
texCoord: model.texCoords[v.t])
if let index = vertices.firstIndex(of: vertex)
@ -61,32 +84,39 @@ public struct ObjLoader: LoaderProtocol
}
}
for mesh in model.objects
for object in model.objects.filter({ $0.key != "Collision3D" })
{
let start = indices.count
for face: ObjModel.Face in mesh.value.faces
var id = 0
for mesh: ObjModel.Mesh in object.value.meshes
{
switch face
let start = indices.count
for face: ObjModel.Face in mesh.faces
{
case .triangle(let v1, let v2, let v3):
for v in [ v1, v2, v3 ] { _ = readIndex(v) }
case .quad(let v1, let v2, let v3, let v4):
let n1 = readIndex(v1)
_ = readIndex(v2)
indices.append(readIndex(v3))
_ = readIndex(v4)
indices.append(n1)
case .ngon(_): fallthrough
default: break
switch face
{
case .triangle(let v1, let v2, let v3):
for v in [ v1, v2, v3 ] { _ = readIndex(v) }
case .quad(let v1, let v2, let v3, let v4):
let n1 = readIndex(v1)
_ = readIndex(v2)
indices.append(readIndex(v3))
_ = readIndex(v4)
indices.append(n1)
case .ngon(_): fallthrough
default: break
}
}
}
let length = indices.count - start
if length > 0
{
subMeshes[mesh.key] = .init(start: start, length: length)
let length = indices.count - start
if length > 0
{
subMeshes["\(object.key)_\(id)"] = .init(
start: start, length: length,
material: materials.index(forKey: mesh.material) ?? -1)
}
id += 1
}
}
return Mesh(vertices: vertices, indices: indices, subMeshes: subMeshes)
return Mesh(vertices: vertices, indices: indices, subMeshes: subMeshes, materials: materials.values.map { $0 })
}
}

View File

@ -8,25 +8,46 @@ public struct ObjReader
var file = try ObjDocumentReader(filePath: url)
var model = ObjModel()
var materials = Dictionary<String, ObjMaterial>()
var name: String? = nil
var object = ObjModel.Object()
var mesh = ObjModel.Mesh()
file.string(tag: "mtllib") { s in
let mats = try ObjMtlLoader.read(url: url.deletingLastPathComponent().appendingPathComponent(s[0]))
materials.merge(mats, uniquingKeysWith: { (_, new) in new } )
model.materials.merge(mats, uniquingKeysWith: { (_, new) in new } )
}
file.string(tag: "o", count: 1) { s in
if !object.faces.isEmpty
if !mesh.faces.isEmpty { object.meshes.append(mesh) }
if !object.meshes.isEmpty
{
model.objects[name!] = object
object = .init()
mesh = .init()
}
name = String(s[0])
}
file.float(tag: "v", count: 3) { v in model.positions.append(Vec3f(v[0], v[1], v[2])) }
file.float(tag: "v") { v in
if v.count == 6
{
if model.colours.isEmpty && !model.positions.isEmpty
{
for _ in 0..<model.positions.count
{
model.colours.append(.one)
}
}
model.colours.append(Vec3f(v[3], v[4], v[5]))
}
else if !model.colours.isEmpty && v.count == 3
{
model.colours.append(.one)
}
else if v.count != 3 { throw ObjLoaderError.badTagParameters }
model.positions.append(Vec3f(v[0], v[1], v[2]))
}
file.float(tag: "vn", count: 3) { v in model.normals.append(Vec3f(v[0], v[1], v[2])) }
file.float(tag: "vt", count: 2) { v in model.texCoords.append(Vec2f(v[0], v[1])) }
file.int(tag: "l") { i in mesh.faces.append(.line(p1: i[0], p2: i[1])) }
file.raw(tag: "f") { raw in
let readIndex =
{ (raw: Substring) in
@ -40,14 +61,14 @@ public struct ObjReader
if raw.count == 3
{
for raw in raw { _ = try readIndex(raw) }
object.faces.append(.triangle(
mesh.faces.append(.triangle(
v1: try readIndex(raw[raw.startIndex]),
v2: try readIndex(raw[raw.startIndex + 1]),
v3: try readIndex(raw[raw.startIndex + 2])))
}
else if raw.count == 4
{
object.faces.append(.quad(
mesh.faces.append(.quad(
v1: try readIndex(raw[raw.startIndex]),
v2: try readIndex(raw[raw.startIndex + 1]),
v3: try readIndex(raw[raw.startIndex + 2]),
@ -55,17 +76,25 @@ public struct ObjReader
}
else if raw.count >= 5
{
object.faces.append(.ngon(try raw.map { try readIndex($0) }))
mesh.faces.append(.ngon(try raw.map { try readIndex($0) }))
}
else { throw ObjLoaderError.badTagParameters }
}
file.int(tag: "l") { i in object.faces.append(.line(p1: i[0], p2: i[1])) }
file.string(tag: "usemtl", count: 1) { s in
if !mesh.faces.isEmpty
{
object.meshes.append(mesh)
mesh = .init()
}
mesh.material = s[0]
}
// file.int(tag: "s", count: 1) { i in } //TODO: Smoothing groups
try file.read()
if !object.faces.isEmpty
{
model.objects[name!] = object
}
if !mesh.faces.isEmpty { object.meshes.append(mesh) }
if !object.meshes.isEmpty { model.objects[name!] = object }
return model
}
}

View File

@ -39,6 +39,8 @@ public extension SIMD2 where Scalar: FloatingPoint
return Self(self.x * invX + b.x * x, self.y * invX + b.y * x)
}
@inline(__always) func distance(_ b: Self) -> Scalar { return (b - self).len }
func cardinalDeadzone(min: Scalar, max: Scalar) -> Self
{
Self(self.x.axisDeadzone(min, max), self.y.axisDeadzone(min, max))

View File

@ -76,13 +76,19 @@ class OpenGL: Renderer
}
}
var wireframe: Bool
{
get { state.polygonMode == .line }
set(mode) { state.polygonMode = mode ? .line : .fill }
}
func setVsync(mode: VSyncMode) throws
{
guard SDL_GL_SetSwapInterval(mode.sdlInterval) == 0
else { throw RendererError.sdlError(message: "SDL_GL_SetSwapInterval: \(String(cString: SDL_GetError()))") }
}
func createMesh(mesh: Mesh) throws -> RenderMesh
func createMesh<V: Vertex>(mesh: Mesh<V>) throws -> RenderMesh<V>
{
var buffers = [GLuint](repeating: 0, count: 2)
buffers.withUnsafeMutableBufferPointer
@ -94,35 +100,36 @@ class OpenGL: Renderer
state.elementArrayBuffer = buffers[1]
glBufferData(GLenum(GL_ARRAY_BUFFER),
MemoryLayout<Mesh.Vertex>.stride * mesh.vertices.count,
MemoryLayout<V>.stride * mesh.vertices.count,
mesh.vertices, GLenum(GL_STATIC_DRAW))
glBufferData(GLenum(GL_ELEMENT_ARRAY_BUFFER),
MemoryLayout<Mesh.Index>.stride * mesh.indices.count,
MemoryLayout<V>.stride * mesh.indices.count,
mesh.indices, GLenum(GL_STATIC_DRAW))
state.elementArrayBuffer = OpenGLState.defaultBuffer
state.arrayBuffer = OpenGLState.defaultBuffer
var subMeshes = [Mesh.SubMesh]()
var subMeshes = [Mesh<V>.SubMesh]()
if mesh.subMeshes.isEmpty
{
subMeshes.append(Mesh.SubMesh(start: 0, length: mesh.indices.count))
subMeshes.append(Mesh<V>.SubMesh(start: 0, length: mesh.indices.count))
}
else
{
for subMesh in mesh.subMeshes
{
if ["Collision", "Collision3D"].contains(subMesh.key) { continue }
subMeshes.append(Mesh.SubMesh(
subMeshes.append(Mesh<V>.SubMesh(
start: subMesh.value.start,
length: subMesh.value.length))
length: subMesh.value.length,
material: subMesh.value.material))
}
}
return RenderMesh(
vbo: Int(buffers[0]),
ibo: Int(buffers[1]),
subMeshes: subMeshes)
subMeshes: subMeshes,
materials: mesh.materials)
}
func createTexture(data: UnsafeRawPointer, width: Int, height: Int) throws -> RenderTexture2D
@ -172,7 +179,7 @@ class OpenGL: Renderer
}
func deleteMesh(_ mesh: RenderMesh)
func deleteMesh<V: Vertex>(_ mesh: RenderMesh<V>)
{
var buffers = [GLuint](repeating: 0, count: 2)
buffers[0] = GLuint(mesh.iboHnd)
@ -208,42 +215,64 @@ class OpenGL: Renderer
}
private func draw(subMesh: Mesh.SubMesh)
private func draw<V: Vertex>(subMesh: Mesh<V>.SubMesh)
{
glDrawElements(
GLenum(GL_TRIANGLES),
GLsizei(subMesh.length),
GLenum(GL_UNSIGNED_SHORT),
.init(bitPattern: MemoryLayout<Mesh.Index>.stride * subMesh.start));
.init(bitPattern: MemoryLayout<Mesh<V>.Index>.stride * subMesh.start));
}
private func draw(mesh: RenderMesh)
private func bindMesh<V: Vertex>(mesh: RenderMesh<V>)
{
state.enableClient([ .vertex, .normal, .textureCoord ])
state.enableClient([ .vertex, .colour, .normal, .textureCoord ])
state.arrayBuffer = UInt32(mesh.vboHnd)
state.elementArrayBuffer = UInt32(mesh.iboHnd)
let stride = GLsizei(MemoryLayout<Mesh.Vertex>.stride)
glVertexPointer(3, GLenum(GL_FLOAT), stride,
UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \Mesh.Vertex.position)!))
glNormalPointer(GLenum(GL_FLOAT), stride,
UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \Mesh.Vertex.normal)!))
glTexCoordPointer(2, GLenum(GL_FLOAT), stride,
UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \Mesh.Vertex.texCoord)!))
for subMesh in mesh.subMeshes
let stride = GLsizei(MemoryLayout<V>.stride)
if V.self == VertexPositionNormalTexcoord.self
{
draw(subMesh: subMesh)
glVertexPointer(3, GLenum(GL_FLOAT), stride,
UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \VertexPositionNormalTexcoord.position)!))
glNormalPointer(GLenum(GL_FLOAT), stride,
UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \VertexPositionNormalTexcoord.normal)!))
glTexCoordPointer(2, GLenum(GL_FLOAT), stride,
UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \VertexPositionNormalTexcoord.texCoord)!))
}
else if V.self == VertexPositionNormalColourTexcoord.self
{
glVertexPointer(3, GLenum(GL_FLOAT), stride,
UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \VertexPositionNormalColourTexcoord.position)!))
glColorPointer(4, GLenum(GL_FLOAT), stride,
UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \VertexPositionNormalColourTexcoord.colour)!))
glNormalPointer(GLenum(GL_FLOAT), stride,
UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \VertexPositionNormalColourTexcoord.normal)!))
glTexCoordPointer(2, GLenum(GL_FLOAT), stride,
UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \VertexPositionNormalColourTexcoord.texCoord)!))
}
}
private func unbindMesh()
{
state.elementArrayBuffer = OpenGLState.defaultBuffer
state.arrayBuffer = OpenGLState.defaultBuffer
state.disableClient([ .vertex, .normal, .textureCoord ])
}
func draw(mesh: RenderMesh, model: Mat4f, environment env: Environment)
private func draw<V: Vertex>(mesh: RenderMesh<V>)
{
bindMesh(mesh: mesh)
for subMesh in mesh.subMeshes
{
draw(subMesh: subMesh)
}
unbindMesh()
}
func draw<V: Vertex>(mesh: RenderMesh<V>, model: Mat4f, environment env: Environment)
{
if (mesh.subMeshes.isEmpty) { return }
@ -255,7 +284,7 @@ class OpenGL: Renderer
glPopMatrix()
}
func draw(mesh: RenderMesh, environment env: Environment)
func draw<V: Vertex>(mesh: RenderMesh<V>, environment env: Environment)
{
if (mesh.subMeshes.isEmpty) { return }
@ -263,6 +292,14 @@ class OpenGL: Renderer
draw(mesh: mesh)
}
func draw<V: Vertex>(mesh: RenderMesh<V>, subMesh: Mesh<V>.SubMesh, environment env: Environment)
{
applyEnvironment(env)
bindMesh(mesh: mesh)
draw(subMesh: subMesh)
unbindMesh()
}
private func loadMatrix(_ matrix: Mat4f)
{
withUnsafePointer(to: matrix.columns)

View File

@ -793,4 +793,26 @@ struct OpenGLState
}
}
}
enum PolygonMode { case point, line, fill }
private var _polygonMode = PolygonMode.fill
var polygonMode: PolygonMode
{
get { _polygonMode }
set(newMode)
{
if newMode != _polygonMode
{
let modeEnum = switch newMode
{
case .point: GL_POINT
case .line: GL_LINE
case .fill: GL_FILL
}
glPolygonMode(GLenum(GL_FRONT_AND_BACK), GLenum(modeEnum));
_polygonMode = newMode
}
}
}
}

View File

@ -11,15 +11,16 @@ public protocol Renderer
func resize(width: Int32, height: Int32)
var clearColour: Colour { get set }
var wireframe: Bool { get set }
func setVsync(mode: VSyncMode) throws
func createMesh(mesh: Mesh) throws -> RenderMesh
func createMesh<V: Vertex>(mesh: Mesh<V>) throws -> RenderMesh<V>
func createTexture(data: UnsafeRawPointer, width: Int, height: Int) throws -> RenderTexture2D
func createTexture(data: UnsafeRawPointer, width: Int, height: Int,
filter: FilterMode, mipMode: MipMode) throws -> RenderTexture2D
func deleteMesh(_ mesh: RenderMesh)
func deleteMesh<V: Vertex>(_ mesh: RenderMesh<V>)
func deleteTexture(_ texture: RenderTexture2D)
func setProjection(matrix: Mat4f)
@ -27,8 +28,9 @@ public protocol Renderer
func setMaterial(_ mat: Material)
func draw(mesh: RenderMesh, model: Mat4f, environment: Environment)
func draw(mesh: RenderMesh, environment: Environment)
func draw<V: Vertex>(mesh: RenderMesh<V>, model: Mat4f, environment: Environment)
func draw<V: Vertex>(mesh: RenderMesh<V>, environment: Environment)
func draw<V: Vertex>(mesh: RenderMesh<V>, subMesh: Mesh<V>.SubMesh, environment: Environment)
func drawGizmos(lines: [Line])
}
@ -76,9 +78,9 @@ public protocol RendererResource
var isValid: Bool { get }
}
public struct RenderMesh: RendererResource
public struct RenderMesh<V: Vertex>: RendererResource
{
public typealias T = Mesh
public typealias T = Mesh<V>
public static var empty: RenderMesh { .init() }
@ -86,7 +88,8 @@ public struct RenderMesh: RendererResource
private let _valid: Bool;
let vboHnd: Int, iboHnd: Int
let subMeshes: [Mesh.SubMesh]
public let subMeshes: [Mesh<V>.SubMesh]
public let materials: [Material] // HACK !!!!
private init()
{
@ -94,14 +97,16 @@ public struct RenderMesh: RendererResource
self.vboHnd = 0
self.iboHnd = 0
self.subMeshes = []
self.materials = []
}
init(vbo: Int, ibo: Int, subMeshes: [Mesh.SubMesh])
init(vbo: Int, ibo: Int, subMeshes: [Mesh<V>.SubMesh], materials: [Material] = .init())
{
self._valid = true
self.vboHnd = vbo
self.iboHnd = ibo
self.subMeshes = subMeshes
self.materials = materials
}
}