diff --git a/Metaballs.xcodeproj/project.pbxproj b/Metaballs.xcodeproj/project.pbxproj index 6c0a38b..76872fa 100644 --- a/Metaballs.xcodeproj/project.pbxproj +++ b/Metaballs.xcodeproj/project.pbxproj @@ -29,6 +29,8 @@ C0FF7C992168062C0081B781 /* Shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = C0BBE3A91F2E91D900E68524 /* Shaders.metal */; }; C0FF7C9A2168062C0081B781 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0B906D41F45432700B5F89B /* Preferences.swift */; }; C0FF7C9B2168062C0081B781 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C091616F1F3F5AE6009C4263 /* PreferencesViewController.swift */; }; + C0FF7C9D216A6DE00081B781 /* Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FF7C9C216A6DE00081B781 /* Math.swift */; }; + C0FF7C9E216A6DE00081B781 /* Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FF7C9C216A6DE00081B781 /* Math.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -78,6 +80,7 @@ C0BBE3AB1F2E941200E68524 /* Renderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Renderer.swift; sourceTree = ""; }; C0CE7BFF1F362C3F001516B6 /* Geometry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Geometry.swift; sourceTree = ""; }; C0DF1D781F3789DC0038B0A0 /* Memory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Memory.swift; sourceTree = ""; }; + C0FF7C9C216A6DE00081B781 /* Math.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Math.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -167,6 +170,7 @@ C0BBE3A31F2E81C700E68524 /* Metaballs.swift */, C0CE7BFF1F362C3F001516B6 /* Geometry.swift */, C0DF1D781F3789DC0038B0A0 /* Memory.swift */, + C0FF7C9C216A6DE00081B781 /* Math.swift */, C0BBE3AB1F2E941200E68524 /* Renderer.swift */, C0BBE3A91F2E91D900E68524 /* Shaders.metal */, C0B906D41F45432700B5F89B /* Preferences.swift */, @@ -345,6 +349,7 @@ files = ( C0FF7C992168062C0081B781 /* Shaders.metal in Sources */, C0FF7C9A2168062C0081B781 /* Preferences.swift in Sources */, + C0FF7C9E216A6DE00081B781 /* Math.swift in Sources */, C0FF7C972168062C0081B781 /* Memory.swift in Sources */, C0FF7C9B2168062C0081B781 /* PreferencesViewController.swift in Sources */, C0B906E91F455D1A00B5F89B /* MetaballsSaverView.swift in Sources */, @@ -367,6 +372,7 @@ C0FF7C912168062B0081B781 /* Renderer.swift in Sources */, C0FF7C932168062B0081B781 /* Preferences.swift in Sources */, C0BBE36B1F2E816500E68524 /* AppDelegate.swift in Sources */, + C0FF7C9D216A6DE00081B781 /* Math.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MetaballsKit/Geometry.swift b/MetaballsKit/Geometry.swift index 7147979..42cd7ab 100644 --- a/MetaballsKit/Geometry.swift +++ b/MetaballsKit/Geometry.swift @@ -7,91 +7,18 @@ // import Foundation +import simd -public struct Point { - var x: Float - var y: Float - - var CGPoint: CGPoint { - return CoreGraphics.CGPoint(x: CGFloat(x), y: CGFloat(y)) - } - - init() { - self.init(x: 0, y: 0) - } - - init(x: Float, y: Float) { - self.x = x - self.y = y - } -} - -extension Point: CustomStringConvertible { - public var description: String { - return "(\(x), \(y))" - } -} - -public struct Size { - var width: UInt16 - var height: UInt16 - - public init() { - self.init(width: 0, height: 0) - } - - public init(width: UInt16, height: UInt16) { - self.width = width - self.height = height - } +public typealias Size = packed_uint2 +extension Size { public init(size: CGSize) { - self.init(width: UInt16(size.width), height: UInt16(size.height)) + self.init(UInt32(size.width), UInt32(size.height)) } } extension Size: CustomStringConvertible { public var description: String { - return "(\(width), \(height))" - } -} - -extension Size: Equatable { - /// Returns a Boolean value indicating whether two values are equal. - /// - /// Equality is the inverse of inequality. For any values `a` and `b`, - /// `a == b` implies that `a != b` is `false`. - /// - /// - Parameters: - /// - lhs: A value to compare. - /// - rhs: Another value to compare. - public static func ==(lhs: Size, rhs: Size) -> Bool { - return lhs.width == rhs.width && lhs.height == rhs.height - } -} - -extension CGSize { - init(size: Size) { - self.init(width: CGFloat(size.width), height: CGFloat(size.height)) - } -} - -public struct Vector { - var dx: Float - var dy: Float - - init() { - self.init(dx: 0, dy: 0) - } - - init(dx: Float, dy: Float) { - self.dx = dx - self.dy = dy - } -} - -extension Vector: CustomStringConvertible { - public var description: String { - return "(\(dx), \(dy))" + return "(\(x), \(y))" } } diff --git a/MetaballsKit/Math.swift b/MetaballsKit/Math.swift new file mode 100644 index 0000000..ca2e809 --- /dev/null +++ b/MetaballsKit/Math.swift @@ -0,0 +1,63 @@ +// +// Math.swift +// Metaballs +// +// Created by Eryn Wells on 9/22/18. +// Copyright © 2018 Eryn Wells. All rights reserved. +// + +import Cocoa +import Foundation +import simd + +public typealias Float2 = packed_float2 +public typealias Float3 = float3 +public typealias Float4 = float4 +public typealias Matrix3x3 = float3x3 +public typealias Matrix4x4 = float4x4 + +extension Float2 { + var CGPoint: CGPoint { + return CoreGraphics.CGPoint(x: CGFloat(x), y: CGFloat(y)) + } +} + +extension Float2: CustomStringConvertible { + public var description: String { + return "(\(x), \(y))" + } +} + +extension Float4 { + public init(r: Float, g: Float, b: Float, a: Float) { + self.init(r, g, b, a) + } + + public init(color: NSColor) { + if let convertedColor = color.usingColorSpace(NSColorSpace.deviceRGB) { + self.init(Float(convertedColor.redComponent), Float(convertedColor.greenComponent), Float(convertedColor.blueComponent), Float(convertedColor.alphaComponent)) + } else { + self.init() + } + } +} + +extension Matrix4x4 { + /// Create a 4x4 orthographic projection matrix with the provided 6-tuple. + /// @see https://en.wikipedia.org/wiki/Orthographic_projection + static func orthographicProjection(top: Float32, left: Float32, bottom: Float32, right: Float32, near: Float32, far: Float32) -> Matrix4x4 { + let rows = [ + Float4(2.0 / (right - left), 0.0, 0.0, -(right + left) / (right - left)), + Float4(0.0, 2.0 / (top - bottom), 0.0, -(top + bottom) / (top - bottom)), + Float4(0.0, 0.0, -2.0 / (far - near), -(far + near) / (far - near)), + Float4(0.0, 0.0, 0.0, 1.0) + ] + return Matrix4x4(rows) + } +} + +extension CGSize { + init(size: Size) { + self.init(width: CGFloat(size.x), height: CGFloat(size.y)) + } +} diff --git a/MetaballsKit/Memory.swift b/MetaballsKit/Memory.swift index 3e5e97a..437327b 100644 --- a/MetaballsKit/Memory.swift +++ b/MetaballsKit/Memory.swift @@ -8,6 +8,7 @@ import Cocoa import Foundation +import simd extension UnsafeMutableRawPointer { func writeAndAdvance(value: inout T) -> UnsafeMutableRawPointer { @@ -17,38 +18,6 @@ extension UnsafeMutableRawPointer { } } -/// Metal's float4 type. 4 bytes per float, 16 bytes total, 16 byte aligned. -public struct Float4 { - public var x: Float = 0 - public var y: Float = 0 - public var z: Float = 0 - public var w: Float = 0 - - public init() { } - - public init(_ x: Float, _ y: Float, _ z: Float, _ w: Float) { - self.x = x - self.y = y - self.z = z - self.w = w - } - - public init(r: Float, g: Float, b: Float, a: Float) { - x = r - y = g - z = b - w = a - } - - init(color: NSColor) { - if let convertedColor = color.usingColorSpace(NSColorSpace.deviceRGB) { - self.init(Float(convertedColor.redComponent), Float(convertedColor.greenComponent), Float(convertedColor.blueComponent), Float(convertedColor.alphaComponent)) - } else { - self.init() - } - } -} - extension NSColor { convenience init(float4: Float4) { self.init(deviceRed: CGFloat(float4.x), green: CGFloat(float4.y), blue: CGFloat(float4.z), alpha: CGFloat(float4.w)) diff --git a/MetaballsKit/Metaballs.swift b/MetaballsKit/Metaballs.swift index 00fabee..b3ee598 100644 --- a/MetaballsKit/Metaballs.swift +++ b/MetaballsKit/Metaballs.swift @@ -13,7 +13,7 @@ public enum MetaballsError: Error { case metalError(String) } -public enum ColorStyle: UInt16 { +public enum ColorStyle: UInt32 { /// Single flat color case singleColor = 1 /// Two color horizontal gradient @@ -33,8 +33,8 @@ public struct Parameters { } // Simulation parameters - var size = Size(width: 0, height: 0) - var numberOfBalls: UInt16 = 0 + var size = Size(0, 0) + var numberOfBalls: UInt32 = 0 private var _colorStyle = ColorStyle.singleColor.rawValue @@ -69,8 +69,8 @@ public struct Parameters { public struct Ball { let radius: Float - var position = Point() - var velocity = Vector() + var position = Float2() + var velocity = Float2() internal var bounds: CGRect { let diameter = CGFloat(radius * 2) @@ -78,8 +78,8 @@ public struct Ball { } internal mutating func update() { - position.x += velocity.dx - position.y += velocity.dy + position.x += velocity.x + position.y += velocity.y } } @@ -98,11 +98,11 @@ public class Field { NSLog("Updating size of field: old:\(parameters.size), new:\(newValue)") if parameters.size != newValue { // Scale balls to new position and size. - let scale = parameters.size.width != 0 ? Float(newValue.width / parameters.size.width) : 1 + let scale = parameters.size.x != 0 ? Float(newValue.x / parameters.size.x) : 1 balls = balls.map { let r = $0.radius * scale let p = randomPoint(forBallWithRadius: r) - let v = Vector(dx: $0.velocity.dx * scale, dy: $0.velocity.dy * scale) + let v = Float2($0.velocity.x * scale, $0.velocity.y * scale) return Ball(radius: r, position: p, velocity: v) } @@ -141,16 +141,16 @@ public class Field { if !selfBounds.contains(balls[i].position.CGPoint) { // Degenerate case. If the ball finds itself outside the bounds of the field, plop it back in the center. - balls[i].position = Point(x: Float(selfBounds.midX), y: Float(selfBounds.midY)) + balls[i].position = Float2(x: Float(selfBounds.midX), y: Float(selfBounds.midY)) } else { // Do collision detection with walls. let ballBounds = balls[i].bounds if !selfBounds.contains(ballBounds) { if ballBounds.minX < selfBounds.minX || ballBounds.maxX > selfBounds.maxX { - balls[i].velocity.dx *= -1 + balls[i].velocity.x *= -1 } if ballBounds.minY < selfBounds.minY || ballBounds.maxY > selfBounds.maxY { - balls[i].velocity.dy *= -1 + balls[i].velocity.y *= -1 } } } @@ -163,7 +163,7 @@ public class Field { let dx = Float(5 - Int(arc4random_uniform(10))) let dy = Float(5 - Int(arc4random_uniform(10))) - let velocity = Vector(dx: dx, dy: dy) + let velocity = Float2(dx, dy) let ball = Ball(radius: radius, position: position, velocity: velocity) balls.append(ball) @@ -178,14 +178,14 @@ public class Field { balls.removeAll(keepingCapacity: true) } - private func randomPoint(forBallWithRadius radius: Float) -> Point { + private func randomPoint(forBallWithRadius radius: Float) -> Float2 { guard Float(bounds.width) > radius && Float(bounds.height) > radius else { - return Point() + return Float2() } let insetBounds = bounds.insetBy(dx: CGFloat(radius), dy: CGFloat(radius)) let x = Float(UInt32(insetBounds.minX) + arc4random_uniform(UInt32(insetBounds.width))) let y = Float(UInt32(insetBounds.minY) + arc4random_uniform(UInt32(insetBounds.height))) - let position = Point(x: x, y: y) + let position = Float2(x: x, y: y) return position } @@ -205,7 +205,7 @@ public class Field { } if let parametersBuffer = parametersBuffer { - parameters.numberOfBalls = UInt16(balls.count) + parameters.numberOfBalls = UInt32(balls.count) self.parameters.write(to: parametersBuffer) } } diff --git a/MetaballsKit/Preferences.swift b/MetaballsKit/Preferences.swift index 4ac90cd..2caab41 100644 --- a/MetaballsKit/Preferences.swift +++ b/MetaballsKit/Preferences.swift @@ -42,7 +42,7 @@ extension UserDefaults { public var colorStyle: ColorStyle? { get { let value = integer(forKey: "colorStyle") - if let colorStyle = ColorStyle(rawValue: UInt16(value)) { + if let colorStyle = ColorStyle(rawValue: UInt32(value)) { return colorStyle } return nil diff --git a/MetaballsKit/PreferencesViewController.swift b/MetaballsKit/PreferencesViewController.swift index d248bf1..fbde6de 100644 --- a/MetaballsKit/PreferencesViewController.swift +++ b/MetaballsKit/PreferencesViewController.swift @@ -222,7 +222,7 @@ public class PreferencesViewController: NSViewController { func postColorNotification() { var info = [String:Any]() if let item = styleMenu.selectedItem { - info["colorStyle"] = ColorStyle(rawValue: UInt16(item.tag)) + info["colorStyle"] = ColorStyle(rawValue: UInt32(item.tag)) } for (idx, cv) in colorViews.enumerated() { info["color\(idx)"] = cv.colorWell.color diff --git a/MetaballsKit/Renderer.swift b/MetaballsKit/Renderer.swift index 8129dc6..8cda175 100644 --- a/MetaballsKit/Renderer.swift +++ b/MetaballsKit/Renderer.swift @@ -20,8 +20,8 @@ public protocol RendererDelegate { } struct Vertex { - let position: Point - let textureCoordinate: Point + let position: Float2 + let textureCoordinate: Float2 } public class Renderer: NSObject, MTKViewDelegate { @@ -101,13 +101,13 @@ public class Renderer: NSObject, MTKViewDelegate { // Two triangles, plus texture coordinates. let points: [Vertex] = [ - Vertex(position: Point(x: 1, y: -1), textureCoordinate: Point(x: 1, y: 0)), - Vertex(position: Point(x: -1, y: -1), textureCoordinate: Point(x: 0, y: 0)), - Vertex(position: Point(x: -1, y: 1), textureCoordinate: Point(x: 0, y: 1)), + Vertex(position: Float2(x: 1, y: -1), textureCoordinate: Float2(x: 1, y: 0)), + Vertex(position: Float2(x: -1, y: -1), textureCoordinate: Float2(x: 0, y: 0)), + Vertex(position: Float2(x: -1, y: 1), textureCoordinate: Float2(x: 0, y: 1)), - Vertex(position: Point(x: 1, y: -1), textureCoordinate: Point(x: 1, y: 0)), - Vertex(position: Point(x: -1, y: 1), textureCoordinate: Point(x: 0, y: 1)), - Vertex(position: Point(x: 1, y: 1), textureCoordinate: Point(x: 1, y: 1)) + Vertex(position: Float2(x: 1, y: -1), textureCoordinate: Float2(x: 1, y: 0)), + Vertex(position: Float2(x: -1, y: 1), textureCoordinate: Float2(x: 0, y: 1)), + Vertex(position: Float2(x: 1, y: 1), textureCoordinate: Float2(x: 1, y: 1)) ] field.update() diff --git a/MetaballsKit/Shaders.metal b/MetaballsKit/Shaders.metal index af9dc91..8c09b8b 100644 --- a/MetaballsKit/Shaders.metal +++ b/MetaballsKit/Shaders.metal @@ -38,13 +38,11 @@ typedef enum { } ColorStyle; typedef struct { - short2 size; - ushort numberOfBalls; - - ushort colorStyle; + packed_uint2 size; + uint numberOfBalls; + uint colorStyle; float target; float feather; - float4 colors[4]; } Parameters; @@ -90,12 +88,12 @@ sampleToColorShader(RasterizerData in [[stage_in]], out = singleColor(sample, target, feather, parameters.colors[0]); break; case Gradient2Horizontal: { - const float blend = in.position.x / parameters.size.x; + const float blend = in.position.x / parameters.size[0]; out = gradient2(sample, target, feather, blend, parameters.colors[0], parameters.colors[1]); break; } case Gradient2Vertical: { - const float blend = in.position.y / parameters.size.y; + const float blend = in.position.y / parameters.size[1]; out = gradient2(sample, target, feather, blend, parameters.colors[0], parameters.colors[1]); break; }