diff --git a/Package.swift b/Package.swift index 65bb694..825b30f 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,8 @@ let package = Package( ], products: [ .executable(name: "Test", targets: ["Test"]), - .library(name: "JolkEngine", targets: ["JolkEngine"]) + .library(name: "JolkEngine", targets: ["JolkEngine"]), + .library(name: "Maths", targets: ["Maths"]), ], dependencies: [ .package( @@ -17,7 +18,7 @@ let package = Package( .upToNextMajor(from: "1.4.1")), .package( url: "https://github.com/apple/swift-collections.git", - .upToNextMinor(from: "1.1.0")) + .upToNextMinor(from: "1.1.0")), ], targets: [ .executableTarget( @@ -30,12 +31,19 @@ let package = Package( dependencies: [ .product(name: "SDL", package: "SwiftSDL2"), .product(name: "Collections", package: "swift-collections"), - "HSLuv" + "HSLuv", + .target(name: "Maths"), ], swiftSettings: [ .unsafeFlags(["-Xcc", "-DGL_SILENCE_DEPRECATION"]) ] ), .target(name: "HSLuv"), + .target( + name: "Maths", + swiftSettings: [ + .unsafeFlags(["-DUSE_SIMD"]) + ] + ), ] ) diff --git a/Sources/JolkEngine/Application.swift b/Sources/JolkEngine/Application.swift index e72023e..e3340bc 100644 --- a/Sources/JolkEngine/Application.swift +++ b/Sources/JolkEngine/Application.swift @@ -93,7 +93,9 @@ public class Application content.setLoader(extension: "obj", loader: ObjLoader()) content.setLoader(extension: "g3db", loader: G3DbLoader()) + content.setLoader(extensions: ["cache", "mesh"], loader: VMeshLoader()) content.setLoader(extensions: ["png", "jpg"], loader: NSImageLoader()) + content.setLoader(extension: "dds", loader: DDSLoader()) try implementation.loadContent(content: &content) } catch diff --git a/Sources/JolkEngine/Colour.swift b/Sources/JolkEngine/Colour.swift index 97f6a30..94a6a05 100644 --- a/Sources/JolkEngine/Colour.swift +++ b/Sources/JolkEngine/Colour.swift @@ -42,6 +42,11 @@ public extension Colour a: Float((fromRgba32 >> 0) & 0xFF) / 0xFF) } + init(grey v: Float) + { + self.init(r: v, g: v, b: v) + } + func mix(with rhs: Colour, _ amount: Float) -> Colour { let x = amount.saturate diff --git a/Sources/JolkEngine/Content/ContentManager.swift b/Sources/JolkEngine/Content/ContentManager.swift index 85d9740..3da8d88 100644 --- a/Sources/JolkEngine/Content/ContentManager.swift +++ b/Sources/JolkEngine/Content/ContentManager.swift @@ -44,16 +44,10 @@ extension ContentManager public mutating func create(texture: Image, params: Texture2DParameters = .init()) throws -> Texture2D { - try texture.pixels.withUnsafeBytes - { - raw in - let data = raw.baseAddress! - let rendTex = try renderer.createTexture( - data: data, width: texture.width, height: texture.height, - filter: params.magFilter, mipMode: params.mipMode) - resources.append(rendTex) - return Texture2D(id: rendTex, width: texture.width, height: texture.height) - } + let rendTex = try renderer.createTexture(image: texture, + filter: params.magFilter, mipMode: params.mipMode) + resources.append(rendTex) + return Texture2D(id: rendTex, width: texture.width, height: texture.height) } public func getResource(_ path: String) throws -> URL @@ -153,6 +147,23 @@ extension ContentManager let renderResource = try self.create(mesh: mesh) return renderResource as! T } + if T.self == RenderMesh.self + { + guard let mesh = resource as? Mesh else { throw ContentError.loadFailure } + let renderResource = try self.create(mesh: mesh) + return renderResource as! T + } + if T.self == Image.self + { + guard let image = resource as? Image else { throw ContentError.loadFailure } + return image as! T + } + if T.self == Texture2D.self + { + guard let image = resource as? Image else { throw ContentError.loadFailure } + let texture2D = try self.create(texture: image) + return texture2D as! T + } throw ContentError.loadFailure } diff --git a/Sources/JolkEngine/Content/Image.swift b/Sources/JolkEngine/Content/Image.swift index 764fae8..bbd6e4f 100644 --- a/Sources/JolkEngine/Content/Image.swift +++ b/Sources/JolkEngine/Content/Image.swift @@ -3,7 +3,30 @@ import Foundation public struct Image: Resource { - let pixels: Data - let width: Int - let height: Int + let data: Data + let format: Format + let width: Int, height: Int + let mipLevels: Int + + public enum Format + { + case argb8888, abgr8888 + case rgb888, bgr888 + //case l8, l16, a8, al88 + case s3tc_bc1, s3tc_bc2_premul + case s3tc_bc2, s3tc_bc3_premul + case s3tc_bc3, rgtc_bc4, rgtc_bc5_3dc + } +} + +extension Image +{ + init(_ data: Data, format: Format, width: Int, height: Int, mipLevels: Int = 0) + { + self.data = data + self.format = format + self.width = width + self.height = height + self.mipLevels = mipLevels + } } diff --git a/Sources/JolkEngine/Content/Mesh.swift b/Sources/JolkEngine/Content/Mesh.swift index 0007f85..88fb68e 100644 --- a/Sources/JolkEngine/Content/Mesh.swift +++ b/Sources/JolkEngine/Content/Mesh.swift @@ -1,4 +1,5 @@ import OrderedCollections +import Maths public struct Model: Resource diff --git a/Sources/JolkEngine/Content/ObjModel.swift b/Sources/JolkEngine/Content/ObjModel.swift index bf7a0ec..25526d9 100644 --- a/Sources/JolkEngine/Content/ObjModel.swift +++ b/Sources/JolkEngine/Content/ObjModel.swift @@ -1,4 +1,5 @@ import Foundation +import Maths import OrderedCollections @@ -25,6 +26,8 @@ public struct ObjModel public struct ObjMaterial { + var name: String + var model: IlluminationModel = .lambert var ambient: Colour = .black var diffuse: Colour = .white diff --git a/Sources/JolkEngine/Input/GamePad.swift b/Sources/JolkEngine/Input/GamePad.swift index c98b1fa..1d1f308 100644 --- a/Sources/JolkEngine/Input/GamePad.swift +++ b/Sources/JolkEngine/Input/GamePad.swift @@ -1,5 +1,6 @@ import Foundation import SDL2 +import Maths public class GamePad @@ -144,8 +145,8 @@ internal extension GamePad.Buttons { 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 .north: SDL_CONTROLLER_BUTTON_Y + case .west: SDL_CONTROLLER_BUTTON_X case .start: SDL_CONTROLLER_BUTTON_START case .select: SDL_CONTROLLER_BUTTON_BACK case .guide: SDL_CONTROLLER_BUTTON_GUIDE @@ -166,3 +167,32 @@ internal extension GamePad.Buttons } } } + + +public extension Vector2 where Scalar: FloatingPoint +{ + func cardinalDeadzone(min: Scalar, max: Scalar) -> Self + { + Self(self.x.axisDeadzone(min, max), self.y.axisDeadzone(min, max)) + } + + func radialDeadzone(min: Scalar, max: Scalar) -> Self + { + let magnitude = self.len + if magnitude == .zero || magnitude < min { return Self.zero } + if magnitude > max { return self / magnitude } + let rescale = (magnitude - min) / (max - min) + return self / magnitude * rescale + } +} + +fileprivate extension FloatingPoint +{ + @inline(__always) internal func axisDeadzone(_ min: Self, _ max: Self) -> Self + { + let xabs = abs(self) + return if xabs <= min { 0 } + else if xabs >= max { Self(signOf: self, magnitudeOf: 1) } + else { Self(signOf: self, magnitudeOf: xabs - min) / (max - min) } + } +} diff --git a/Sources/JolkEngine/Loaders/DDSLoader.swift b/Sources/JolkEngine/Loaders/DDSLoader.swift new file mode 100644 index 0000000..053179d --- /dev/null +++ b/Sources/JolkEngine/Loaders/DDSLoader.swift @@ -0,0 +1,239 @@ +import Foundation + + +struct DDSLoader: LoaderProtocol +{ + typealias T = Image + + func load(url: URL) -> T? + { + guard let image = try? DDSLoader.read(url: url) else { return nil } + return image + } + + func load(url: URL, content: inout ContentManager) -> T? { return load(url: url) } + + static func read(url: URL) throws -> T + { + let file = try FileHandle(forReadingFrom: url) + let header = DDSHeader( + magic: .init(try file.read(upToCount: 4)), + size: try file.read(as: UInt32.self).littleEndian, + flags: .init(rawValue: try file.read(as: UInt32.self).littleEndian), + height: try file.read(as: UInt32.self).littleEndian, + width: try file.read(as: UInt32.self).littleEndian, + pitch: try file.read(as: UInt32.self).littleEndian, + depth: try file.read(as: UInt32.self).littleEndian, + mipNum: try file.read(as: UInt32.self).littleEndian, + reserved1: try (0..<11).map { _ in try file.read(as: UInt32.self) }, + pixFmt: DDSPixelFormat( + size: try file.read(as: UInt32.self).littleEndian, + flags: .init(rawValue: try file.read(as: UInt32.self).littleEndian), + fourCC: .init(try file.read(upToCount: 4)), + bits: try file.read(as: UInt32.self).littleEndian, + rMask: try file.read(as: UInt32.self).littleEndian, + gMask: try file.read(as: UInt32.self).littleEndian, + bMask: try file.read(as: UInt32.self).littleEndian, + aMask: try file.read(as: UInt32.self).littleEndian), + caps1: try file.read(as: UInt32.self).littleEndian, + caps2: try file.read(as: UInt32.self).littleEndian, + caps3: try file.read(as: UInt32.self).littleEndian, + caps4: try file.read(as: UInt32.self).littleEndian, + reserved2: try file.read(as: UInt32.self)) + + guard header.magic == FourCC("DDS "), + header.size == 124 // MemoryLayout.size + else { throw NSError() /* ("DDS Header size mismatch") */ } + + //#if MemoryLayout.size != 20 +//#error("DDS DX10 Extended Header size mismatch") +//#endif + + let format = try Image.Format.resolve(pixFmt: header.pixFmt) + let size = (0...header.mipNum).reduce(0) { size, i in + let width = header.width &>> i, height = header.height &>> i + guard width > 0 && height > 0 else { return size } + return size + format.computeSize(width: width, height: height) + } + + guard let data = try file.read(upToCount: size) + else { throw NSError() } + + return .init(data, + format: format, + width: Int(header.width), height: Int(header.height), + mipLevels: Int(header.mipNum)) + } +} + +fileprivate extension Image.Format +{ + static func resolve(pixFmt: DDSPixelFormat) throws -> Self + { + switch pixFmt + { + case DDSPixelFormat(.rgb, bits: 24, r: 0xFF0000, g: 0x00FF00, b: 0x0000FF): .rgb888 + case DDSPixelFormat(.rgb, bits: 24, r: 0x0000FF, g: 0x00FF00, b: 0xFF0000): .bgr888 + case DDSPixelFormat(.rgba, bits: 32, r: 0x00FF0000, g: 0x0000FF00, b: 0x000000FF, a: 0xFF000000): .argb8888 + case DDSPixelFormat(.rgba, bits: 32, r: 0x000000FF, g: 0x0000FF00, b: 0x00FF0000, a: 0xFF000000): .abgr8888 + //case DDSPixelFormat(.luminance, bits: 8, r: 0xFF): .l8 + //case DDSPixelFormat(.alpha, bits: 8, a: 0xFF): .a8 + //case DDSPixelFormat(.luminance, bits: 16, a: 0xFFFF): .l16 + //case DDSPixelFormat(.luminanceA, bits: 16, r: 0x00FF, a: 0xFF00): .al88 + case DDSPixelFormat(.fourCC, fourCC: .init("DXT1")): .s3tc_bc1 + case DDSPixelFormat(.fourCC, fourCC: .init("DXT2")): .s3tc_bc2_premul + case DDSPixelFormat(.fourCC, fourCC: .init("DXT3")): .s3tc_bc2 + case DDSPixelFormat(.fourCC, fourCC: .init("DXT4")): .s3tc_bc3_premul + case DDSPixelFormat(.fourCC, fourCC: .init("DXT5")): .s3tc_bc3 + case DDSPixelFormat(.fourCC, fourCC: .init("ATI1")): .rgtc_bc4 + case DDSPixelFormat(.fourCC, fourCC: .init("ATI2")): .rgtc_bc5_3dc + default: throw NSError() + } + } +} + +internal extension Image.Format +{ + private func dxtcSize(_ w: UInt32, _ h: UInt32) -> Int + { + let mul = [.s3tc_bc1, .rgtc_bc4].contains(self) ? 8 : 16 + let cw = Int(w + 3) / 4 + let ch = Int(h + 3) / 4 + return cw * ch * mul + } + + func computeSize(width w: UInt32, height h: UInt32) -> Int + { + switch self + { + //case .l8, .a8: Int(w) * Int(h) + //case .l16, .al88: Int(w) * Int(h) * 2 + case .rgb888, .bgr888: Int(w) * Int(h) * 3 + case .argb8888, .abgr8888: Int(w) * Int(h) * 4 + case .s3tc_bc1, .s3tc_bc2_premul, .s3tc_bc2, .s3tc_bc3_premul, .s3tc_bc3, .rgtc_bc4, .rgtc_bc5_3dc: + dxtcSize(w, h) + } + } +} + +struct DDSPixelFormat: Equatable +{ + let size: UInt32 + let flags: Flags + let fourCC: FourCC + let bits: UInt32 + let rMask: UInt32, gMask: UInt32, bMask: UInt32, aMask: UInt32 + + struct Flags: OptionSet + { + let rawValue: UInt32 + + static let alphaPix = Self(rawValue: 0x00000001) + static let alpha = Self(rawValue: 0x00000002) + static let fourCC = Self(rawValue: 0x00000004) + static let rgb = Self(rawValue: 0x00000040) + static let yuv = Self(rawValue: 0x00000200) + static let indexed1 = Self(rawValue: 0x00000800) + static let indexed2 = Self(rawValue: 0x00001000) + static let indexed4 = Self(rawValue: 0x00000008) + static let indexed8 = Self(rawValue: 0x00000020) + static let luminance = Self(rawValue: 0x00020000) + static let premult = Self(rawValue: 0x00008000) + + static let nvNormal = Self(rawValue: 0x80000000) + static let nvSrgb = Self(rawValue: 0x40000000) + + static let rgba: Self = [ .rgb, .alphaPix ] + static let luminanceA: Self = [ .luminance, .alphaPix ] + } +} + +extension DDSPixelFormat +{ + init( + _ flags: Flags, + fourCC: FourCC = .init(), + bits: UInt32 = 0, + r: UInt32 = 0, g: UInt32 = 0, b: UInt32 = 0, a: UInt32 = 0) + { + self.size = UInt32(MemoryLayout.size) + assert(self.size == 32, "DDS pixel format size mismatch") + self.flags = flags + self.fourCC = fourCC + self.bits = bits + self.rMask = r + self.gMask = g + self.bMask = b + self.aMask = a + } +} + +struct FourCC: Equatable +{ + let rawValue: UInt32 + + init() + { + self.rawValue = 0 + } + + init(rawValue: UInt32) + { + self.rawValue = rawValue.bigEndian + } + + init(_ data: Data?) + { + assert(data != nil) + assert(data!.count == 4) + self.rawValue = data!.withUnsafeBytes { $0.load(as: UInt32.self).bigEndian } + } + + init(_ a: Character, _ b: Character, _ c: Character, _ d: Character) + { + assert(a.isASCII && b.isASCII && c.isASCII && d.isASCII) + var u = UInt32(a.asciiValue!) + u |= UInt32(b.asciiValue!) << 8 + u |= UInt32(c.asciiValue!) << 16 + u |= UInt32(d.asciiValue!) << 24 + self.rawValue = u.bigEndian + } + + init(_ abcd: String) + { + assert(abcd.count == 4) + self.init( + abcd[abcd.index(abcd.startIndex, offsetBy: 0)], + abcd[abcd.index(abcd.startIndex, offsetBy: 1)], + abcd[abcd.index(abcd.startIndex, offsetBy: 2)], + abcd[abcd.index(abcd.startIndex, offsetBy: 3)]) + } +} + +struct DDSHeader +{ + let magic: FourCC + let size: UInt32 + let flags: Flags + let height: UInt32, width: UInt32, pitch: UInt32, depth: UInt32 + let mipNum: UInt32 + let reserved1: [UInt32] + let pixFmt: DDSPixelFormat + let caps1: UInt32, caps2: UInt32, caps3: UInt32, caps4: UInt32 + let reserved2: UInt32 + + struct Flags: OptionSet + { + let rawValue: UInt32 + + static let caps = Self(rawValue: 0x000001) + static let height = Self(rawValue: 0x000002) + static let width = Self(rawValue: 0x000004) + static let pitch = Self(rawValue: 0x000008) + static let pixFmt = Self(rawValue: 0x001000) + static let mipNum = Self(rawValue: 0x020000) + static let linSz = Self(rawValue: 0x080000) + static let depth = Self(rawValue: 0x800000) + } +} + diff --git a/Sources/JolkEngine/Loaders/G3DbLoader.swift b/Sources/JolkEngine/Loaders/G3DbLoader.swift index f144d65..c6a5b7f 100644 --- a/Sources/JolkEngine/Loaders/G3DbLoader.swift +++ b/Sources/JolkEngine/Loaders/G3DbLoader.swift @@ -1,5 +1,6 @@ import Foundation import OrderedCollections +import Maths class G3DbLoader: LoaderProtocol diff --git a/Sources/JolkEngine/Loaders/NSImageLoader.swift b/Sources/JolkEngine/Loaders/NSImageLoader.swift index 5d19906..64a6415 100644 --- a/Sources/JolkEngine/Loaders/NSImageLoader.swift +++ b/Sources/JolkEngine/Loaders/NSImageLoader.swift @@ -43,10 +43,9 @@ struct NSImageLoader: LoaderProtocol context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height)) guard let data = context.data else { throw ImageLoaderError.loadFailed("what") } - return Image( - pixels: Data(bytes: data, count: 4 * image.width * image.height), - width: image.width, - height: image.height) + return Image(Data(bytes: data, count: 4 * image.width * image.height), + format: .argb8888, + width: image.width, height: image.height) } } } diff --git a/Sources/JolkEngine/Loaders/ObjLoader.swift b/Sources/JolkEngine/Loaders/ObjLoader.swift index dbd04ca..ae41a9d 100644 --- a/Sources/JolkEngine/Loaders/ObjLoader.swift +++ b/Sources/JolkEngine/Loaders/ObjLoader.swift @@ -7,12 +7,17 @@ extension ObjMaterial func convert(content: UnsafeMutablePointer? = nil) -> Material { var m = Material() + m.id = self.name m.diffuse = self.diffuse.setAlpha(self.alpha) if ![ .colour, .lambert, .shadowOnly ].contains(self.model) { m.specular = self.specular m.specularExp = self.specularExp } + else + { + m.specularExp = self.specularExp + } if let content = content { if let albedo = self.diffuseMap diff --git a/Sources/JolkEngine/Loaders/ObjReader.swift b/Sources/JolkEngine/Loaders/ObjReader.swift index 5702146..e2c279d 100644 --- a/Sources/JolkEngine/Loaders/ObjReader.swift +++ b/Sources/JolkEngine/Loaders/ObjReader.swift @@ -1,4 +1,5 @@ import Foundation +import Maths public struct ObjReader @@ -115,8 +116,8 @@ fileprivate struct ObjMtlLoader { materials[name] = mat! } - mat = .init() name = String(s[0]) + mat = .init(name: name) } file.preHandle { s in if s != "newmtl" && mat == nil { throw ObjLoaderError.unexpectedTag } } diff --git a/Sources/JolkEngine/Loaders/VMeshLoader.swift b/Sources/JolkEngine/Loaders/VMeshLoader.swift new file mode 100644 index 0000000..a154480 --- /dev/null +++ b/Sources/JolkEngine/Loaders/VMeshLoader.swift @@ -0,0 +1,118 @@ +import Foundation +import Maths + + +struct VMeshLoader: LoaderProtocol +{ + typealias T = Mesh + + func load(url: URL) -> T? { try? Self.read(url: url) } + func load(url: URL, content: inout ContentManager) -> T? { load(url: url) } + + static func read(url: URL) throws -> T + { + let file = try FileHandle(forReadingFrom: url) + + // read header fields + let header = VMeshHeader( + magic: try file.read(upToCount: 4), + version: try file.read(as: UInt8.self).littleEndian, + idxSize: try file.read(as: UInt8.self).littleEndian, + subMeshCount: try file.read(as: UInt16.self).littleEndian, + vertexCount: try file.read(as: UInt32.self).littleEndian, + indexCount: try file.read(as: UInt32.self).littleEndian) + + // header sanity checks + guard header.magic == Data("VMSH".utf8) + else { throw NSError() } + guard header.version == 2 + else { throw NSError() } + + // check index type + let idxSize = switch header.idxSize + { + case 1, 2, 4: header.idxSize + default: throw NSError() + } + + // fail on empty data lengths + guard header.vertexCount > 0, header.indexCount > 0 + else { throw NSError() } + + // read submeshes + var subMeshes = [VMeshSubMesh](repeating: .empty, count: Int(header.subMeshCount)) + for i in 0.. + let texCoord: Vec2f + let normal: SIMD4 + let tangent: SIMD4 +} + +extension VMeshVertex +{ + static let empty: Self = .init(position: .init(), texCoord: .init(), normal: .init(), tangent: .init()) +} + +struct VMeshSubMesh +{ + let name: String + let offset: UInt32, count: UInt32 +} + +extension VMeshSubMesh +{ + static let empty: Self = .init(name: .init(), offset: .init(), count: .init()) +} diff --git a/Sources/JolkEngine/Maths.swift b/Sources/JolkEngine/Maths.swift deleted file mode 100644 index cf95780..0000000 --- a/Sources/JolkEngine/Maths.swift +++ /dev/null @@ -1,293 +0,0 @@ -import Foundation -import simd - - -public extension FloatingPoint -{ - @inline(__always) var saturate: Self { min(max(self , 0), 1) } - - @inline(__always) static func lerp(_ a: Self, _ b: Self, _ x: Self) -> Self { a * (1 - x) + b * x } - - @inline(__always) static func deg(fromRad: Self) -> Self { fromRad * (180 / Self.pi) } - @inline(__always) static func rad(fromDeg: Self) -> Self { fromDeg * (Self.pi / 180) } - - fileprivate func axisDeadzone(_ min: Self, _ max: Self) -> Self - { - let xabs = abs(self) - return if xabs <= min { 0 } - else if xabs >= max { Self(signOf: self, magnitudeOf: 1) } - else { Self(signOf: self, magnitudeOf: xabs - min) / (max - min) } - } -} - -public extension SIMD2 where Scalar: FloatingPoint -{ - @inline(__always) var len2: Scalar { x * x + y * y } - //@inline(__always) var len2: Scalar { simd_dot(self, self) } - @inline(__always) var len: Scalar { len2.squareRoot() } - @inline(__always) var normalised: Self { self / len } - - @inline(__always) func dot(_ b: Self) -> Scalar { x * b.x + y * b.y } - @inline(__always) func reflect(_ n: Self) -> Self { self - (n * 2 * self.dot(n)) } - @inline(__always) func project(_ n: Self) -> Self { n * self.dot(n) } - - @inline(__always) func cross(_ b: Self) -> Scalar { x * b.y - y * b.x } - - @inline(__always) func lerp(_ b: Self, _ x: Scalar) -> Self - { - let invX = 1 - x - 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)) - } - - func radialDeadzone(min: Scalar, max: Scalar) -> Self - { - let magnitude = self.len - if magnitude == .zero || magnitude < min { return Self.zero } - if magnitude > max { return self / magnitude } - let rescale = (magnitude - min) / (max - min) - return self / magnitude * rescale - } -} - -public extension SIMD3 where Scalar: FloatingPoint -{ - @inline(__always) static var X: Self { Self(1, 0, 0) } - @inline(__always) static var Y: Self { Self(0, 1, 0) } - @inline(__always) static var Z: Self { Self(0, 0, 1) } - - @inline(__always) static var up: Self { Y } - @inline(__always) static var down: Self { -Y } - @inline(__always) static var left: Self { -X } - @inline(__always) static var right: Self { X } - @inline(__always) static var forward: Self { Z } - @inline(__always) static var back: Self { -Z } - - @inline(__always) var len2: Scalar { x * x + y * y + z * z } - @inline(__always) var len: Scalar { len2.squareRoot() } - @inline(__always) var normalised: Self { self / len } - - @inline(__always) mutating func normalise() { self /= len } - - @inline(__always) func lerp(_ b: Self, _ x: Scalar) -> Self - { - let invX = 1 - x - return Self(self.x * invX + b.x * x, self.y * invX + b.y * x, self.z * invX + b.z * x) - } - - @inline(__always) func dot(_ b: Self) -> Scalar { x * b.x + y * b.y + z * b.z } - @inline(__always) func cross(_ b: Self) -> Self { Self(y * b.z - z * b.y, z * b.x - x * b.z, x * b.y - y * b.x) } - - @inline(__always) func project(_ n: Self) -> Self { n * self.dot(n) } -} - -public extension SIMD4 where Scalar: FloatingPoint -{ - @inline(__always) static var X: Self { Self(1, 0, 0, 0) } - @inline(__always) static var Y: Self { Self(0, 1, 0, 0) } - @inline(__always) static var Z: Self { Self(0, 0, 1, 0) } - @inline(__always) static var W: Self { Self(0, 0, 0, 1) } -} - -public extension simd_float4x4 -{ - @inline(__always) static var identity: Self { Self(diagonal: .one) } - - @inline(__always) static func translate(_ v: Vec3f) -> Self - { - Self( - .init( 1, 0, 0, 0), - .init( 0, 1, 0 ,0), - .init( 0, 0, 1, 0), - .init(v.x, v.y, v.z, 1)) - } - - @inline(__always) static func scale(_ v: Vec3f) -> Self - { - Self( - .init(v.x, 0, 0, 0), - .init( 0, v.y, 0, 0), - .init( 0, 0, v.z, 0), - .init( 0, 0, 0, 1)) - } - - @inline(__always) static func scale(scalar v: Float) -> Self - { - Self( - .init(v, 0, 0, 0), - .init(0, v, 0, 0), - .init(0, 0, v, 0), - .init(0, 0, 0, 1)) - } - - static func rotate(axis v: Vec3f, angle theta: Float) -> Self - { - //FIXME: THIS IS FUCKED UP FOR EVERYTHING OTHER THAN X AXIS ROTATION LOL - /* - let vv = v * v - let xy = v.x * v.y, xz = v.x * v.z, yz = v.y * v.z - let ts = sin(angle), tc = cos(angle) - return Self( - .init( - vv.x + (tc * (1 - vv.x)), - (xz - (tc * xy)) + (ts * v.z), - (xz - (tc * xz)) - (ts * v.y), 0), - .init( - (xy - (tc * xy)) - (ts * v.z), - vv.y + (tc * (1 - vv.y)), - (xz - (tc * xz)) + (ts * v.x), 0), - .init( - (xz - (tc * xz)) + (ts * v.y), - (xz - (tc * xz)) - (ts * v.y), - vv.z + (tc * (1 - vv.z)), 1), - .init(0, 0, 0, 1)) - */ - let vxx = v.x * v.x, vxy = v.x * v.y, vxz = v.x * v.z - let vyy = v.y * v.y, vyz = v.y * v.z - let vzz = v.z * v.z - let ts = sin(theta), tc = cos(theta) - let ic = 1 - tc - return Self( - .init( - ic * vxx + tc, - ic * vxy - v.z * ts, - ic * vxz + v.z * ts, - 0), - .init( - ic * vxy + v.z * ts, - ic * vyy + tc, - ic * vyz - v.x * ts, - 0), - .init( - ic * vxz - v.y * ts, - ic * vyz + v.x * ts, - ic * vzz + tc, - 0), - .init(0, 0, 0, 1)) - } - - @inline(__always) static func rotate(yawPitch: Vec2f) -> Self { return rotate(yaw: yawPitch.x, pitch: yawPitch.y) } - - static func rotate(yaw ytheta: Float, pitch xtheta: Float) -> Self - { - let xc = cos(xtheta), xs = sin(xtheta) - let yc = cos(ytheta), ys = sin(ytheta) - - return Self( - .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 rotate(yaw ytheta: Float, pitch xtheta: Float, roll ztheta: Float) -> Self - { - //FIXME: this doesn't null against control - let xc = cos(xtheta), xs = sin(xtheta) - let yc = cos(ytheta), ys = sin(ytheta) - let zc = cos(ztheta), zs = sin(ztheta) - - let ysxs = ys * xs, ycxs = yc * xs - - let result = Mat4f( - .init(yc * zc + ysxs * zs, yc * -zs + ysxs * zc, -ys * xc, 0), - .init( xc * zs, xc * zc, xs, 0), - .init(ys * zc - ycxs * zs, ys * -zs - ycxs * zc, yc * xc, 0), - .init( 0, 0, 0, 1)) - let shouldBe = .rotate(z: ztheta) * .rotate(x: xtheta) * .rotate(y: ytheta) - - let epsilon: Float = .ulpOfOne - if (result != shouldBe) - { - assert(abs(result[0][0] - shouldBe[0][0]) <= epsilon) // epsilon - assert(result[1][0] == shouldBe[1][0]) - assert(abs(result[2][0] - shouldBe[2][0]) <= epsilon) // epsilon - assert(result[3][0] == shouldBe[3][0]) - assert(abs(result[0][1] - shouldBe[0][1]) <= epsilon) // epsilon - assert(result[1][1] == shouldBe[1][1]) - assert(abs(result[2][1] - shouldBe[2][1]) <= epsilon) // epsilon - assert(result[3][1] == shouldBe[3][1]) - assert(result[0][2] == shouldBe[0][2]) - assert(result[1][2] == shouldBe[1][2]) - assert(result[2][2] == shouldBe[2][2]) - assert(result[3][2] == shouldBe[3][2]) - assert(result[0][3] == shouldBe[0][3]) - assert(result[1][3] == shouldBe[1][3]) - assert(result[2][3] == shouldBe[2][3]) - assert(result[3][3] == shouldBe[3][3]) - } - return result - } - - @inline(__always) static func rotate(x theta: Float) -> Self - { - let c = cos(theta), s = sin(theta) - return Self( - .init(1, 0, 0, 0), - .init(0, c, s, 0), - .init(0, -s, c, 0), - .init(0, 0, 0, 1)) - } - - @inline(__always) static func rotate(y theta: Float) -> Self - { - let c = cos(theta), s = sin(theta) - return Self( - .init(c, 0, -s, 0), - .init(0, 1, 0, 0), - .init(s, 0, c, 0), - .init(0, 0, 0, 1)) - } - - @inline(__always) static func rotate(z theta: Float) -> Self - { - let c = cos(theta), s = sin(theta) - return Self( - .init(c, -s, 0, 0), - .init(s, c, 0, 0), - .init(0, 0, 1, 0), - .init(0, 0, 0, 1)) - } - - static func perspective(fovY: Float, aspect: Float, zNear: Float, zFar: Float) -> Self - { - let h = 1 / tanf(fovY * 0.5) - let w = h / aspect - let invClipRange = 1 / (zFar - zNear) - let z = -(zFar + zNear) * invClipRange - let z2 = -(2 * zFar * zNear) * invClipRange - return simd_matrix( - .init(w, 0, 0, 0), - .init(0, h, 0, 0), - .init(0, 0, z, -1), - .init(0, 0, z2, 0)) - } - - static func lookAt(from: Vec3f = .zero, to: Vec3f, up: Vec3f = .up) -> Self - { - let forward = (to - from).normalised - let bitangent = forward.cross(up).normalised - let tangent = bitangent.cross(forward).normalised - let normal = -forward - return simd_matrix( - .init(bitangent.x, tangent.x, normal.x, 0.0), - .init(bitangent.y, tangent.y, normal.y, 0.0), - .init(bitangent.z, tangent.z, normal.z, 0.0), - .init( 0.0, 0.0, 0.0, 1.0)) - } -} - - -public typealias Vec2f = SIMD2 -public typealias Vec2d = SIMD2 -public typealias Vec3f = SIMD3 -public typealias Vec3d = SIMD3 -public typealias Vec4f = SIMD4 -public typealias Vec4d = SIMD4 -public typealias Mat4f = simd_float4x4 diff --git a/Sources/JolkEngine/Renderer/Environment.swift b/Sources/JolkEngine/Renderer/Environment.swift index 2d30a23..30e8428 100644 --- a/Sources/JolkEngine/Renderer/Environment.swift +++ b/Sources/JolkEngine/Renderer/Environment.swift @@ -1,3 +1,6 @@ +import Maths + + public enum Fog { public enum Mode diff --git a/Sources/JolkEngine/Renderer/Material.swift b/Sources/JolkEngine/Renderer/Material.swift index b2aff31..1e87963 100644 --- a/Sources/JolkEngine/Renderer/Material.swift +++ b/Sources/JolkEngine/Renderer/Material.swift @@ -1,19 +1,21 @@ public struct Material { public var id: String - public var diffuse: Colour, specular: Colour + public var diffuse: Colour, specular: Colour, emmision: Colour public var specularExp: Float public var texture: RenderTexture2D public init(id: String = "", diffuse: Colour = .white, - specular: Colour = .zero, - gloss: Float = 0, + specular: Colour = .black, + emmision: Colour = .black, + gloss: Float = 20, texture: RenderTexture2D = .empty) { self.id = id self.diffuse = diffuse self.specular = specular + self.emmision = emmision self.specularExp = gloss self.texture = texture } diff --git a/Sources/JolkEngine/Renderer/OpenGL.swift b/Sources/JolkEngine/Renderer/OpenGL.swift index 4142571..d144293 100644 --- a/Sources/JolkEngine/Renderer/OpenGL.swift +++ b/Sources/JolkEngine/Renderer/OpenGL.swift @@ -1,9 +1,11 @@ //#set OPENGL3 import Foundation +import Maths import SDL2 -#if OPENGL3 import OpenGL +#if OPENGL3 +import OpenGL.GL #else import OpenGL.GL3 #endif @@ -41,7 +43,7 @@ class OpenGL: Renderer guard SDL_GL_MakeCurrent(sdlWindow, glCtx) == 0 else { throw RendererError.sdlError(message: "SDL_GL_MakeCurrent: \(String(cString: SDL_GetError()))") } - state.enable([.texture2D, .cullFace, .depthTest, .rescaleNormal, .colourMaterial]) + state.enable([.texture2D, .cullFace, .depthTest, .rescaleNormal/*, .colourMaterial */]) if srgb { state.enable(.frameBufferSrgb) } state.cullFace = .back state.clearDepth = 1 @@ -132,13 +134,12 @@ class OpenGL: Renderer materials: mesh.materials) } - func createTexture(data: UnsafeRawPointer, width: Int, height: Int) throws -> RenderTexture2D + func createTexture(image: Image) throws -> RenderTexture2D { - try createTexture(data: data, width: width, height: height, filter: .linear, mipMode: .off) + try createTexture(image: image, filter: .linear, mipMode: .off) } - func createTexture(data: UnsafeRawPointer, width: Int, height: Int, - filter: FilterMode, mipMode: MipMode) throws -> RenderTexture2D + func createTexture(image: Image, filter: FilterMode, mipMode: MipMode) throws -> RenderTexture2D { let min: Int32 = switch mipMode { @@ -146,14 +147,19 @@ class OpenGL: Renderer case .nearest: filter.glMip case .linear: filter.glLinearMip } - return RenderTexture2D(try loadTexture(data: data, - width: GLsizei(width), height: GLsizei(height), - minFilter: min, magFilter: filter.gl)) + return try image.data.withUnsafeBytes + { raw in + RenderTexture2D(try loadTexture( + data: raw.baseAddress!, format: image.format, + width: GLsizei(image.width), height: GLsizei(image.height), + mipLevels: image.mipLevels, + minFilter: min, magFilter: filter.gl)) + } } private func loadTexture( - data: UnsafeRawPointer, - width: GLsizei, height: GLsizei, + data: UnsafeRawPointer, format: Image.Format, + width: GLsizei, height: GLsizei, mipLevels: Int, minFilter: Int32 = GL_LINEAR, magFilter: Int32 = GL_LINEAR, wrap: Int32 = GL_REPEAT) throws -> GLuint { @@ -165,19 +171,49 @@ class OpenGL: Renderer state.texture2DParameter(active: 0, param: .magFilter, int: magFilter) state.texture2DParameter(active: 0, param: .wrapS, int: wrap) state.texture2DParameter(active: 0, param: .wrapT, int: wrap) - let format: GLint = srgb ? GL_SRGB8 : GL_RGBA - glTexImage2D(GLenum(GL_TEXTURE_2D), - 0, format, width, height, 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), data) + + let upload = getTextureUploader(format: format) + var offset = 0 + for i in 0...mipLevels + { + let levelWidth = width &>> i, levelHeight = height &>> i + let size = format.computeSize(width: UInt32(levelWidth), height: UInt32(levelHeight)) + upload(GLint(i), levelWidth, levelHeight, data + offset, GLsizei(size)) + offset += size + } // Generate mipmaps if needed if [GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST] - .contains(minFilter) { glGenerateMipmap(GLenum(GL_TEXTURE_2D)) } + .contains(minFilter) && mipLevels == 0 { glGenerateMipmap(GLenum(GL_TEXTURE_2D)) } state.bindTexture2D(active: 0, texture: OpenGLState.defaultTexture) return texId } + private typealias UploadFunc = (GLint, GLsizei, GLsizei, UnsafeRawPointer, GLsizei) -> Void + private func getTextureUploader(format: Image.Format) -> UploadFunc + { + let target = GLenum(GL_TEXTURE_2D) + let border: GLint = 0 + return switch format + { + case .argb8888: { (level, width, height, data, size) in + glTexImage2D(target, level, GLint(self.srgb ? GL_SRGB8 : GL_RGBA), + width, height, border, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), data) + } + case .s3tc_bc1: { (level, width, height, data, size) in + glCompressedTexImage2D(target, level, GLenum(GL_COMPRESSED_SRGB_S3TC_DXT1_EXT), + width, height, border, size, data) + } + case .rgtc_bc5_3dc: { (level, width, height, data, size) in + glCompressedTexImage2D(target, level, GLenum(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT), + width, height, border, size, data) + } + default: fatalError() + } + } + func deleteMesh(_ mesh: RenderMesh) { @@ -210,7 +246,11 @@ class OpenGL: Renderer func setMaterial(_ mat: Material) { - glColor4f(mat.diffuse.r, mat.diffuse.g, mat.diffuse.b, mat.diffuse.a) + //glColor4f(mat.diffuse.r, mat.diffuse.g, mat.diffuse.b, mat.diffuse.a) + state.materialDiffuse = srgb ? mat.diffuse.linear : mat.diffuse + state.materialSpecular = srgb ? mat.specular.linear : mat.specular + state.materialEmmision = srgb ? mat.emmision.linear : mat.emmision + state.materialShininess = mat.specularExp state.bindTexture2D(active: 0, texture: mat.texture.isValid ? mat.texture.id : 0) } @@ -226,14 +266,13 @@ class OpenGL: Renderer private func bindMesh(mesh: RenderMesh) { - state.enableClient([ .vertex, .colour, .normal, .textureCoord ]) - state.arrayBuffer = UInt32(mesh.vboHnd) state.elementArrayBuffer = UInt32(mesh.iboHnd) let stride = GLsizei(MemoryLayout.stride) if V.self == VertexPositionNormalTexcoord.self { + state.enableClient([ .vertex, .normal, .textureCoord ]) glVertexPointer(3, GLenum(GL_FLOAT), stride, UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \VertexPositionNormalTexcoord.position)!)) glNormalPointer(GLenum(GL_FLOAT), stride, @@ -243,6 +282,7 @@ class OpenGL: Renderer } else if V.self == VertexPositionNormalColourTexcoord.self { + state.enableClient([ .vertex, .colour, .normal, .textureCoord ]) glVertexPointer(3, GLenum(GL_FLOAT), stride, UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \VertexPositionNormalColourTexcoord.position)!)) glColorPointer(4, GLenum(GL_FLOAT), stride, @@ -252,6 +292,16 @@ class OpenGL: Renderer glTexCoordPointer(2, GLenum(GL_FLOAT), stride, UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \VertexPositionNormalColourTexcoord.texCoord)!)) } + else if V.self == VMeshVertex.self + { + state.enableClient([ .vertex, .normal, .textureCoord ]) + glVertexPointer(3, GLenum(GL_FLOAT), stride, + UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \VMeshVertex.position)!)) + glTexCoordPointer(2, GLenum(GL_FLOAT), stride, + UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \VMeshVertex.texCoord)!)) + glNormalPointer(GLenum(GL_BYTE), stride, + UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \VMeshVertex.normal)!)) + } } private func unbindMesh() @@ -259,7 +309,7 @@ class OpenGL: Renderer state.elementArrayBuffer = OpenGLState.defaultBuffer state.arrayBuffer = OpenGLState.defaultBuffer - state.disableClient([ .vertex, .normal, .textureCoord ]) + state.disableClient([ .vertex, .colour, .normal, .textureCoord ]) } private func draw(mesh: RenderMesh) @@ -302,7 +352,8 @@ class OpenGL: Renderer private func loadMatrix(_ matrix: Mat4f) { - withUnsafePointer(to: matrix.columns) + //withUnsafePointer(to: matrix.columns) + withUnsafePointer(to: matrix) { $0.withMemoryRebound(to: GLfloat.self, capacity: 16) { (values: UnsafePointer) in @@ -313,7 +364,8 @@ class OpenGL: Renderer private func mulMatrix(_ matrix: Mat4f) { - withUnsafePointer(to: matrix.columns) + //withUnsafePointer(to: matrix.columns) + withUnsafePointer(to: matrix) { $0.withMemoryRebound(to: GLfloat.self, capacity: 16) { (values: UnsafePointer) in @@ -360,6 +412,7 @@ class OpenGL: Renderer } state.lightModelAmbient = env.ambient + state.lightModelLocalViewer = true let lightCaps: [OpenGLState.Capability] = [ .light0, .light1, .light2, .light3, diff --git a/Sources/JolkEngine/Renderer/OpenGLState.swift b/Sources/JolkEngine/Renderer/OpenGLState.swift index 5ee78b3..13f6cfe 100644 --- a/Sources/JolkEngine/Renderer/OpenGLState.swift +++ b/Sources/JolkEngine/Renderer/OpenGLState.swift @@ -1,3 +1,4 @@ +import Maths import OpenGL @@ -308,23 +309,28 @@ struct OpenGLState } } - enum CullFace { case front, back, frontAndBack } - private var _cullFace: CullFace = .back + enum Face { case front, back, frontAndBack } + private var _cullFace: Face = .back - var cullFace: CullFace + fileprivate func glFace(_ face: Face) -> GLenum + { + let face = switch face + { + case .front: GL_FRONT + case .back: GL_BACK + case .frontAndBack: GL_FRONT_AND_BACK + } + return GLenum(face) + } + + var cullFace: Face { get { _cullFace } set(newMode) { if newMode != _cullFace { - let modeEnum = switch newMode - { - case .front: GL_FRONT - case .back: GL_BACK - case .frontAndBack: GL_FRONT_AND_BACK - } - glCullFace(GLenum(modeEnum)) + glCullFace(glFace(newMode)) _cullFace = newMode } } @@ -815,4 +821,97 @@ struct OpenGLState } } } + + private var _materialAmbient = Colour(r: 0.2, g: 0.2, b: 0.2) + private var _materialDiffuse = Colour(r: 0.8, g: 0.8, b: 0.8) + private var _materialSpecular = Colour.black + private var _materialEmmision = Colour.black + + fileprivate func setMaterialColour(_ face: Face, _ pname: Int32, _ colour: Colour) + { + let face = glFace(.frontAndBack) + if _capabilities.contains(.colourMaterial) + { + glColorMaterial(face, GLenum(pname)) + glColor4f(colour.r, colour.g, colour.b, colour.a) + } + else + { + withUnsafePointer(to: colour) + { + $0.withMemoryRebound(to: GLfloat.self, capacity: 4) + { + glMaterialfv(face, GLenum(pname), $0) + } + } + } + } + + var materialAmbient: Colour + { + get { _materialAmbient } + set(newColour) + { + if newColour != _materialAmbient + { + setMaterialColour(.frontAndBack, GL_AMBIENT, newColour) + _materialAmbient = newColour + } + } + } + + var materialDiffuse: Colour + { + get { _materialDiffuse } + set(newColour) + { + if newColour != _materialDiffuse + { + setMaterialColour(.frontAndBack, GL_DIFFUSE, newColour) + _materialDiffuse = newColour + } + } + } + + var materialSpecular: Colour + { + get { _materialSpecular } + set(newColour) + { + if newColour != _materialSpecular + { + setMaterialColour(.frontAndBack, GL_SPECULAR, newColour) + _materialSpecular = newColour + } + } + } + + var materialEmmision: Colour + { + get { _materialEmmision } + set(newColour) + { + if newColour != _materialEmmision + { + setMaterialColour(.frontAndBack, GL_EMISSION, newColour) + _materialEmmision = newColour + } + } + } + + private var _materialShininess: Float = 0 + + var materialShininess: Float + { + get { _materialShininess } + set(newParam) + { + if newParam != _materialShininess + { + let face = glFace(.frontAndBack) + glMaterialf(face, GLenum(GL_SHININESS), newParam) + _materialShininess = newParam + } + } + } } diff --git a/Sources/JolkEngine/Renderer/Renderer.swift b/Sources/JolkEngine/Renderer/Renderer.swift index 40f9551..ab551fc 100644 --- a/Sources/JolkEngine/Renderer/Renderer.swift +++ b/Sources/JolkEngine/Renderer/Renderer.swift @@ -1,4 +1,5 @@ import Foundation +import Maths import OpenGL.GL @@ -16,9 +17,8 @@ public protocol Renderer func setVsync(mode: VSyncMode) throws func createMesh(mesh: Mesh) throws -> RenderMesh - 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 createTexture(image: Image) throws -> RenderTexture2D + func createTexture(image: Image, filter: FilterMode, mipMode: MipMode) throws -> RenderTexture2D func deleteMesh(_ mesh: RenderMesh) func deleteTexture(_ texture: RenderTexture2D) diff --git a/Sources/JolkEngine/Util/FileHandle.swift b/Sources/JolkEngine/Util/FileHandle.swift index 4d82362..804d853 100644 --- a/Sources/JolkEngine/Util/FileHandle.swift +++ b/Sources/JolkEngine/Util/FileHandle.swift @@ -4,7 +4,7 @@ import Foundation extension FileHandle { // FixedWidthInteger or BinaryFloatingPoint - func read(as: T.Type) throws -> T + func read(as: T.Type) throws -> T { let size = MemoryLayout.size guard let data = try self.read(upToCount: size) diff --git a/Sources/Maths/FloatExtensions.swift b/Sources/Maths/FloatExtensions.swift new file mode 100644 index 0000000..6d491a0 --- /dev/null +++ b/Sources/Maths/FloatExtensions.swift @@ -0,0 +1,62 @@ +import Foundation + + +public extension FloatingPoint +{ + @inline(__always) var saturate: Self { min(max(self , 0), 1) } + + @inline(__always) static func lerp(_ a: Self, _ b: Self, _ x: Self) -> Self { a * (1 - x) + b * x } + + @inline(__always) static func deg(fromRad: Self) -> Self { fromRad * (180 / Self.pi) } + @inline(__always) static func rad(fromDeg: Self) -> Self { fromDeg * (Self.pi / 180) } + + @inline(__always) func smoothStep() -> Self + { + let x = self.saturate + return x * x * (3 - 2 * x) + } + + func smoothStep(_ a: Self, _ b: Self) -> Self + { + let x = self.smoothStep() + return b * x + a * (1 - x) + } + + @inline(__always) func smootherStep() -> Self + { + let x = self.saturate + return x * x * x * (x * (x * 6 - 15) + 10) + } + + func smootherStep(_ a: Self, _ b: Self) -> Self + { + let x = self.smootherStep() + return b * x + a * (1 - x) + } + + func sqrInterp(_ a: Self, _ b: Self) -> Self + { + let x = self.saturate, xx = x * x + return a * (1 - xx) + b * xx + } + + func invSqrInterp(_ a: Self, _ b: Self) -> Self + { + let x = 1 - self.saturate, xx = x * x + return a * xx + b * (1 - xx) + } +} + +extension FloatingPoint where Self == Double +{ + @inline(__always) var sine: Self { sin(self) } + @inline(__always) var cosine: Self { cos(self) } + @inline(__always) var tangent: Self { tan(self) } +} + +extension FloatingPoint where Self == Float +{ + @inline(__always) var sine: Self { sinf(self) } + @inline(__always) var cosine: Self { cosf(self) } + @inline(__always) var tangent: Self { tanf(self) } +} diff --git a/Sources/Maths/Matrix3x3.swift b/Sources/Maths/Matrix3x3.swift new file mode 100644 index 0000000..7ba08a6 --- /dev/null +++ b/Sources/Maths/Matrix3x3.swift @@ -0,0 +1,99 @@ +#if USE_SIMD +import simd +#endif + + +#if USE_SIMD +public typealias Matrix3x3 = simd_float3x3 +public extension Matrix3x3 +{ + typealias T = Float +} +#else +public struct Matrix3x3: Equatable +{ + public var m00: T, m01: T, m02: T + public var m10: T, m11: T, m12: T + public var m20: T, m21: T, m22: T + + public init() + { + self.m00 = 1; self.m01 = 0; self.m02 = 0 + self.m10 = 0; self.m11 = 1; self.m12 = 0 + self.m20 = 0; self.m21 = 0; self.m22 = 1 + } + + public init( + _ a00: T, _ a01: T, _ a02: T, + _ a10: T, _ a11: T, _ a12: T, + _ a20: T, _ a21: T, _ a22: T) + { + self.m00 = a00; self.m01 = a01; self.m02 = a02 + self.m10 = a10; self.m11 = a11; self.m12 = a12 + self.m20 = a20; self.m21 = a21; self.m22 = a22 + } +} + +public extension Matrix3x3 +{ + init(_ row0: Vector3, _ row1: Vector3, _ row2: Vector3) + { + self.m00 = row0.x; self.m01 = row0.y; self.m02 = row0.z + self.m10 = row1.x; self.m11 = row1.y; self.m12 = row1.z + self.m20 = row2.x; self.m21 = row2.y; self.m22 = row2.z + } + + @inline(__always) var identity: Self + { + Self( + 1, 0, 0, + 0, 1, 0, + 0, 0, 1) + } + + @inline(__always) var transpose: Self + { + Self( + m00, m10, m20, + m01, m11, m21, + m02, m12, m22) + } + + static func * (lhs: Self, rhs: Self) -> Self + { + Self( + lhs.m00 * rhs.m00 + lhs.m01 * rhs.m10 + lhs.m02 * rhs.m20, + lhs.m00 * rhs.m01 + lhs.m01 * rhs.m11 + lhs.m02 * rhs.m21, + lhs.m00 * rhs.m02 + lhs.m01 * rhs.m12 + lhs.m02 * rhs.m22, + lhs.m10 * rhs.m00 + lhs.m11 * rhs.m10 + lhs.m12 * rhs.m20, + lhs.m10 * rhs.m01 + lhs.m11 * rhs.m11 + lhs.m12 * rhs.m21, + lhs.m10 * rhs.m02 + lhs.m11 * rhs.m12 + lhs.m12 * rhs.m22, + lhs.m20 * rhs.m00 + lhs.m21 * rhs.m10 + lhs.m22 * rhs.m20, + lhs.m20 * rhs.m01 + lhs.m21 * rhs.m11 + lhs.m22 * rhs.m21, + lhs.m20 * rhs.m02 + lhs.m21 * rhs.m12 + lhs.m22 * rhs.m22) + } +} +#endif + +public extension Matrix3x3 +{ + @inline(__always) static func lookAt(from: Vector3 = .zero, to: Vector3, up: Vector3 = .up) -> Self + { + let direction = (to - from).normalised + let normal = -direction + let bitangent = up.cross(normal).normalised + let tangent = normal.cross(bitangent).normalised + + return Self( + .init(bitangent.x, tangent.x, normal.x), + .init(bitangent.y, tangent.y, normal.y), + .init(bitangent.z, tangent.z, normal.z)) + } +} + +#if USE_SIMD +public typealias Mat3f = Matrix3x3 +#else +public typealias Mat3f = Matrix3x3 +public typealias Mat3d = Matrix3x3 +#endif diff --git a/Sources/Maths/Matrix4x4.swift b/Sources/Maths/Matrix4x4.swift new file mode 100644 index 0000000..daab1ee --- /dev/null +++ b/Sources/Maths/Matrix4x4.swift @@ -0,0 +1,543 @@ +#if USE_SIMD +import simd +#endif + + +#if USE_SIMD + +public typealias Matrix4x4 = simd_float4x4 +public extension Matrix4x4 +{ + typealias T = Float +} + +#else + +public struct Matrix4x4: Equatable +{ + public typealias Scalar = T + + public var m00: T, m01: T, m02: T, m03: T + public var m10: T, m11: T, m12: T, m13: T + public var m20: T, m21: T, m22: T, m23: T + public var m30: T, m31: T, m32: T, m33: T + + public init() + { + self.m00 = 1; self.m01 = 0; self.m02 = 0; self.m03 = 0 + self.m10 = 0; self.m11 = 1; self.m12 = 0; self.m13 = 0 + self.m20 = 0; self.m21 = 0; self.m22 = 1; self.m23 = 0 + self.m30 = 0; self.m31 = 0; self.m32 = 0; self.m33 = 1 + } + + public init( + _ a00: T, _ a01: T, _ a02: T, _ a03: T, + _ a10: T, _ a11: T, _ a12: T, _ a13: T, + _ a20: T, _ a21: T, _ a22: T, _ a23: T, + _ a30: T, _ a31: T, _ a32: T, _ a33: T) + { + self.m00 = a00; self.m01 = a01; self.m02 = a02; self.m03 = a03 + self.m10 = a10; self.m11 = a11; self.m12 = a12; self.m13 = a13 + self.m20 = a20; self.m21 = a21; self.m22 = a22; self.m23 = a23 + self.m30 = a30; self.m31 = a31; self.m32 = a32; self.m33 = a33 + } +} + +public extension Matrix4x4 +{ + init(diagonal d: Vector4) + { + self.m00 = d.x; self.m01 = 0; self.m02 = 0; self.m03 = 0 + self.m10 = 0; self.m11 = d.y; self.m12 = 0; self.m13 = 0 + self.m20 = 0; self.m21 = 0; self.m22 = d.z; self.m23 = 0 + self.m30 = 0; self.m31 = 0; self.m32 = 0; self.m33 = d.w + + } + + init(_ row0: Vector4, _ row1: Vector4, _ row2: Vector4, _ row3: Vector4) + { + self.m00 = row0.x; self.m01 = row0.y; self.m02 = row0.z; self.m03 = row0.w + self.m10 = row1.x; self.m11 = row1.y; self.m12 = row1.z; self.m13 = row1.w + self.m20 = row2.x; self.m21 = row2.y; self.m22 = row2.z; self.m23 = row2.w + self.m30 = row3.x; self.m31 = row3.y; self.m32 = row3.z; self.m33 = row3.w + } + + /* + init(_ col0: Vector4, _ col1: Vector4, _ col2: Vector4, _ col3: Vector4) + { + self.m00 = col0.x; self.m01 = col1.x; self.m02 = col2.x; self.m03 = col3.x + self.m10 = col0.y; self.m11 = col1.y; self.m12 = col2.y; self.m13 = col3.y + self.m20 = col0.z; self.m21 = col1.z; self.m22 = col2.z; self.m23 = col3.z + self.m30 = col0.w; self.m31 = col1.w; self.m32 = col2.w; self.m33 = col3.w + } + */ + + subscript(index: Int) -> Vector4 + { + get + { + switch index + { + case 0: rows.0 + case 1: rows.1 + case 2: rows.2 + case 3: rows.3 + default: fatalError() + } + } + } + + var rows: (Vector4, Vector4, Vector4, Vector4) + {( + .init(m00, m01, m02, m03), + .init(m10, m11, m12, m13), + .init(m20, m21, m22, m23), + .init(m30, m31, m32, m33)) + } + + var columns: (Vector4, Vector4, Vector4, Vector4) + {( + .init(m00, m10, m20, m30), + .init(m01, m11, m21, m31), + .init(m02, m12, m22, m32), + .init(m03, m13, m23, m33)) + } + + var transpose: Self + { + Self( + m00, m10, m20, m30, + m01, m11, m21, m31, + m02, m12, m22, m32, + m03, m13, m23, m33) + } + + static func * (lhs: Self, rhs: Self) -> Self + { + let l = rhs, r = lhs + return Self( + l.m00 * r.m00 + l.m01 * r.m10 + l.m02 * r.m20 + l.m03 * r.m30, + l.m00 * r.m01 + l.m01 * r.m11 + l.m02 * r.m21 + l.m03 * r.m31, + l.m00 * r.m02 + l.m01 * r.m12 + l.m02 * r.m22 + l.m03 * r.m32, + l.m00 * r.m03 + l.m01 * r.m13 + l.m02 * r.m23 + l.m03 * r.m33, + l.m10 * r.m00 + l.m11 * r.m10 + l.m12 * r.m20 + l.m13 * r.m30, + l.m10 * r.m01 + l.m11 * r.m11 + l.m12 * r.m21 + l.m13 * r.m31, + l.m10 * r.m02 + l.m11 * r.m12 + l.m12 * r.m22 + l.m13 * r.m32, + l.m10 * r.m03 + l.m11 * r.m13 + l.m12 * r.m23 + l.m13 * r.m33, + l.m20 * r.m00 + l.m21 * r.m10 + l.m22 * r.m20 + l.m23 * r.m30, + l.m20 * r.m01 + l.m21 * r.m11 + l.m22 * r.m21 + l.m23 * r.m31, + l.m20 * r.m02 + l.m21 * r.m12 + l.m22 * r.m22 + l.m23 * r.m32, + l.m20 * r.m03 + l.m21 * r.m13 + l.m22 * r.m23 + l.m23 * r.m33, + l.m30 * r.m00 + l.m31 * r.m10 + l.m32 * r.m20 + l.m33 * r.m30, + l.m30 * r.m01 + l.m31 * r.m11 + l.m32 * r.m21 + l.m33 * r.m31, + l.m30 * r.m02 + l.m31 * r.m12 + l.m32 * r.m22 + l.m33 * r.m32, + l.m30 * r.m03 + l.m31 * r.m13 + l.m32 * r.m23 + l.m33 * r.m33) + } +} + +#endif + +#if USE_SIMD + +public extension Matrix4x4 +{ + init(quat q: Quaternion) + { + self = .init(simd_quatf(ix: q.x, iy: q.y, iz: q.z, r: q.w)).transpose + /* + let FUCK = simd_matrix4x4(simd_quatf(ix: q.x, iy: q.y, iz: q.z, r: q.w)) + self = .init( + FUCK.columns.0.x, FUCK.columns.1.x, FUCK.columns.2.x, FUCK.columns.3.x, + FUCK.columns.0.y, FUCK.columns.1.y, FUCK.columns.2.y, FUCK.columns.3.y, + FUCK.columns.0.z, FUCK.columns.1.z, FUCK.columns.2.z, FUCK.columns.3.z, + FUCK.columns.0.w, FUCK.columns.1.w, FUCK.columns.2.w, FUCK.columns.3.w).transpose + */ + } +} + +#else + +public extension Matrix4x4 where T == Float +{ + init(_ m3: Matrix3x3) + { + self.m00 = m3.m00; self.m01 = m3.m01; self.m02 = m3.m02; self.m03 = 0 + self.m10 = m3.m10; self.m11 = m3.m11; self.m12 = m3.m12; self.m13 = 0 + self.m20 = m3.m20; self.m21 = m3.m21; self.m22 = m3.m22; self.m23 = 0 + self.m30 = 0; self.m31 = 0; self.m32 = 0; self.m33 = 1 + } + + init(quat q: Quaternion) + { + /* + let qq = Quaternion(q.w * q.w, q.x * q.x, q.y * q.y, q.z * q.z) + return .init( + .init( + qq.w + qq.x - qq.y - qq.z, + 2.0 * (q.x * q.y - q.w * q.z), + 2.0 * (q.x * q.z + q.w * q.y), + 0.0), + .init( + 2.0 * (q.x * q.y + q.w * q.z), + qq.w - qq.x + qq.y - qq.z, + 2.0 * (q.y * q.z - q.w * q.x), + 0.0), + .init( + 2.0 * (q.x * q.z - q.w * q.y), + 2.0 * (q.y * q.z + q.w * q.x), + qq.w - qq.x - qq.y + qq.z, + 0.0), + .init(0.0, 0.0, 0.0, 1.0)) + */ + /* + let qww = q.w * q.w + let qxx = q.x * q.x + let qyy = q.y * q.y + let qzz = q.z * q.z + .init( + .init( + qqw + qqx - qqy - qqz, + 2.0 * (q.x * q.y - q.w * q.z), + 2.0 * (q.x * q.z + q.w * q.y), + 0.0), + .init( + 2.0 * (q.x * q.y + q.w * q.z), + qqw - qqx + qqy - qqz, + 2.0 * (q.y * q.z - q.w * q.x), + 0.0), + .init( + 2.0 * (q.x * q.z - q.w * q.y), + 2.0 * (q.y * q.z + q.w * q.x), + qqw - qqx - qqy + qqz, + 0.0), + .init(0.0, 0.0, 0.0, 1.0)) + */ + let qww = q.w * q.w, qxx = q.x * q.x, qyy = q.y * q.y, qzz = q.z * q.z + let qxy = q.x * q.y, qxz = q.x * q.z, qyz = q.y * q.z + let qwx = q.w * q.x, qwy = q.w * q.y, qwz = q.w * q.z + self = .init( + .init(qww + qxx - qyy - qzz, 2 * (qxy - qwz), 2 * (qxz + qwy), 0), + .init( 2 * (qxy + qwz), qww - qxx + qyy - qzz, 2 * (qyz - qwx), 0), + .init( 2 * (qxz - qwy), 2 * (qyz + qwx), qww - qxx - qyy + qzz, 0), + .init( 0, 0, 0, 1)).transpose + } + + @inline(__always) static var identity: Self { Self(diagonal: .one) } +} + +#endif + + +public extension Matrix4x4 +{ + // what the fuck lmao + @inline(__always) static func * (lhs: Self, rhs: Vector3) -> Vector3 + { + return .init( + lhs.columns.0.x * rhs.x + lhs.columns.0.y * rhs.y + lhs.columns.0.z * rhs.z, + lhs.columns.1.x * rhs.x + lhs.columns.1.y * rhs.y + lhs.columns.1.z * rhs.z, + lhs.columns.2.x * rhs.x + lhs.columns.2.y * rhs.y + lhs.columns.2.z * rhs.z) + } +} + +#if USE_SIMD + +public extension Matrix4x4 +{ + @inline(__always) static func translate(_ v: Vector3) -> Self + { + Self( + .init( 1, 0, 0, 0), + .init( 0, 1, 0, 0), + .init( 0, 0, 1, 0), + .init(v.x, v.y, v.z, 1)) + } + + @inline(__always) static func scale(_ v: Vector3) -> Self + { + Self( + .init(v.x, 0, 0, 0), + .init( 0, v.y, 0, 0), + .init( 0, 0, v.z, 0), + .init( 0, 0, 0, 1)) + } + + @inline(__always) static func scale(scalar v: T) -> Self + { + Self( + .init(v, 0, 0, 0), + .init(0, v, 0, 0), + .init(0, 0, v, 0), + .init(0, 0, 0, 1)) + } + + @inline(__always) static func rotate(yawPitch: Vector2) -> Self { return rotate(yaw: yawPitch.x, pitch: yawPitch.y) } + + static func rotate(yaw ytheta: T, pitch xtheta: T) -> Self + { + let xc = xtheta.cosine, xs = xtheta.sine + let yc = ytheta.cosine, ys = ytheta.sine + + return Self( + .init(yc, ys * xs, -ys * xc, 0), + .init( 0, xc, xs, 0), + .init(ys, yc * -xs, yc * xc, 0), + .init( 0, 0, 0, 1)) + } + + @inline(__always) static func rotate(x theta: T) -> Self + { + let c = theta.cosine, s = theta.sine + return Self( + .init(1, 0, 0, 0), + .init(0, c, s, 0), + .init(0, -s, c, 0), + .init(0, 0, 0, 1)) + } + + @inline(__always) static func rotate(y theta: T) -> Self + { + let c = theta.cosine, s = theta.sine + return Self( + .init(c, 0, -s, 0), + .init(0, 1, 0, 0), + .init(s, 0, c, 0), + .init(0, 0, 0, 1)) + } + + @inline(__always) static func rotate(z theta: T) -> Self + { + let c = theta.cosine, s = theta.sine + return Self( + .init(c, -s, 0, 0), + .init(s, c, 0, 0), + .init(0, 0, 1, 0), + .init(0, 0, 0, 1)) + } + + static func perspective(fovY: T, aspect: T, zNear: T, zFar: T) -> Self + { + let h = 1 / (fovY * T(0.5)).tangent + let w = h / aspect + let invClipRange = 1 / (zFar - zNear) + let z = -(zFar + zNear) * invClipRange + let z2 = -(2 * zFar * zNear) * invClipRange + return Self( + .init(w, 0, 0, 0), + .init(0, h, 0, 0), + .init(0, 0, z, -1), + .init(0, 0, z2, 0)) + } + + static func lookAt(from: Vector3 = .zero, to: Vector3, up: Vector3 = .up) -> Self + { + let forward = (to - from).normalised + let bitangent = forward.cross(up).normalised + let tangent = bitangent.cross(forward).normalised + let normal = -forward + + return Self( + .init(bitangent, 0), + .init( tangent, 0), + .init( normal, 0), + .init(0, 0, 0, 1)) + } +} + +#else + +public extension Matrix4x4 where T == Float +{ + @inline(__always) static func translate(_ v: Vector3) -> Self + { + Self( + .init( 1, 0, 0, 0), + .init( 0, 1, 0, 0), + .init( 0, 0, 1, 0), + .init(v.x, v.y, v.z, 1)) + } + + @inline(__always) static func scale(_ v: Vector3) -> Self + { + Self( + .init(v.x, 0, 0, 0), + .init( 0, v.y, 0, 0), + .init( 0, 0, v.z, 0), + .init( 0, 0, 0, 1)) + } + + @inline(__always) static func scale(scalar v: T) -> Self + { + Self( + .init(v, 0, 0, 0), + .init(0, v, 0, 0), + .init(0, 0, v, 0), + .init(0, 0, 0, 1)) + } + + static func rotate(axis v: Vector3, angle theta: T) -> Self + { + //FIXME: THIS IS FUCKED UP FOR EVERYTHING OTHER THAN X AXIS ROTATION LOL + /* + let vv = v * v + let xy = v.x * v.y, xz = v.x * v.z, yz = v.y * v.z + let ts = sin(theta), tc = cos(theta) + return Self( + .init( + vv.x + (tc * (1 - vv.x)), + (xz - (tc * xy)) + (ts * v.z), + (xz - (tc * xz)) - (ts * v.y), 0), + .init( + (xy - (tc * xy)) - (ts * v.z), + vv.y + (tc * (1 - vv.y)), + (xz - (tc * xz)) + (ts * v.x), 0), + .init( + (xz - (tc * xz)) + (ts * v.y), + (xz - (tc * xz)) - (ts * v.y), + vv.z + (tc * (1 - vv.z)), 1), + .init(0, 0, 0, 1)) + */ + let vxx = v.x * v.x, vxy = v.x * v.y, vxz = v.x * v.z + let vyy = v.y * v.y, vyz = v.y * v.z + let vzz = v.z * v.z + let ts = theta.sine, tc = theta.cosine + let ic = 1 - tc + return Self( + .init( + ic * vxx + tc, + ic * vxy - v.z * ts, + ic * vxz + v.z * ts, + 0), + .init( + ic * vxy + v.z * ts, + ic * vyy + tc, + ic * vyz - v.x * ts, + 0), + .init( + ic * vxz - v.y * ts, + ic * vyz + v.x * ts, + ic * vzz + tc, + 0), + .init(0, 0, 0, 1)) + } + + @inline(__always) static func rotate(yawPitch: Vector2) -> Self { return rotate(yaw: yawPitch.x, pitch: yawPitch.y) } + + static func rotate(yaw ytheta: T, pitch xtheta: T) -> Self + { + let xc = xtheta.cosine, xs = xtheta.sine + let yc = ytheta.cosine, ys = ytheta.sine + + return Self( + .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 rotate(yaw ytheta: T, pitch xtheta: T, roll ztheta: T) -> Self + { + //FIXME: this doesn't null against control + let xc = xtheta.cosine, xs = xtheta.sine + let yc = ytheta.cosine, ys = ytheta.sine + let zc = ztheta.cosine, zs = ztheta.sine + + let ysxs = ys * xs, ycxs = yc * xs + + let result = Mat4f( + .init(yc * zc + ysxs * zs, yc * -zs + ysxs * zc, -ys * xc, 0), + .init( xc * zs, xc * zc, xs, 0), + .init(ys * zc - ycxs * zs, ys * -zs - ycxs * zc, yc * xc, 0), + .init( 0, 0, 0, 1)) + let shouldBe = .rotate(z: ztheta) * .rotate(x: xtheta) * .rotate(y: ytheta) + + let epsilon: Float = .ulpOfOne + if (result != shouldBe) + { + assert(abs(result[0][0] - shouldBe[0][0]) <= epsilon) // epsilon + assert(result[1][0] == shouldBe[1][0]) + assert(abs(result[2][0] - shouldBe[2][0]) <= epsilon) // epsilon + assert(result[3][0] == shouldBe[3][0]) + assert(abs(result[0][1] - shouldBe[0][1]) <= epsilon) // epsilon + assert(result[1][1] == shouldBe[1][1]) + assert(abs(result[2][1] - shouldBe[2][1]) <= epsilon) // epsilon + assert(result[3][1] == shouldBe[3][1]) + assert(result[0][2] == shouldBe[0][2]) + assert(result[1][2] == shouldBe[1][2]) + assert(result[2][2] == shouldBe[2][2]) + assert(result[3][2] == shouldBe[3][2]) + assert(result[0][3] == shouldBe[0][3]) + assert(result[1][3] == shouldBe[1][3]) + assert(result[2][3] == shouldBe[2][3]) + assert(result[3][3] == shouldBe[3][3]) + } + return result + } + + @inline(__always) static func rotate(x theta: T) -> Self + { + let c = theta.cosine, s = theta.sine + return Self( + .init(1, 0, 0, 0), + .init(0, c, s, 0), + .init(0, -s, c, 0), + .init(0, 0, 0, 1)) + } + + @inline(__always) static func rotate(y theta: T) -> Self + { + let c = theta.cosine, s = theta.sine + return Self( + .init(c, 0, -s, 0), + .init(0, 1, 0, 0), + .init(s, 0, c, 0), + .init(0, 0, 0, 1)) + } + + @inline(__always) static func rotate(z theta: T) -> Self + { + let c = theta.cosine, s = theta.sine + return Self( + .init(c, -s, 0, 0), + .init(s, c, 0, 0), + .init(0, 0, 1, 0), + .init(0, 0, 0, 1)) + } + + static func perspective(fovY: T, aspect: T, zNear: T, zFar: T) -> Self + { + let h = 1 / (fovY * 0.5).tangent + let w = h / aspect + let invClipRange = 1 / (zFar - zNear) + let z = -(zFar + zNear) * invClipRange + let z2 = -(2 * zFar * zNear) * invClipRange + return Self( + .init(w, 0, 0, 0), + .init(0, h, 0, 0), + .init(0, 0, z, -1), + .init(0, 0, z2, 0)) + } + + static func lookAt(from: Vector3 = .zero, to: Vector3, up: Vector3 = .up) -> Self + { + let forward = (to - from).normalised + let bitangent = forward.cross(up).normalised + let tangent = bitangent.cross(forward).normalised + let normal = -forward + + return Self( + .init(bitangent, 0), + .init( tangent, 0), + .init( normal, 0), + .init(0, 0, 0, 1)) + } +} + +#endif + +#if USE_SIMD +public typealias Mat4f = Matrix4x4 +#else +public typealias Mat4f = Matrix4x4 +public typealias Mat4d = Matrix4x4 +#endif diff --git a/Sources/Maths/Quaternion.swift b/Sources/Maths/Quaternion.swift new file mode 100644 index 0000000..a7851c8 --- /dev/null +++ b/Sources/Maths/Quaternion.swift @@ -0,0 +1,333 @@ +import Foundation +import simd + + +public struct Quaternion: Equatable +{ + public var w: T, x: T, y: T, z: T + + public init() { self.x = 0; self.y = 0; self.z = 0; self.w = 1 } + public init(_ w: T, _ x: T, _ y: T, _ z: T) { self.w = w; self.x = x; self.y = y; self.z = z } + + public static var identity: Self { .init(1, 0, 0, 0) } +} + +public extension Quaternion where T: SIMDScalar +{ + init(real w: T, imaginary i: Vector3) { self.w = w; self.x = i.x; self.y = i.y; self.z = i.z } +} + +#if USE_SIMD + +public extension Quaternion where T == Float +{ + init(_ q: simd_quatf) + { + self.init(real: q.real, imaginary: q.imag) + } + + static func lookAt(from: Vector3 = .zero, to: Vector3, up: Vector3 = .up) -> Self + { + let m3 = Matrix3x3.lookAt(from: from, to: to, up: up) + let et_blee_eve_nyu = simd_quatf(m3) + return Self(real: et_blee_eve_nyu.real, imaginary: .init( + et_blee_eve_nyu.imag.x, + et_blee_eve_nyu.imag.y, + et_blee_eve_nyu.imag.z)) + } +} + +#else + +public extension Quaternion where T == Float +{ + init(_ m3: Matrix3x3) + { + if m3.m22 < 0 + { + if m3.m00 > m3.m11 + { + let t = 1 + m3.m00 - m3.m11 - m3.m22 + self.x = t.squareRoot() * 0.5 + if m3.m21 < m3.m12 { self.x = -self.x } + let ix4 = 1 / (4 * self.x) + self.w = (m3.m21 - m3.m12) * ix4 + self.y = (m3.m10 + m3.m01) * ix4 + self.z = (m3.m02 + m3.m20) * ix4 + if t == 1 && self.w == 0 && self.y == 0 && self.z == 0 { self.x = 1 } + } + else + { + let t = 1 - m3.m00 + m3.m11 - m3.m22 + self.y = t.squareRoot() * 0.5 + if m3.m02 < m3.m20 { self.y = -self.y } + let iy4 = 1 / (4 * self.y) + self.w = (m3.m02 - m3.m20) * iy4 + self.x = (m3.m10 + m3.m01) * iy4 + self.z = (m3.m21 + m3.m12) * iy4 + if t == 1 && self.w == 0 && self.x == 0 && self.z == 0 { self.y = 1 } + } + } + else + { + if m3.m00 < -m3.m11 + { + let t = 1 - m3.m00 - m3.m11 + m3.m22 + self.z = t.squareRoot() * 0.5 + if m3.m10 < m3.m01 { self.z = -self.z } + let iz4 = 1 / (4 * self.z) + self.w = (m3.m10 - m3.m01) * iz4 + self.x = (m3.m02 + m3.m20) * iz4 + self.y = (m3.m21 + m3.m12) * iz4 + if t == 1 && self.w == 0 && self.x == 0 && self.y == 0 { self.z = 1 } + } + else + { + let t = 1 + m3.m00 + m3.m11 + m3.m22 + self.w = t.squareRoot() * 0.5 + let iw4 = 1 / (4 * self.w) + self.x = (m3.m21 - m3.m12) * iw4 + self.y = (m3.m02 - m3.m20) * iw4 + self.z = (m3.m10 - m3.m01) * iw4 + if t == 1 && self.x == 0 && self.y == 0 && self.z == 0 { self.w = 1 } + } + } + + /* + let m3 = m3FUCK.transpose + if m3.m22 < 0 + { + if m3.m00 > m3.m11 + { + let t = 1 + m3.m00 - m3.m11 - m3.m22 + self.x = t.squareRoot() * 0.5 + if m3.m12 < m3.m21 { self.x = -self.x } + let ix4 = 1 / (4 * self.x) + self.w = (m3.m12 - m3.m21) * ix4 + self.y = (m3.m01 + m3.m10) * ix4 + self.z = (m3.m20 + m3.m02) * ix4 + if t == 1 && self.w == 0 && self.y == 0 && self.z == 0 { self.x = 1 } + } + else + { + let t = 1 - m3.m00 + m3.m11 - m3.m22 + self.y = t.squareRoot() * 0.5 + if m3.m20 < m3.m02 { self.y = -self.y } + let iy4 = 1 / (4 * self.y) + self.w = (m3.m20 - m3.m02) * iy4 + self.x = (m3.m01 + m3.m10) * iy4 + self.z = (m3.m12 + m3.m21) * iy4 + if t == 1 && self.w == 0 && self.x == 0 && self.z == 0 { self.y = 1 } + } + } + else + { + if m3.m00 < -m3.m11 + { + let t = 1 - m3.m00 - m3.m11 + m3.m22 + self.z = t.squareRoot() * 0.5 + if m3.m01 < m3.m10 { self.z = -self.z } + let iz4 = 1 / (4 * self.z) + self.w = (m3.m01 - m3.m10) * iz4 + self.x = (m3.m20 + m3.m02) * iz4 + self.y = (m3.m12 + m3.m21) * iz4 + if t == 1 && self.w == 0 && self.x == 0 && self.y == 0 { self.z = 1 } + } + else + { + let t = 1 + m3.m00 + m3.m11 + m3.m22 + self.w = t.squareRoot() * 0.5 + let iw4 = 1 / (4 * self.w) + self.x = (m3.m12 - m3.m21) * iw4 + self.y = (m3.m20 - m3.m02) * iw4 + self.z = (m3.m01 - m3.m10) * iw4 + if t == 1 && self.x == 0 && self.y == 0 && self.z == 0 { self.w = 1 } + } + } + */ + + + /* + self.w = (1 + m3.m00 + m3.m11 + m3.m22).squareRoot() * 0.5 + let iw4 = 1 / (4 * self.w) + self.x = (m3.m21 - m3.m12) * iw4 + self.y = (m3.m02 - m3.m20) * iw4 + self.z = (m3.m10 - m3.m01) * iw4 + */ + /* + if m3.m22 < 0 + { + if m3.m00 > m3.m11 + { + self.w = (1 + m3.m00 - m3.m11 - m3.m22).squareRoot() * 0.5 + let iw4 = 1 / (4 * self.w) + //self.x = (m3.m01 + m3.m10) * iw4 + //self.y = (m3.m20 + m3.m02) * iw4 + //self.z = (m3.m12 - m3.m21) * iw4 + self.x = (m3.m21 - m3.m12) * iw4 + self.y = (m3.m02 - m3.m20) * iw4 + self.z = (m3.m10 - m3.m01) * iw4 + } + else + { + self.x = (1 - m3.m00 + m3.m11 - m3.m22).squareRoot() * 0.5 + let ix4 = 1 / (4 * self.x) + //self.w = (m3.m01 + m3.m10) * ix4 + //self.y = (m3.m12 + m3.m21) * ix4 + //self.z = (m3.m20 - m3.m02) * ix4 + self.z = (m3.m02 - m3.m20) * ix4 + self.y = (m3.m21 - m3.m12) * ix4 + self.w = (m3.m10 - m3.m01) * ix4 + } + } + else + { + if m3.m00 < -m3.m11 + { + self.y = (1 - m3.m00 - m3.m11 + m3.m22).squareRoot() * 0.5 + let iy4 = 1 / (4 * self.y) + //self.w = (m3.m20 + m3.m02) * iy4 + //self.x = (m3.m12 + m3.m21) * iy4 + //self.z = (m3.m01 - m3.m10) * iy4 + self.z = (m3.m10 - m3.m01) * iy4 + self.x = (m3.m21 - m3.m12) * iy4 + self.w = (m3.m02 - m3.m20) * iy4 + } + else + { + self.z = (1 + m3.m00 + m3.m11 + m3.m22).squareRoot() * 0.5 + let iz4 = 1 / (4 * self.z) + //self.w = (m3.m12 - m3.m21) * iz4 + //self.x = (m3.m20 - m3.m02) * iz4 + //self.y = (m3.m01 - m3.m10) * iz4 + self.y = (m3.m10 - m3.m01) * iz4 + self.x = (m3.m02 - m3.m20) * iz4 + self.w = (m3.m21 - m3.m12) * iz4 + } + } + */ + } + + static func lookAt(from: Vector3 = .zero, to: Vector3, up: Vector3 = .up) -> Self + { + Self(Matrix3x3.lookAt(from: from, to: to, up: up)) + } +} + +#endif + + +public extension Quaternion where T == Float +{ + init(axis v: Vector3, angle theta: T) + { + let tc = (theta * 0.5).cosine, ts = (theta * 0.5).sine + self.init(real: tc, imaginary: v * ts) + } + + @inline(__always) func slerp(_ rhs: Self, _ theta: T) -> Self + { + var b = rhs + var cosHalfTheta = self.dot(b) + if abs(cosHalfTheta) >= 1 + { + // if a == b or a == -b then theta == 0 + return self + } + else if cosHalfTheta < 0 + { + b = -b + cosHalfTheta = -cosHalfTheta + } + let halfTheta = acos(cosHalfTheta) + let sinHalfTheta = (1 - cosHalfTheta * cosHalfTheta).squareRoot() + if abs(sinHalfTheta) <= .ulpOfOne + { + // if theta == 180 degrees then result is not fully defined + // we could rotate around any axis normal to a or b + return (self + b) * 0.5 + } + let ratioA = ((1 - theta) * halfTheta).sine / sinHalfTheta + let ratioB = (theta * halfTheta).sine / sinHalfTheta + return self * ratioA + b * ratioB + //let piss = simd_quatf(ix: x, iy: y, iz: z, r: w) + //let shit = simd_quatf(ix: b.x, iy: b.y, iz: b.z, r: b.w) + //let cunt = simd_slerp(piss, shit, x) + //return .init(real: cunt.real, imaginary: cunt.imag) + } + + // Because swift type checking brain injury + @inline(__always) static func * (a: Self, b: Self) -> Self + { + .init( + a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z, + a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, + a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x, + a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w) + } +} + +public extension Quaternion where T: FloatingPoint +{ + @inline(__always) static prefix func - (this: Self) -> Self + { + .init(-this.w, -this.x, -this.y, -this.z) + } + + @inline(__always) static func + (lhs: Self, rhs: Self) -> Self + { + .init(lhs.w + rhs.w, lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z) + } + + @inline(__always) static func * (lhs: Self, rhs: T) -> Self + { + .init(lhs.w * rhs, lhs.x * rhs, lhs.y * rhs, lhs.z * rhs) + } + + @inline(__always) mutating func normalise() + { + let invMag = 1 / self.dot(self).squareRoot() + self.w *= invMag + self.x *= invMag + self.y *= invMag + self.z *= invMag + } + + @inline(__always) var normalised: Self + { + let invMag = 1 / self.dot(self).squareRoot() + return self * invMag + } + + @inline(__always) func dot(_ b: Self) -> T { x * b.x + y * b.y + z * b.z + w * b.w } +} + + +#if USE_SIMD + +public extension Vector3 where Scalar == Float +{ + @inline(__always) static func * (v: Self, q: Quaternion) -> Self + { + return v * simd_float3x3(simd_quatf(ix: q.x, iy: q.y, iz: q.z, r: q.w)) + } +} + +#else + +public extension Vector3 where T: BinaryFloatingPoint +{ + @inline(__always) static func * (v: Self, q: Quaternion) -> Self + { + let qw = q.w, qv = Self(q.x, q.y, q.z) + + var out = qv * 2.0 * qv.dot(v) + out += v * (qw * qw - qv.dot(qv)) + return out + qv.cross(v) * 2.0 * qw + } +} + +#endif + +public typealias Quatf = Quaternion +public typealias Quatd = Quaternion diff --git a/Sources/Maths/Vector2.swift b/Sources/Maths/Vector2.swift new file mode 100644 index 0000000..4946fc2 --- /dev/null +++ b/Sources/Maths/Vector2.swift @@ -0,0 +1,98 @@ +import Foundation +#if USE_SIMD +import simd +#endif + + +#if USE_SIMD +public typealias Vector2 = SIMD2 +#else + +public struct Vector2: Equatable +{ + public typealias Scalar = T + + public var x, y: T + + public init() { self.x = 0; self.y = 0 } + public init(_ x: T, _ y: T) { self.x = x; self.y = y } +} + +public extension Vector2 +{ + // constants + @inline(__always) static var zero: Self { Self(0, 0) } + @inline(__always) static var one: Self { Self(1, 1) } + + subscript(index: Int) -> T + { + get + { + switch index + { + case 0: x + case 1: y + default: fatalError() + } + } + } + + // relational + @inline(__always) static func == (lhs: Self, rhs: Self) -> Bool { lhs.x == rhs.x && lhs.y == rhs.y } + @inline(__always) static func != (lhs: Self, rhs: Self) -> Bool { rhs.x != rhs.x || lhs.y != rhs.y } + + // arithmetic + @inline(__always) static func + (lhs: Self, rhs: Self) -> Self { Self(lhs.x + rhs.x, lhs.y + rhs.y) } + @inline(__always) static func - (lhs: Self, rhs: Self) -> Self { Self(lhs.x - rhs.x, lhs.y - rhs.y) } + @inline(__always) static func * (lhs: Self, rhs: Self) -> Self { Self(lhs.x * rhs.x, lhs.y * rhs.y) } + @inline(__always) static func / (lhs: Self, rhs: Self) -> Self { Self(lhs.x / rhs.x, lhs.y / rhs.y) } + + // scalar arithmetic + @inline(__always) static func * (lhs: Self, rhs: T) -> Self { Self(lhs.x * rhs, lhs.y * rhs) } + @inline(__always) static func / (lhs: Self, rhs: T) -> Self { Self(lhs.x / rhs, lhs.y / rhs) } + @inline(__always) static func * (lhs: T, rhs: Self) -> Self { Self(lhs * rhs.x, lhs * rhs.y) } + @inline(__always) static func / (lhs: T, rhs: Self) -> Self { Self(lhs / rhs.x, lhs / rhs.y) } + + // compound arithmetic + @inline(__always) static func += (lhs: inout Self, rhs: Self) { lhs.x += rhs.x; lhs.y += rhs.y } + @inline(__always) static func -= (lhs: inout Self, rhs: Self) { lhs.x -= rhs.x; lhs.y -= rhs.y } + @inline(__always) static func *= (lhs: inout Self, rhs: Self) { lhs.x *= rhs.x; lhs.y *= rhs.y } + @inline(__always) static func /= (lhs: inout Self, rhs: Self) { lhs.x /= rhs.x; lhs.y /= rhs.y } + + // compound scalar arithmetic + @inline(__always) static func *= (lhs: inout Self, rhs: T) { lhs.x *= rhs; lhs.y *= rhs } + @inline(__always) static func /= (lhs: inout Self, rhs: T) { lhs.x /= rhs; lhs.y /= rhs } + + // unary + @inline(__always) static prefix func + (vec: Self) -> Self { vec } + @inline(__always) static prefix func - (vec: Self) -> Self { Self(-vec.x, -vec.y) } +} + +#endif + +public extension Vector2 where Scalar: FloatingPoint +{ + @inline(__always) var len2: Scalar { x * x + y * y } + @inline(__always) var len: Scalar { len2.squareRoot() } + @inline(__always) var normalised: Self { let invLen = 1 / len; return self * invLen } + + @inline(__always) mutating func normalise() { let invLen = 1 / len; self *= invLen } + + @inline(__always) func dot(_ b: Self) -> Scalar { x * b.x + y * b.y } + @inline(__always) func reflect(_ n: Self) -> Self { self - (n * 2 * self.dot(n)) } + @inline(__always) func project(_ n: Self) -> Self { n * self.dot(n) } + + @inline(__always) func cross(_ b: Self) -> Scalar { x * b.y - y * b.x } + + @inline(__always) func lerp(_ b: Self, _ x: Scalar) -> Self + { + let invX = 1 - x + 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 } +} + + +public typealias Vec2f = Vector2 +public typealias Vec2d = Vector2 diff --git a/Sources/Maths/Vector3.swift b/Sources/Maths/Vector3.swift new file mode 100644 index 0000000..eed79ae --- /dev/null +++ b/Sources/Maths/Vector3.swift @@ -0,0 +1,112 @@ +#if USE_SIMD +import simd +#endif + + +#if USE_SIMD +public typealias Vector3 = SIMD3 +public extension Vector3 +{ + typealias T = Scalar +} +#else + +public struct Vector3: Equatable +{ + public typealias Scalar = T + + public var x, y, z: T + + public init() { self.x = 0; self.y = 0; self.z = 0 } + public init(_ x: T, _ y: T, _ z: T) { self.x = x; self.y = y; self.z = z } +} + +public extension Vector3 +{ + // constants + @inline(__always) static var zero: Self { Self(0, 0, 0) } + @inline(__always) static var one: Self { Self(1, 1, 1) } + + subscript(index: Int) -> T + { + get + { + switch index + { + case 0: x + case 1: y + case 2: z + default: fatalError() + } + } + } + + init(repeating v: T) { self.x = v; self.y = v; self.z = v } + + // relational + @inline(__always) static func == (lhs: Self, rhs: Self) -> Bool { lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z } + @inline(__always) static func != (lhs: Self, rhs: Self) -> Bool { lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z } + + // arithmetic + @inline(__always) static func + (lhs: Self, rhs: Self) -> Self { Self(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z) } + @inline(__always) static func - (lhs: Self, rhs: Self) -> Self { Self(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z) } + @inline(__always) static func * (lhs: Self, rhs: Self) -> Self { Self(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z) } + @inline(__always) static func / (lhs: Self, rhs: Self) -> Self { Self(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z) } + + // scalar arithmetic + @inline(__always) static func * (lhs: Self, rhs: T) -> Self { Self(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs) } + @inline(__always) static func / (lhs: Self, rhs: T) -> Self { Self(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs) } + @inline(__always) static func * (lhs: T, rhs: Self) -> Self { Self(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z) } + @inline(__always) static func / (lhs: T, rhs: Self) -> Self { Self(lhs / rhs.x, lhs / rhs.y, lhs / rhs.z) } + + // compound arithmetic + @inline(__always) static func += (lhs: inout Self, rhs: Self) { lhs.x += rhs.x; lhs.y += rhs.y; lhs.z += rhs.z } + @inline(__always) static func -= (lhs: inout Self, rhs: Self) { lhs.x -= rhs.x; lhs.y -= rhs.y; lhs.z -= rhs.z } + @inline(__always) static func *= (lhs: inout Self, rhs: Self) { lhs.x *= rhs.x; lhs.y *= rhs.y; lhs.z *= rhs.z } + @inline(__always) static func /= (lhs: inout Self, rhs: Self) { lhs.x /= rhs.x; lhs.y /= rhs.y; lhs.z /= rhs.z } + + // compound scalar arithmetic + @inline(__always) static func *= (lhs: inout Self, rhs: T) { lhs.x *= rhs; lhs.y *= rhs; lhs.z *= rhs } + @inline(__always) static func /= (lhs: inout Self, rhs: T) { lhs.x /= rhs; lhs.y /= rhs; lhs.z /= rhs } + + // unary + @inline(__always) static prefix func + (vec: Self) -> Self { vec } + @inline(__always) static prefix func - (vec: Self) -> Self { Self(-vec.x, -vec.y, -vec.z) } +} + +#endif + +public extension Vector3 where Scalar: FloatingPoint +{ + @inline(__always) static var X: Self { Self(1, 0, 0) } + @inline(__always) static var Y: Self { Self(0, 1, 0) } + @inline(__always) static var Z: Self { Self(0, 0, 1) } + + @inline(__always) static var up: Self { Y } + @inline(__always) static var down: Self { -Y } + @inline(__always) static var left: Self { -X } + @inline(__always) static var right: Self { X } + @inline(__always) static var forward: Self { -Z } + @inline(__always) static var back: Self { Z } + + @inline(__always) var len2: Scalar { x * x + y * y + z * z } + @inline(__always) var len: Scalar { len2.squareRoot() } + @inline(__always) var normalised: Self { let invLen = 1 / len; return self * invLen } + + @inline(__always) mutating func normalise() { let invLen = 1 / len; self *= invLen } + + @inline(__always) func lerp(_ b: Self, _ x: Scalar) -> Self + { + let invX = 1 - x + return Self(self.x * invX + b.x * x, self.y * invX + b.y * x, self.z * invX + b.z * x) + } + + @inline(__always) func dot(_ b: Self) -> Scalar { x * b.x + y * b.y + z * b.z } + @inline(__always) func cross(_ b: Self) -> Self { Self(y * b.z - z * b.y, z * b.x - x * b.z, x * b.y - y * b.x) } + + @inline(__always) func project(_ n: Self) -> Self { n * self.dot(n) } +} + + +public typealias Vec3f = Vector3 +public typealias Vec3d = Vector3 diff --git a/Sources/Maths/Vector4.swift b/Sources/Maths/Vector4.swift new file mode 100644 index 0000000..75fc578 --- /dev/null +++ b/Sources/Maths/Vector4.swift @@ -0,0 +1,56 @@ +#if USE_SIMD +import simd +#endif + + +#if USE_SIMD +public typealias Vector4 = SIMD4 +#else + +public struct Vector4: Equatable +{ + public typealias Scalar = T + + public var x, y, z, w: T + + public init() { self.x = 0; self.y = 0; self.z = 0; self.w = 0 } + public init(_ x: T, _ y: T, _ z: T, _ w: T) { self.x = x; self.y = y; self.z = z; self.w = w } +} + +public extension Vector4 +{ + // constants + @inline(__always) static var zero: Self { Self(0, 0, 0, 0) } + @inline(__always) static var one: Self { Self(1, 1, 1, 1) } + + subscript(index: Int) -> T + { + get + { + switch index + { + case 0: x + case 1: y + case 2: z + case 3: w + default: fatalError() + } + } + } + + init(_ xyz: Vector3, _ w: T) { self.x = xyz.x; self.y = xyz.y; self.z = xyz.z; self.w = w } +} + +#endif + +public extension Vector4 where Scalar: FloatingPoint +{ + @inline(__always) static var X: Self { Self(1, 0, 0, 0) } + @inline(__always) static var Y: Self { Self(0, 1, 0, 0) } + @inline(__always) static var Z: Self { Self(0, 0, 1, 0) } + @inline(__always) static var W: Self { Self(0, 0, 0, 1) } +} + + +public typealias Vec4f = Vector4 +public typealias Vec4d = Vector4 diff --git a/Sources/Test/CavesOfJolk.swift b/Sources/Test/CavesOfJolk.swift index 64df2a0..a6da34e 100644 --- a/Sources/Test/CavesOfJolk.swift +++ b/Sources/Test/CavesOfJolk.swift @@ -22,8 +22,8 @@ import JolkEngine class CavesOfJolk: ApplicationImplementation { - private lazy var scene = Scene1() - //private lazy var scene = CaveScene() + //private lazy var scene = Scene1() + private lazy var scene = CaveScene() private var aspect: Float = 0 private var frameCount = 0 private var frameTimer: Float = 1 diff --git a/Sources/Test/Collision.swift b/Sources/Test/Collision.swift index ead1b23..abd10e7 100644 --- a/Sources/Test/Collision.swift +++ b/Sources/Test/Collision.swift @@ -1,4 +1,5 @@ import Foundation +import Maths import JolkEngine diff --git a/Sources/Test/Objects/Actor.swift b/Sources/Test/Objects/Actor.swift index 96ed691..9a6fa9f 100644 --- a/Sources/Test/Objects/Actor.swift +++ b/Sources/Test/Objects/Actor.swift @@ -1,4 +1,6 @@ import JolkEngine +import Maths + protocol Actor { diff --git a/Sources/Test/Objects/Colin.swift b/Sources/Test/Objects/Colin.swift index 18b363a..2238f6a 100644 --- a/Sources/Test/Objects/Colin.swift +++ b/Sources/Test/Objects/Colin.swift @@ -1,4 +1,5 @@ import SDL2 +import Maths import simd import OpenGL.GL import JolkEngine @@ -13,7 +14,19 @@ struct Colin: Actor { //.rotate(yaw: angle.x, pitch: angle.y, roll: sin(time)) * //.scale(Vec3f(1.0 + 0.25 * cos(time), 1.0 + 0.25 * sin(time), 1.0)) * - .rotate(yawPitch: angle) * .translate(-position - Vec3f(0, 1, 0)) + .rotate(yawPitch: angle) + //.rotate(x: angle.y) * .rotate(y: angle.x) + //.rotate(axis: .left, angle: angle.y) * .rotate(y: angle.x) + //.rotate(axis: .left, angle: angle.y) * .rotate(axis: .up, angle: angle.x) + //Mat4f(quat: Quatf(axis: .X, angle: angle.y) * Quatf(axis: .Y, angle: angle.x)) + //simd_matrix4x4(simd_quatf(angle: angle.y, axis: .X) * simd_quatf(angle: angle.x, axis: .Y)) + //.lookAt(to: Mat4f.rotate(yawPitch: angle) * -Vec3f.Z * 20.0) + //Mat4f(quat: Quatf.lookAt(to: .rotate(yawPitch: angle).transpose * -Vec3f.Z)).transpose + //Mat4f(Mat3f.lookAt(to: .rotate(yawPitch: angle) * -Vec3f.Z)) + //.lookAt(from: _pos, to: .zero) + + * .translate(-position - Vec3f(0, 1, 0)) + } var angle: Vec2f { return Vec2f(ofsAngle.x + _angle, ofsAngle.y) } @@ -45,13 +58,13 @@ struct Colin: Actor for edge in edges { let diff = _pos2D - lastPos - if simd_dot(edge.n, diff) > 0 && simd_dot(edge.n, velocity) > 0 { continue } + if edge.n.dot(diff) > 0 && edge.n.dot(velocity) > 0 { continue } let deltaPos = _pos2D - edge.p let something = deltaPos.cross(edge.n) if abs(something) * 2.0 < edge.w { - let dot = simd_dot(edge.n, deltaPos) + let dot = edge.n.dot(deltaPos) if dot > 0 && dot < colinWidth { lastPos = _pos2D @@ -246,5 +259,10 @@ struct Colin: Actor // jumpVel = max(jumpVel, 0.0) // _pos.y = 0.0 //} + + + //let piss = Quatf(axis: .X, angle: angle.y) * Quatf(axis: .Y, angle: angle.x) + //let shit = simd_quatf(angle: angle.y, axis: .X) * simd_quatf(angle: angle.x, axis: .Y) + //print(piss.w - shit.real, piss.x - shit.imag.x, piss.y - shit.imag.y, piss.z - shit.imag.z) } } diff --git a/Sources/Test/Objects/JolkCube.swift b/Sources/Test/Objects/JolkCube.swift index 2f31ca6..1425385 100644 --- a/Sources/Test/Objects/JolkCube.swift +++ b/Sources/Test/Objects/JolkCube.swift @@ -1,4 +1,5 @@ import Foundation +import Maths import simd import JolkEngine diff --git a/Sources/Test/Resources/Models/Axis.obj.cache b/Sources/Test/Resources/Models/Axis.obj.cache new file mode 100644 index 0000000..15cd37a Binary files /dev/null and b/Sources/Test/Resources/Models/Axis.obj.cache differ diff --git a/Sources/Test/Resources/Models/rock.mesh b/Sources/Test/Resources/Models/rock.mesh new file mode 100644 index 0000000..e0eb7d2 Binary files /dev/null and b/Sources/Test/Resources/Models/rock.mesh differ diff --git a/Sources/Test/Resources/Textures/AxisTex.png b/Sources/Test/Resources/Textures/AxisTex.png new file mode 100644 index 0000000..0129ed4 Binary files /dev/null and b/Sources/Test/Resources/Textures/AxisTex.png differ diff --git a/Sources/Test/Resources/Textures/rock.dds b/Sources/Test/Resources/Textures/rock.dds new file mode 100644 index 0000000..d3e0dba Binary files /dev/null and b/Sources/Test/Resources/Textures/rock.dds differ diff --git a/Sources/Test/Resources/Textures/rocknorm.dds b/Sources/Test/Resources/Textures/rocknorm.dds new file mode 100644 index 0000000..d7a7090 Binary files /dev/null and b/Sources/Test/Resources/Textures/rocknorm.dds differ diff --git a/Sources/Test/Scenes/CaveScene.swift b/Sources/Test/Scenes/CaveScene.swift index 41561e4..bc2aa60 100644 --- a/Sources/Test/Scenes/CaveScene.swift +++ b/Sources/Test/Scenes/CaveScene.swift @@ -1,4 +1,5 @@ import Foundation +import Maths import simd import JolkEngine @@ -8,12 +9,18 @@ struct CaveScene: Scene private var colin = Colin() private var world = Collision() private var worldModel = RenderMesh.empty + private var rockModel = RenderMesh.empty, axisModel = RenderMesh.empty + private var rockAlbedo = Texture2D.empty, rockNormal = Texture2D.empty, axisAlbedo = Texture2D.empty + private var skung = Quatf.init(), skungFrom = Quatf.init(), skungTo = Quatf.init() + private var skungx: Float = 0.0 + private var axisPos = Vec3f(2.0, 0.5, 0.0) private var drawEdges = false mutating func setup(render: inout any JolkEngine.Renderer) { colin.setPosition(Vec3f(3.55475903, 0.0667395443, 0.221960306)) colin.setAngle(Vec2f(-1.47447872, 0.0)) + skungTo = .lookAt(from: axisPos, to: colin.position + .up) } mutating func loadContent(content: inout JolkEngine.ContentManager) throws @@ -21,6 +28,11 @@ struct CaveScene: Scene let obj = try ObjReader.read(url: try content.getResource("CaveScene.obj")) let mesh: Mesh = try ObjLoader.read(model: obj, content: &content) worldModel = try content.create(mesh: mesh) + rockModel = try content.load("rock.mesh") + rockAlbedo = try content.load("rock.dds") + rockNormal = try content.load("rocknorm.dds") + axisModel = try content.load("Axis.obj.cache") + axisAlbedo = try content.load("AxisTex.png") if let collision = obj.objects["Collision3D"] { @@ -31,6 +43,23 @@ struct CaveScene: Scene mutating func update(deltaTime: Float) { colin.update(deltaTime: deltaTime, world: world) + + skungx = skungx + deltaTime + if skungx >= 1 + { + skungx = fmod(skungx, 1.0) + skungFrom = skungTo + skungTo = .lookAt(from: axisPos, to: colin.position + .up) + } + skung = skungFrom.slerp(skungTo, skungx.smoothStep()) + + if let pad = GamePad.current?.state + { + if pad.down(.north) + { + axisPos += .forward * skung * deltaTime + } + } if Input.instance.keyboard.keyPressed(.c) { drawEdges = !drawEdges } } @@ -41,7 +70,7 @@ struct CaveScene: Scene aspect: aspect, zNear: 0.1, zFar: 4000.0)) render.setView(matrix: colin.transform) - let env = Environment() + var env = Environment() let drawRanges = { [render](range: any Sequence) in for i in range @@ -53,6 +82,25 @@ struct CaveScene: Scene } if colin.position.x < 14 { drawRanges([ 0, 1, 2, 6 ]) } else { drawRanges(3...5) } + + //print(Mat4f.rotate(yawPitch: colin.angle) * -Vec3f.Z) + env.addDirectionalLight(direction: -.init(-0.6199881, 0.7049423, 0.34448668), colour: .white) + render.setMaterial(.init( + //specular: .init(grey: 0.5), + specular: .white, + gloss: 1, + texture: axisAlbedo.id)) + render.draw(mesh: axisModel, model: .translate(axisPos) * Mat4f(quat: skung), environment: env) + + render.setMaterial(.init( + diffuse: XnaColour.DarkGray.lighten(by: -0.1), + //specular: .init(grey: 0.9), + specular: .white, + emmision: XnaColour.Green.mix(with: .black, 0.876), + gloss: 20, + texture: rockAlbedo.id)) + render.draw(mesh: rockModel, model: .translate(.init(27.622196, 0.8187093, -19.366903)), environment: env) + if drawEdges { world.draw(render, position: colin.position) } } } diff --git a/Sources/Test/Scenes/Scene1.swift b/Sources/Test/Scenes/Scene1.swift index aa43988..0a8749c 100644 --- a/Sources/Test/Scenes/Scene1.swift +++ b/Sources/Test/Scenes/Scene1.swift @@ -1,5 +1,6 @@ import JolkEngine import Foundation +import Maths import simd @@ -57,18 +58,18 @@ struct Scene1: Scene try loadWorld(&content) cube = try content.create(mesh: .init( vertices: [ - .init(position: Vec3f(-1, -1, 1), normal: .forward, texCoord: Vec2f(0, 0)), - .init(position: Vec3f( 1, -1, 1), normal: .forward, texCoord: Vec2f(1, 0)), - .init(position: Vec3f(-1, 1, 1), normal: .forward, texCoord: Vec2f(0, 1)), - .init(position: Vec3f( 1, 1, 1), normal: .forward, texCoord: Vec2f(1, 1)), + .init(position: Vec3f(-1, -1, 1), normal: .back, texCoord: Vec2f(0, 0)), + .init(position: Vec3f( 1, -1, 1), normal: .back, texCoord: Vec2f(1, 0)), + .init(position: Vec3f(-1, 1, 1), normal: .back, texCoord: Vec2f(0, 1)), + .init(position: Vec3f( 1, 1, 1), normal: .back, texCoord: Vec2f(1, 1)), .init(position: Vec3f( 1, -1, 1), normal: .right, texCoord: Vec2f(0, 0)), .init(position: Vec3f( 1, -1, -1), normal: .right, texCoord: Vec2f(1, 0)), .init(position: Vec3f( 1, 1, 1), normal: .right, texCoord: Vec2f(0, 1)), .init(position: Vec3f( 1, 1, -1), normal: .right, texCoord: Vec2f(1, 1)), - .init(position: Vec3f( 1, -1, -1), normal: .back, texCoord: Vec2f(0, 0)), - .init(position: Vec3f(-1, -1, -1), normal: .back, texCoord: Vec2f(1, 0)), - .init(position: Vec3f( 1, 1, -1), normal: .back, texCoord: Vec2f(0, 1)), - .init(position: Vec3f(-1, 1, -1), normal: .back, texCoord: Vec2f(1, 1)), + .init(position: Vec3f( 1, -1, -1), normal: .forward, texCoord: Vec2f(0, 0)), + .init(position: Vec3f(-1, -1, -1), normal: .forward, texCoord: Vec2f(1, 0)), + .init(position: Vec3f( 1, 1, -1), normal: .forward, texCoord: Vec2f(0, 1)), + .init(position: Vec3f(-1, 1, -1), normal: .forward, texCoord: Vec2f(1, 1)), .init(position: Vec3f(-1, -1, -1), normal: .left, texCoord: Vec2f(0, 0)), .init(position: Vec3f(-1, -1, 1), normal: .left, texCoord: Vec2f(1, 0)), .init(position: Vec3f(-1, 1, -1), normal: .left, texCoord: Vec2f(0, 1)), @@ -158,37 +159,33 @@ struct Scene1: Scene // Draw world for s in worldMesh.subMeshes { - //render.setMaterial(Material( - // specular: XnaColour.BlanchedAlmond.mix(with: .black, 0.12), - // texture: texture.id)) - if s.material != -1 - { - render.setMaterial(worldMesh.materials[s.material]) - } - else - { - render.setMaterial(.init()) - } + let material = s.material != -1 + ? worldMesh.materials[s.material] + : .init(specular: XnaColour.BlanchedAlmond.mix(with: .black, 0.12)) + render.setMaterial(material) render.draw(mesh: worldMesh, subMesh: s, environment: env) } // Draw jolked up shit render.setMaterial(Material( specular: XnaColour.Gray, - gloss: 20.0, + gloss: 28.0, texture: jolkTex.id)) render.draw(mesh: cube, model: jolkCube.transform, environment: env) - render.setMaterial(Material(texture: suzanneDiffuse.id)) - render.draw(mesh: suzanne, model: .translate(.up + Vec3f(3.0, 0.0, -3.5) * 2.5), environment: env) + render.setMaterial(Material( + specular: .init(grey: 0.5), + gloss: 15.0, + texture: suzanneDiffuse.id)) + let suzannePos = .up + Vec3f(3.0, 0.0, -3.5) * 2.5 + render.draw(mesh: suzanne, model: + .translate(suzannePos) * + .lookAt(from: suzannePos, to: colin.position + .up) * .rotate(y: .pi), environment: env) render.draw(mesh: toybox, model: .translate(Vec3f(6.0, 0.667, -3.5) * Vec3f(2.5, 1, 2.5)) * .rotate(y: lightTheta * 0.5) * .scale(scalar: 0.25), environment: env) - if drawEdges - { - world.draw(render, position: colin.position) - } + if drawEdges { world.draw(render, position: colin.position) } } }