The Skung Cave commit
This commit is contained in:
@ -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))
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -6,4 +6,5 @@ public protocol LoaderProtocol
|
||||
associatedtype T: Resource
|
||||
|
||||
func load(url: URL) -> T?
|
||||
func load(url: URL, content: inout ContentManager) -> T?
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
157
Sources/JolkEngine/Input/GamePad.swift
Normal file
157
Sources/JolkEngine/Input/GamePad.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
62
Sources/JolkEngine/Input/Input.swift
Normal file
62
Sources/JolkEngine/Input/Input.swift
Normal 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() }
|
||||
}
|
||||
}
|
137
Sources/JolkEngine/Input/Keyboard.swift
Normal file
137
Sources/JolkEngine/Input/Keyboard.swift
Normal 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))
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user