├── .gitignore ├── Artwork ├── header.png └── header.psd ├── Frameworks └── AHNoise │ ├── AHNCombiner.swift │ ├── AHNCombinerAdd.swift │ ├── AHNCombinerDivide.swift │ ├── AHNCombinerMax.swift │ ├── AHNCombinerMin.swift │ ├── AHNCombinerMultiply.swift │ ├── AHNCombinerPower.swift │ ├── AHNCombinerSubtract.swift │ ├── AHNContext.swift │ ├── AHNGenerator.swift │ ├── AHNGeneratorBillow.swift │ ├── AHNGeneratorChecker.swift │ ├── AHNGeneratorCoherent.swift │ ├── AHNGeneratorConstant.swift │ ├── AHNGeneratorCylinder.swift │ ├── AHNGeneratorGradientBox.swift │ ├── AHNGeneratorGradientLinear.swift │ ├── AHNGeneratorGradientRadial.swift │ ├── AHNGeneratorRidgedMulti.swift │ ├── AHNGeneratorSimplex.swift │ ├── AHNGeneratorSphere.swift │ ├── AHNGeneratorVoronoi.swift │ ├── AHNGeneratorWave.swift │ ├── AHNModifier.swift │ ├── AHNModifierAbsolute.swift │ ├── AHNModifierBlur.swift │ ├── AHNModifierClamp.swift │ ├── AHNModifierColour.swift │ ├── AHNModifierInvert.swift │ ├── AHNModifierLoop.swift │ ├── AHNModifierMapNormal.swift │ ├── AHNModifierPerspective.swift │ ├── AHNModifierRotate.swift │ ├── AHNModifierRound.swift │ ├── AHNModifierScaleBias.swift │ ├── AHNModifierScaleCanvas.swift │ ├── AHNModifierStep.swift │ ├── AHNModifierStretch.swift │ ├── AHNModifierSwirl.swift │ ├── AHNSelector.swift │ ├── AHNSelectorBlend.swift │ ├── AHNSelectorSelect.swift │ ├── AHNTextureProvider.swift │ ├── Combiners.metal │ ├── Generators.metal │ ├── Modifiers.metal │ ├── Selectors.metal │ └── UIImage+AHNoise.swift ├── Planets!.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Planets! ├── App │ └── AppDelegate.swift ├── Extensions │ ├── Color.swift │ ├── Constraints.swift │ ├── Foundation.swift │ └── String.swift ├── Game │ ├── GameViewController.swift │ ├── HUD.swift │ ├── Nodes │ │ ├── Camera.swift │ │ ├── Planet.swift │ │ ├── Stars.swift │ │ ├── Sun.swift │ │ └── Universe.swift │ └── Objects │ │ ├── Combiner.swift │ │ ├── Generator.swift │ │ ├── Loader.swift │ │ ├── PlanetName.swift │ │ └── Texture.swift └── Resources │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── envmap.imageset │ │ ├── Contents.json │ │ └── envmap.jpg │ ├── envmap2_blurred.imageset │ │ ├── Contents.json │ │ └── envmap2_blurred.png │ └── envmap_blurred.imageset │ │ ├── Contents.json │ │ └── envmap_blurred.jpg │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── art.scnassets │ ├── planet.obj │ ├── star.png │ └── stars.scnp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Planets!.xcodeproj/xcuserdata/katiebogdanska.xcuserdatad/xcschemes/Planets!.xcscheme 2 | Planets!.xcodeproj/xcuserdata/katiebogdanska.xcuserdatad/xcschemes/xcschememanagement.plist 3 | -------------------------------------------------------------------------------- /Artwork/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roberthein/Planets/c557b239d5ca106ad9161f121eaed589c1cfc2a9/Artwork/header.png -------------------------------------------------------------------------------- /Artwork/header.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roberthein/Planets/c557b239d5ca106ad9161f121eaed589c1cfc2a9/Artwork/header.psd -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNCombiner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNCombiner.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 25/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | The general class used to combine two input `AHNTextureProvider`s and write the result to an output. This class is not instantiated directly, but is used by various subclasses. 16 | 17 | *Conforms to the `AHNTextureProvider` protocol.* 18 | */ 19 | open class AHNCombiner: NSObject, AHNTextureProvider { 20 | 21 | 22 | // MARK:- Properties 23 | 24 | 25 | ///The `AHNContext` that is being used by the `AHNTextureProvider` to communicate with the GPU. This is recovered from the first `AHNGenerator` class that is encountered in the chain of classes. 26 | open var context: AHNContext 27 | 28 | 29 | 30 | ///The `MTLComputePipelineState` used to run the `Metal` compute kernel on the GPU. 31 | let pipeline: MTLComputePipelineState 32 | 33 | 34 | 35 | ///The `MTLBuffer` used to transfer the constant values used by the compute kernel to the GPU. 36 | open var uniformBuffer: MTLBuffer? 37 | 38 | 39 | 40 | ///The `MTLTexture` that the compute kernel writes to as an output. 41 | var internalTexture: MTLTexture? 42 | 43 | 44 | 45 | /** 46 | The `MTLFunction` compute kernel that modifies the input `MTLTexture`s and writes the output to the `internalTexture` property. 47 | 48 | The function used is specific to each class. 49 | */ 50 | let kernelFunction: MTLFunction 51 | 52 | 53 | 54 | ///Indicates whether or not the `internalTexture` needs updating. 55 | open var dirty: Bool = true 56 | 57 | 58 | 59 | ///The first input that will be combined with `provider2` to provide the output. 60 | open var provider: AHNTextureProvider?{ 61 | didSet{ 62 | dirty = true 63 | } 64 | } 65 | 66 | 67 | 68 | ///The second input that will be combined with `provider` to provide the output. 69 | open var provider2: AHNTextureProvider?{ 70 | didSet{ 71 | dirty = true 72 | } 73 | } 74 | 75 | 76 | 77 | /** 78 | The width of the output `MTLTexure` in pixels. 79 | 80 | This is dictated by the width of the texture of the first input `AHNTextureProvider`. If there is no input, the default width is `128` pixels. 81 | */ 82 | open var textureWidth: Int{ 83 | get{ 84 | if let provider = provider, let provider2 = provider2{ 85 | return max(provider.textureSize().width, provider2.textureSize().width) 86 | }else{ 87 | return 128 88 | } 89 | } 90 | } 91 | 92 | 93 | 94 | /** 95 | The height of the output `MTLTexure` in pixels. 96 | 97 | This is dictated by the height of the texture of the first input `AHNTextureProvider`. If there is no input, the default height is `128` pixels. 98 | */ 99 | open var textureHeight: Int{ 100 | get{ 101 | if let provider = provider, let provider2 = provider2{ 102 | return max(provider.textureSize().height, provider2.textureSize().height) 103 | }else{ 104 | return 128 105 | } 106 | } 107 | } 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | // MARK:- Initialiser 129 | 130 | 131 | /** 132 | Creates a new `AHNCombiner` object. 133 | 134 | To be called when instantiating a subclass. 135 | 136 | - parameter functionName: The name of the kernel function that this combiner will use to combine inputs. 137 | */ 138 | public init(functionName: String){ 139 | context = AHNContext.SharedContext 140 | 141 | // Load the kernel function and compute pipeline state 142 | guard let kernelFunction = context.library.makeFunction(name: functionName) else{ 143 | fatalError("AHNoise: Error loading function \(functionName).") 144 | } 145 | self.kernelFunction = kernelFunction 146 | 147 | do{ 148 | try pipeline = context.device.makeComputePipelineState(function: kernelFunction) 149 | }catch let error{ 150 | fatalError("AHNoise: Error creating pipeline state for \(functionName).\n\(error)") 151 | } 152 | 153 | super.init() 154 | } 155 | 156 | 157 | 158 | override public required init(){ 159 | context = AHNContext.SharedContext 160 | 161 | let functionName = "simplexGenerator" 162 | // Load the kernel function and compute pipeline state 163 | guard let kernelFunction = context.library.makeFunction(name: functionName) else{ 164 | fatalError("AHNoise: Error loading function \(functionName).") 165 | } 166 | self.kernelFunction = kernelFunction 167 | 168 | do{ 169 | try pipeline = context.device.makeComputePipelineState(function: kernelFunction) 170 | }catch let error{ 171 | fatalError("AHNoise: Error creating pipeline state for \(functionName).\n\(error)") 172 | } 173 | 174 | super.init() 175 | } 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | // MARK:- Configure Uniforms 194 | 195 | 196 | /** 197 | This function is overridden by subclasses to write class specific variables to the `uniformBuffer`. 198 | 199 | - parameter commandEncoder: The `MTLComputeCommandEncoder` used to run the kernel. This can be used to lazily create a buffer of data and add it to the argument table. Any buffer index can be used without affecting the rest of this class. 200 | */ 201 | open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder){ 202 | } 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | // MARK:- Texture Functions 222 | 223 | /** 224 | Updates the output `MTLTexture`. 225 | 226 | This should not need to be called manually as it is called by the `texture()` method automatically if the texture does not represent the current properties. 227 | */ 228 | open func updateTexture(){ 229 | guard let provider1 = provider?.texture(), let provider2 = provider2?.texture() else { return } 230 | 231 | // Create the internalTexture if it equals nil or is the wrong size. 232 | if internalTexture == nil{ 233 | newInternalTexture() 234 | } 235 | if internalTexture!.width != textureWidth || internalTexture!.height != textureHeight{ 236 | newInternalTexture() 237 | } 238 | 239 | let threadGroupsCount = MTLSizeMake(8, 8, 1) 240 | let threadGroups = MTLSizeMake(textureWidth / threadGroupsCount.width, textureHeight / threadGroupsCount.height, 1) 241 | 242 | let commandBuffer = context.commandQueue.makeCommandBuffer() 243 | 244 | let commandEncoder = commandBuffer.makeComputeCommandEncoder() 245 | commandEncoder.setComputePipelineState(pipeline) 246 | commandEncoder.setTexture(provider1, at: 0) 247 | commandEncoder.setTexture(provider2, at: 1) 248 | commandEncoder.setTexture(internalTexture, at: 2) 249 | 250 | // Encode the uniform buffer 251 | configureArgumentTableWithCommandencoder(commandEncoder) 252 | commandEncoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupsCount) 253 | commandEncoder.endEncoding() 254 | 255 | commandBuffer.commit() 256 | commandBuffer.waitUntilCompleted() 257 | dirty = false 258 | } 259 | 260 | 261 | 262 | ///Create a new `internalTexture` for the first time or whenever the texture is resized. 263 | fileprivate func newInternalTexture(){ 264 | let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: textureWidth, height: textureHeight, mipmapped: false) 265 | internalTexture = context.device.makeTexture(descriptor: textureDescriptor) 266 | } 267 | 268 | 269 | 270 | ///- returns: The updated output `MTLTexture` for the `AHNCombiner`. 271 | open func texture() -> MTLTexture?{ 272 | if isDirty(){ 273 | updateTexture() 274 | } 275 | return internalTexture 276 | } 277 | 278 | 279 | 280 | ///- returns: The MTLSize of the the output `MTLTexture`. If no size has been explicitly set, the default value returned is `128x128` pixels. 281 | open func textureSize() -> MTLSize{ 282 | return MTLSizeMake(textureWidth, textureHeight, 1) 283 | } 284 | 285 | 286 | 287 | ///- returns: The input `AHNTextureProvider` that provides the input `MTLTexture` to the `AHNCombiner`. This is taken from `input1`. If there is no input, returns `nil`. 288 | open func textureProvider() -> AHNTextureProvider?{ 289 | return provider 290 | } 291 | 292 | 293 | 294 | ///- returns: `False` if both inputs and the `internalTexture` do not need updating. 295 | open func isDirty() -> Bool { 296 | let dirtyProvider1 = provider?.isDirty() ?? false 297 | let dirtyProvider2 = provider2?.isDirty() ?? false 298 | return dirtyProvider1 || dirtyProvider2 || dirty 299 | } 300 | 301 | 302 | 303 | ///- returns: `False` if either of the two required texture inputs are `nil`. 304 | open func canUpdate() -> Bool { 305 | return provider != nil && provider2 != nil 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNCombinerAdd.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNCombinerAdd.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 25/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Combines two input `AHNTextureProvider`s by adding their colour values together. 16 | 17 | For example a pixel with a value of `0.3` when added to another pixel with a value of `0.6` will result in a value of `0.9`. 18 | 19 | The `normalise` property indicates whether or not the resulting value should be normalised (divided by two) to return the output the the original value range and preventing output values from exceeding `1.0`. Setting this to `true` results in the ouput being the average of the two inputs. 20 | 21 | Resultant values larger than `1.0` will show as white. The addition is done separately for each colour channel, so the result does not default to greyscale. 22 | */ 23 | open class AHNCombinerAdd: AHNCombiner { 24 | 25 | 26 | // MARK:- Properties 27 | 28 | 29 | ///When set to `true` (`false` by default) the output value range is remapped back to `0.0 - 1.0` to prevent overly bright areas where the combination of inputs has exceeded `1.0`. Setting this to `true` results in the output being the average of the two inputs. 30 | open var normalise: Bool = false{ 31 | didSet{ 32 | dirty = true 33 | } 34 | } 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | // MARK:- Initialiser 48 | 49 | 50 | required public init(){ 51 | super.init(functionName: "addCombiner") 52 | } 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | // MARK:- Argument table update 64 | 65 | 66 | ///Encodes the required uniform values for this `AHNCombiner` subclass. This should never be called directly. 67 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 68 | var uniforms = normalise 69 | 70 | if uniformBuffer == nil{ 71 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: .storageModeShared) 72 | } 73 | 74 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 75 | 76 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNCombinerDivide.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNCombinerDivide.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 26/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Combines two input `AHNTextureProvider`s by dividing their colour values by one another. 16 | 17 | The value of the output is calculated using: `output = input1.rgb / input2.rgb`. 18 | 19 | For example a pixel with a value of `0.3` when divided by another pixel with a value of `0.6` will result in a value of `0.5`. 20 | 21 | Resultant values larger than `1.0` will show as white, and lower than `0.0` will show as black. The division is done separately for each colour channel, so the result does not default to greyscale. 22 | */ 23 | open class AHNCombinerDivide: AHNCombiner { 24 | 25 | 26 | // MARK:- Initialiser 27 | 28 | 29 | required public init(){ 30 | super.init(functionName: "divideCombiner") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNCombinerMax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNCombinerMax.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 25/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Combines two input `AHNTextureProvider`s by choosing the maximum value of the two. 16 | 17 | The value of the output is calculated by first calculating the average value of the three colour channels, then selecting the maximum value and writing the three channels to the output in order to retain colour. 18 | 19 | For example a pixel with a noise value of `0.3` when compared with another pixel with a noise value of `0.6` will result in a noise value of `0.6`. 20 | */ 21 | open class AHNCombinerMax: AHNCombiner { 22 | 23 | 24 | // MARK:- Initialiser 25 | 26 | 27 | required public init(){ 28 | super.init(functionName: "maxCombiner") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNCombinerMin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNCombinerMin.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 25/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Combines two input `AHNTextureProvider`s by choosing the minimum value of the two. 16 | 17 | The value of the output is calculated by first calculating the average value of the three colour channels, then selecting the minimum value and writing the three channels to the output in order to retain colour. 18 | 19 | For example a pixel with a noise value of `0.3` when compared with another pixel with a noise value of `0.6` will result in a noise value of `0.3`. 20 | */ 21 | open class AHNCombinerMin: AHNCombiner { 22 | 23 | 24 | // MARK:- Initialiser 25 | 26 | 27 | required public init(){ 28 | super.init(functionName: "minCombiner") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNCombinerMultiply.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNCombinerMultiply.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 25/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Combines two input `AHNTextureProvider`s by multiplying their colour values with one another. The result is at least as dark as the two inputs. 16 | 17 | For example a pixel with a noise value of `0.3` when multiplied by another pixel with a noise value of `0.6` will result in a noise value of `0.18`. 18 | 19 | The multiplication is done separately for each colour channel, so the result does not default to greyscale. 20 | */ 21 | open class AHNCombinerMultiply: AHNCombiner { 22 | 23 | 24 | // MARK:- Initialiser 25 | 26 | 27 | required public init(){ 28 | super.init(functionName: "multiplyCombiner") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNCombinerPower.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNCombinerPower.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 25/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Combines two input `AHNTextureProvider`s by raising the power of the first input to the second input. 16 | 17 | The value of the output is calculated using: `output = pow(input1.rgb, input2.rgb)`. 18 | 19 | For example a pixel with a noise value of `0.3` when raised to the power of another pixel with a noise value of `0.6` will result in a noise value of `0.486`. 20 | 21 | The multiplication is done separately for each colour channel, so the result does not default to greyscale. 22 | */ 23 | open class AHNCombinerPower: AHNCombiner{ 24 | 25 | 26 | // MARK:- Initialiser 27 | 28 | 29 | required public init(){ 30 | super.init(functionName: "powerCombiner") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNCombinerSubtract.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNCombinerSubtract.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 26/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Combines two input `AHNTextureProvider`s by subtracting their colour values from one another. 16 | 17 | The value of the output is calculated using: `output = input1.rgb - input2.rgb`. 18 | 19 | For example a pixel with a noise value of `0.3` when subtracted from another pixel with a noise value of `0.6` will result in a noise value of `0.3`. 20 | 21 | Resultant values lower than `0.0` will show as black. The subtraction is done separately for each colour channel, so the result does not default to greyscale. 22 | */ 23 | open class AHNCombinerSubtract: AHNCombiner { 24 | 25 | 26 | // MARK:- Initialiser 27 | 28 | 29 | required public init(){ 30 | super.init(functionName: "subtractCombiner") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNContext.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 22/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | import Metal 12 | import simd 13 | 14 | 15 | /** 16 | A wrapper for the `MTLDevice`, `MTLLibrary` and `MTLCommandQueue` used to create the noise textures. Used when generating noise textures using an `AHNGenerator` subclass. 17 | 18 | `AHNModifier`, `AHNCombiner` and `AHNSelector` require an `AHNContext` to run, but reference the same `AHNContext` object as their input, which comes from the `Shared Context` class property for `AHNGenerators`. 19 | */ 20 | open class AHNContext: NSObject { 21 | 22 | 23 | // MARK:- Static Functions 24 | 25 | ///The shared `AHNContext` object that is used by all `AHNTextureProvider` objects to communicate with the GPU. 26 | static var SharedContext: AHNContext! = AHNContext.CreateContext() 27 | 28 | 29 | 30 | ///Set the `MTLDevice` of the `SharedContect` object to a specific object. An `MTLDevice` is a representation of a GPU, so apps for macOS (OSX) will want to set the device to the most powerful graphics hardware available, and not automatically default to onboard graphics. 31 | static func SetContextDevice(_ device: MTLDevice){ 32 | SharedContext = CreateContext(device) 33 | } 34 | 35 | 36 | 37 | ///- returns: An `AHNContext` object with the specified `MTLDevice`. If no `MTLDevice` is specified then the default is obtained from `MTLCreateSystemDefaultDevice()`. 38 | fileprivate static func CreateContext(_ device: MTLDevice? = MTLCreateSystemDefaultDevice()) -> AHNContext{ 39 | return AHNContext(device: device) 40 | } 41 | 42 | 43 | 44 | 45 | 46 | // MARK:- Properties 47 | 48 | 49 | ///The `MTLDevice` used by the various noise classes to create buffers, pipelines and command encoders. 50 | open let device: MTLDevice 51 | 52 | 53 | 54 | ///The `MTLLibrary` that stores the `Metal` kernel functions used to create an manipulate noise. 55 | open let library: MTLLibrary 56 | 57 | 58 | 59 | ///The `MTLCommandQueue` that is used to create `MTLCommandEncoder`s for each kernel. 60 | open let commandQueue: MTLCommandQueue 61 | 62 | 63 | 64 | internal var grad3Buffer: MTLBuffer 65 | 66 | 67 | 68 | internal var grad4Buffer: MTLBuffer 69 | 70 | 71 | 72 | internal var permBuffer: MTLBuffer 73 | 74 | 75 | 76 | internal var permMod12Buffer: MTLBuffer 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | // MARK:- Initialiser 88 | 89 | 90 | /** 91 | Creates a new `AHNContext` object for use with `AHNoise` modules. 92 | 93 | - parameter device: (Optional) The `MTLDevice` used throughout the `AHNoise` framework.. 94 | */ 95 | private init(device: MTLDevice?) { 96 | guard let device = device else{ 97 | fatalError("AHNoise: Error creating MTLDevice).") 98 | } 99 | self.device = device 100 | 101 | guard let library = device.newDefaultLibrary() else{ 102 | fatalError("AHNoise: Error creating default library.") 103 | } 104 | self.library = library 105 | 106 | commandQueue = device.makeCommandQueue() 107 | 108 | 109 | var grad3 = [float3(1,1,0), float3(-1,1,0), float3(1,-1,0), float3(-1,-1, 0), float3(1,0,1), float3(-1,0,1), float3(1,0,-1), float3(-1,0,-1), float3(0,1,1), float3(0,-1,1), float3(0,1,-1), float3(0,-1,-1)] 110 | grad3Buffer = device.makeBuffer(bytes: &grad3, length: MemoryLayout.size * grad3.count, options: MTLResourceOptions.storageModeShared) 111 | 112 | var grad4 = [float4(0,1,1,1), float4(0,1,1,-1), float4(0,1,-1,1), float4(0,1,-1,-1), float4(0,-1,1,1), float4(0,-1,1,-1), float4(0,-1,-1,1), float4(0,-1,-1,-1), float4(1,0,1,1), float4(1,0,1,-1), float4(1,0,-1,1), float4(1,0,-1,-1), float4(-1,0,1,1), float4(-1,0,1,-1), float4(-1,0,-1,1), float4(-1,0,-1,-1), float4(1,1,0,1), float4(1,1,0,-1), float4(1,-1,0,1), float4(1,-1,0,-1), float4(-1,1,0,1), float4(-1,1,0,-1), float4(-1,-1,0,1), float4(-1,-1,0,-1), float4(1,1,1,0), float4(1,1,-1,0), float4(1,-1,1,0), float4(1,-1,-1,0), float4(-1,1,1,0), float4(-1,1,-1,0), float4(-1,-1,1,0), float4(-1,-1,-1,0)] 113 | grad4Buffer = device.makeBuffer(bytes: &grad4, length: MemoryLayout.size * grad4.count, options: .storageModeShared) 114 | 115 | var perm: [Int32] = [151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180] 116 | permBuffer = device.makeBuffer(bytes: &perm, length: MemoryLayout.size * perm.count, options: .storageModeShared) 117 | 118 | var permMod12: [Int32] = [7,4,5,7,6,3,11,1,9,11,0,5,2,5,7,9,8,0,7,6,9,10,8,3,1,0,9,10,11,10,6,4,7,0,6,3,0,2,5,2,10,0,3,11,9,11,11,8,9,9,9,4,9,5,8,3,6,8,5,4,3,0,8,7,2,9,11,2,7,0,3,10,5,2,2,3,11,3,1,2,0,7,1,2,4,9,8,5,7,10,5,4,4,6,11,6,5,1,3,5,1,0,8,1,5,4,0,7,4,5,6,1,8,4,3,10,8,8,3,2,8,4,1,6,5,6,3,4,4,1,10,10,4,3,5,10,2,3,10,6,3,10,1,8,3,2,11,11,11,4,10,5,2,9,4,6,7,3,2,9,11,8,8,2,8,10,7,10,5,9,5,11,11,7,4,9,9,10,3,1,7,2,0,2,7,5,8,4,10,5,4,8,2,6,1,0,11,10,2,1,10,6,0,0,11,11,6,1,9,3,1,7,9,2,11,11,1,0,10,7,1,7,10,1,4,0,0,8,7,1,2,9,7,4,6,2,6,8,1,9,6,6,7,5,0,0,3,9,8,3,6,6,11,1,0,0,7,4,5,7,6,3,11,1,9,11,0,5,2,5,7,9,8,0,7,6,9,10,8,3,1,0,9,10,11,10,6,4,7,0,6,3,0,2,5,2,10,0,3,11,9,11,11,8,9,9,9,4,9,5,8,3,6,8,5,4,3,0,8,7,2,9,11,2,7,0,3,10,5,2,2,3,11,3,1,2,0,7,1,2,4,9,8,5,7,10,5,4,4,6,11,6,5,1,3,5,1,0,8,1,5,4,0,7,4,5,6,1,8,4,3,10,8,8,3,2,8,4,1,6,5,6,3,4,4,1,10,10,4,3,5,10,2,3,10,6,3,10,1,8,3,2,11,11,11,4,10,5,2,9,4,6,7,3,2,9,11,8,8,2,8,10,7,10,5,9,5,11,11,7,4,9,9,10,3,1,7,2,0,2,7,5,8,4,10,5,4,8,2,6,1,0,11,10,2,1,10,6,0,0,11,11,6,1,9,3,1,7,9,2,11,11,1,0,10,7,1,7,10,1,4,0,0,8,7,1,2,9,7,4,6,2,6,8,1,9,6,6,7,5,0,0,3,9,8,3,6,6,11,1,0,0] 119 | permMod12Buffer = device.makeBuffer(bytes: &permMod12, length: MemoryLayout.size * permMod12.count, options: .storageModeShared) 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNGenerator.swift 3 | // Noise Studio 4 | // 5 | // Created by App Work on 23/06/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | The general class used to output a procedurally generated texture. This class is not instantiated directly, but is used by various subclasses. 13 | 14 | The output texture represents a 2D slice through a 3D geometric or noise function that can optionally be distorted in the x and y axes. 15 | 16 | *Conforms to the `AHNTextureProvider` protocol.* 17 | */ 18 | open class AHNGenerator: NSObject, AHNTextureProvider { 19 | // MARK:- Properties 20 | ///The `AHNContext` that is being used by the `AHNTextureProvider` to communicate with the GPU. This is taken from the `SharedContext` class property of `AHNContext`. 21 | open var context: AHNContext 22 | ///The `MTLComputePipelineState` used to run the `Metal` compute kernel on the GPU. 23 | let pipeline: MTLComputePipelineState 24 | ///The `MTLBuffer` used to communicate the constant values used by the compute kernel to the GPU. 25 | open var uniformBuffer: MTLBuffer? 26 | ///The `MTLTexture` that the compute kernel writes to as an output. 27 | var internalTexture: MTLTexture? 28 | ///The default uniform greyscale texture to use as a displacement texture that results in zero displacement. 29 | var defaultDisplaceTexture: MTLTexture? 30 | ///The texture to offset pixels by in the x axis. Pixel values less than `0.5` offset to the left, and above `0.5` offset to the right. 31 | open var xoffsetInput: AHNTextureProvider?{ 32 | didSet{ 33 | dirty = true 34 | } 35 | } 36 | ///The texture to offset pixels by in the y axis. Pixel values less than `0.5` offset downwards, and above `0.5` offset upwards. 37 | open var yoffsetInput: AHNTextureProvider?{ 38 | didSet{ 39 | dirty = true 40 | } 41 | } 42 | ///The intensity of the effects of the `xoffsetInput` and `yoffsetInput`. A value of `0.0` results in no displacement. The default value is `0.2`. 43 | open var offsetStrength: Float = 0.2{ 44 | didSet{ 45 | dirty = true 46 | } 47 | } 48 | ///The angle (in radians) by which to rotate the 2D slice of the texture about the x axis of the 3D space of the geometric or noise `kernelFunction`. The default value is `0.0`. 49 | open var xRotation: Float = 0{ 50 | didSet{ 51 | dirty = true 52 | } 53 | } 54 | ///The angle (in radians) by which to rotate the 2D slice of the texture about the y axis of the 3D space of the geometric or noise `kernelFunction`. The default value is `0.0`. 55 | open var yRotation: Float = 0{ 56 | didSet{ 57 | dirty = true 58 | } 59 | } 60 | ///The angle (in radians) by which to rotate the 2D slice of the texture about the z axis of the 3D space of the geometric or noise `kernelFunction`. The default value is `0.0`. 61 | open var zRotation: Float = 0{ 62 | didSet{ 63 | dirty = true 64 | } 65 | } 66 | /** 67 | The `MTLFunction` compute kernel that generates the output `MTLTexture` property. 68 | 69 | The function used is specific to each class. 70 | */ 71 | let kernelFunction: MTLFunction 72 | ///Indicates whether or not the `internalTexture` needs updating. 73 | open var dirty: Bool = true 74 | /** 75 | The width of the output `MTLTexure` in pixels. The default value is `128`. 76 | */ 77 | open var textureWidth: Int = 128{ 78 | didSet{ 79 | dirty = true 80 | } 81 | } 82 | /** 83 | The height of the output `MTLTexure` in pixels. The default value is `128`. 84 | */ 85 | open var textureHeight: Int = 128{ 86 | didSet{ 87 | dirty = true 88 | } 89 | } 90 | // MARK:- Inititalisers 91 | /** 92 | Creates a new `AHNGenerator` object. 93 | 94 | To be called when instantiating a subclass. 95 | 96 | - parameter functionName: The name of the kernel function that this generator will use to create an output. 97 | */ 98 | public init(functionName: String){ 99 | context = AHNContext.SharedContext 100 | guard let kernelFunction = context.library.makeFunction(name: functionName) else{ 101 | fatalError("AHNoise: Error loading function \(functionName).") 102 | } 103 | self.kernelFunction = kernelFunction 104 | 105 | do{ 106 | try pipeline = context.device.makeComputePipelineState(function: kernelFunction) 107 | }catch let error{ 108 | fatalError("AHNoise: Error creating pipeline state for \(functionName).\n\(error)") 109 | } 110 | dirty = true 111 | super.init() 112 | } 113 | override public required init(){ 114 | context = AHNContext.SharedContext 115 | // Load the kernel function and compute pipeline state 116 | guard let kernelFunction = context.library.makeFunction(name: "simplexGenerator") else{ 117 | fatalError("AHNoise: Error loading function simplexGenerator.") 118 | } 119 | self.kernelFunction = kernelFunction 120 | 121 | do{ 122 | try pipeline = context.device.makeComputePipelineState(function: kernelFunction) 123 | }catch let error{ 124 | fatalError("AHNoise: Error creating pipeline state for simplexGenerator.\n\(error)") 125 | } 126 | 127 | super.init() 128 | } 129 | // MARK:- Configure Uniforms 130 | /** 131 | This function is overridden by subclasses to write class specific variables to the `uniformBuffer`. 132 | 133 | - parameter commandEncoder: The `MTLComputeCommandEncoder` used to run the kernel. This can be used to lazily create a buffer of data and add it to the argument table. Any buffer index can be used without affecting the rest of this class. 134 | */ 135 | open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder){ 136 | } 137 | // MARK:- Texture Functions 138 | /** 139 | Updates the output `MTLTexture`. 140 | 141 | This should not need to be called manually as it is called by the `texture()` method automatically if the texture does not represent the current properties. 142 | */ 143 | open func updateTexture(){ 144 | if internalTexture == nil{ 145 | newInternalTexture() 146 | } 147 | if internalTexture!.width != textureWidth || internalTexture!.height != textureHeight{ 148 | newInternalTexture() 149 | } 150 | 151 | let threadGroupsCount = MTLSizeMake(8, 8, 1) 152 | let threadGroups = MTLSizeMake(textureWidth / threadGroupsCount.width, textureHeight / threadGroupsCount.height, 1) 153 | 154 | let commandBuffer = context.commandQueue.makeCommandBuffer() 155 | 156 | let commandEncoder = commandBuffer.makeComputeCommandEncoder() 157 | commandEncoder.setComputePipelineState(pipeline) 158 | commandEncoder.setTexture(internalTexture, at: 0) 159 | commandEncoder.setTexture(xoffsetInput?.texture() ?? defaultDisplaceTexture!, at: 1) 160 | commandEncoder.setTexture(yoffsetInput?.texture() ?? defaultDisplaceTexture!, at: 2) 161 | 162 | configureArgumentTableWithCommandencoder(commandEncoder) 163 | commandEncoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupsCount) 164 | commandEncoder.endEncoding() 165 | 166 | commandBuffer.commit() 167 | commandBuffer.waitUntilCompleted() 168 | 169 | dirty = false 170 | } 171 | ///Create a new `internalTexture` for the first time or whenever the texture is resized. 172 | func newInternalTexture(){ 173 | let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: textureWidth, height: textureHeight, mipmapped: false) 174 | internalTexture = context.device.makeTexture(descriptor: textureDescriptor) 175 | 176 | let grey: [UInt8] = [128, 128, 128, 255] 177 | var textureBytes: [UInt8] = [] 178 | for _ in 0.. MTLTexture?{ 188 | if isDirty(){ 189 | updateTexture() 190 | } 191 | return internalTexture 192 | } 193 | ///- returns: The size of the output `MTLTexture`. 194 | open func textureSize() -> MTLSize{ 195 | return MTLSizeMake(textureWidth, textureHeight, 1) 196 | } 197 | ///- returns: A boolean value indicating whether or not the texture need updating to include updated properties. 198 | open func isDirty() -> Bool { 199 | let dirtyProvider1 = xoffsetInput?.isDirty() ?? false 200 | let dirtyProvider2 = yoffsetInput?.isDirty() ?? false 201 | return dirtyProvider1 || dirtyProvider2 || dirty 202 | } 203 | ///- returns: `True` as this a generator can always update. 204 | open func canUpdate() -> Bool { 205 | return true 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGeneratorBillow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNBillowGenerator.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 24/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | ///Generates a cohesive billow effect, useful for creating clouds. The noise created lies within the range -1.0 - 1.0 numerically [0.0 - 1.0 in colour space]. 15 | /// 16 | ///*Conforms to the `AHNTextureProvider` protocol.* 17 | open class AHNGeneratorBillow: AHNGeneratorCoherent { 18 | 19 | 20 | // MARK:- Initialiser 21 | 22 | 23 | required public init(){ 24 | super.init(functionName: "billowGenerator") 25 | } 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | // MARK:- Argument table update 41 | 42 | 43 | ///Encodes the required uniform values for this `AHNGenerator` subclass. This should never be called directly. 44 | override open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 45 | super.configureArgumentTableWithCommandencoder(commandEncoder) 46 | var uniforms = CoherentInputs(pos: vector_float2(xValue, yValue), rotations: vector_float3(xRotation, yRotation, zRotation), octaves: Int32(octaves), persistence: persistence, frequency: frequency, lacunarity: lacunarity, zValue: zValue, wValue: wValue, offsetStrength: offsetStrength, use4D: Int32(use4D || seamless || sphereMap ? 1 : 0), sphereMap: Int32(sphereMap ? 1 : 0), seamless: Int32(seamless ? 1 : 0)) 47 | 48 | if uniformBuffer == nil{ 49 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: .storageModeShared) 50 | } 51 | 52 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 53 | 54 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 4) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGeneratorChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNGeneratorChecker.swift 3 | // Noise Studio 4 | // 5 | // Created by App Work on 23/06/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import simd 11 | 12 | 13 | ///Generates a texture representing a slice through a field of alternating black and white cubes. 14 | /// 15 | ///*Conforms to the `AHNTextureProvider` protocol.* 16 | open class AHNGeneratorChecker: AHNGenerator { 17 | 18 | 19 | // MARK:- Properties 20 | 21 | 22 | ///The frequency of the cubes, higher values result in more, closer packed cubes. The default value is `1.0`. 23 | open var frequency: Float = 1{ 24 | didSet{ 25 | dirty = true 26 | } 27 | } 28 | 29 | 30 | 31 | ///The value along the z axis that the texture slice is taken. The default value is `0.0`. 32 | open var zValue: Float = 0{ 33 | didSet{ 34 | dirty = true 35 | } 36 | } 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | // MARK:- Initialiser 50 | 51 | 52 | required public init(){ 53 | super.init(functionName: "checkerGenerator") 54 | } 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | // MARK:- Argument table update 70 | 71 | 72 | ///Encodes the required uniform values for this `AHNGenerator` subclass. This should never be called directly. 73 | override open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 74 | var uniforms = GeometricInputs(offset: 0, frequency: frequency, xPosition: 0, yPosition: 0, zValue: zValue, offsetStrength: offsetStrength, rotations: vector_float3(xRotation, yRotation, zRotation)) 75 | 76 | if uniformBuffer == nil{ 77 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: .storageModeShared) 78 | } 79 | 80 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 81 | 82 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGeneratorCoherent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNGeneratorCoherent.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 22/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | import Metal 12 | import simd 13 | 14 | 15 | 16 | ///Struct used to communicate properties to the GPU. 17 | struct CoherentInputs { 18 | var pos: vector_float2 19 | var rotations: vector_float3 20 | var octaves: Int32 21 | var persistence: Float 22 | var frequency: Float 23 | var lacunarity: Float 24 | var zValue: Float 25 | var wValue: Float 26 | var offsetStrength: Float 27 | var use4D: Int32 28 | var sphereMap: Int32 29 | var seamless: Int32 30 | } 31 | 32 | 33 | /** 34 | The general class to generate cohesive noise outputs. This class is not instantiated directly, but is used by various subclasses. 35 | 36 | The output texture represents a 2D slice through a 3D geometric or noise function that can optionally be distorted in the x and y axes. 37 | 38 | *Conforms to the `AHNTextureProvider` protocol.* 39 | */ 40 | open class AHNGeneratorCoherent: AHNGenerator { 41 | 42 | 43 | // MARK:- Properties 44 | 45 | 46 | ///When `true`, the simplex kernel used has four degrees of freedom, which allows for interesting seamless patterns but has extra computational cost. This has no effect for the `AHNGeneratorVoronoi`, which is always calculated in 4 dimensions. The default value is `false`. 47 | open var use4D: Bool = false{ 48 | didSet{ 49 | dirty = true 50 | } 51 | } 52 | 53 | 54 | ///When `true`, the output texture is warped towards the top and bottom to seamless map to a UV sphere. When `true` the `zValue` and `wValue` properties have no effect as they are overridden in the shader to produce the sphere mapped effect. The default value is `false`. 55 | open var sphereMap: Bool = false{ 56 | didSet{ 57 | dirty = true 58 | } 59 | } 60 | 61 | 62 | 63 | ///When `true`, the output texture can be seamlessly tiled without apparent edges showing. When `true` the `zValue` and `wValue` properties have no effect as they are overridden in the shader to produce the seamless effect. The default value is `false`. 64 | open var seamless: Bool = false{ 65 | didSet{ 66 | dirty = true 67 | } 68 | } 69 | 70 | 71 | 72 | /** 73 | The number of `octaves` to use in the texture. Each `octave` is calculated with a different amplitude (altered by the `persistence` property) and `frequency` (altered by the `lacunarity` property). The amplitude starts with a value of `1.0`, the first octave is calculated using this value, the amplitude is then multiplied by the `persistence` and the next octave is calculated using this new amplitude before multiplying it again by the `persistence` and so on. The `frequency` follows a similar pattern with the `lacunarity` property. 74 | 75 | Each `octave` is calculated and then combined to produce the final value. 76 | 77 | Higher values (`12`) produce more detailed noise, where as lower values produce smoother noise. Higher values have a performance impact. 78 | 79 | The default value is `6`. 80 | */ 81 | open var octaves: Int = 6{ 82 | didSet{ 83 | dirty = true 84 | } 85 | } 86 | 87 | 88 | 89 | /** 90 | Varies the amplitude every octave. The amplitude is multiplied by the `persistence` for each octave. Generally values less than 1.0 are used. 91 | 92 | For example an initial amplitude of `1.0` (fixed) and a `persistence` of `0.5` for `4` octaves would produce an amplitude of `1.0`, `0.5`, `0.25` and `0.125` respectively for each octave. 93 | 94 | The default value is `0.5`. 95 | */ 96 | open var persistence: Float = 0.5{ 97 | didSet{ 98 | dirty = true 99 | } 100 | } 101 | 102 | 103 | 104 | /** 105 | The frequency used when calculating the noise. Higher values produce more dense noise. 106 | 107 | The `frequency` is multiplied by the `lacunarity` property each octave. 108 | 109 | The default value is `1.0`. 110 | */ 111 | open var frequency: Float = 1.0{ 112 | didSet{ 113 | dirty = true 114 | } 115 | } 116 | 117 | 118 | 119 | /** 120 | Varies the `frequency` every octave. The `frequency` is multiplied by the `lacunarity` for each octave. Generally values greater than `1.0` are used. 121 | 122 | For example an initial `frequency` of `1.0` and a `lacunarity` of `2.0` for `4` octaves would produce a `frequency` of `1.0`, `2.0`, `4.0` and `8.0` respectively for each octave. 123 | 124 | The default value is `2.0`. 125 | */ 126 | open var lacunarity: Float = 2.0{ 127 | didSet{ 128 | dirty = true 129 | } 130 | } 131 | 132 | 133 | 134 | ///The origin of the noise along the x axis in noise space. Changing this slightly will make the noise texture appear to move. 135 | /// 136 | ///The default value is `1.0`. 137 | open var xValue: Float = 1{ 138 | didSet{ 139 | dirty = true 140 | } 141 | } 142 | 143 | 144 | 145 | ///The origin of the noise along the y axis in noise space. Changing this slightly will make the noise texture appear to move. 146 | /// 147 | ///The default value is `1.0`. 148 | open var yValue: Float = 1{ 149 | didSet{ 150 | dirty = true 151 | } 152 | } 153 | 154 | 155 | 156 | ///The value for the third dimension when calculating the noise. Changing this slightly will make the noise texture appear to animate. 157 | /// 158 | ///Default is `1.0`. 159 | open var zValue: Float = 1{ 160 | didSet{ 161 | dirty = true 162 | } 163 | } 164 | 165 | 166 | 167 | ///The value for the fourth dimension when calculating the noise. Changing this slightly will make the noise texture appear to animate. 168 | /// 169 | ///Default is `1.0`. 170 | open var wValue: Float = 1{ 171 | didSet{ 172 | dirty = true 173 | } 174 | } 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | // MARK:- Initialiser 186 | 187 | 188 | /** 189 | Creates a new `AHNGeneratorCoherent` object. 190 | 191 | To be called when instantiating a subclass. 192 | 193 | - parameter functionName: The name of the kernel function that this this generator will use to create an output. 194 | */ 195 | public override init(functionName: String){ 196 | super.init(functionName: functionName) 197 | } 198 | 199 | 200 | 201 | public required init() { 202 | super.init() 203 | } 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | // MARK:- Array buffer binding 213 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 214 | commandEncoder.setBuffer(context.grad3Buffer, offset: 0, at: 0) 215 | commandEncoder.setBuffer(context.grad4Buffer, offset: 0, at: 1) 216 | commandEncoder.setBuffer(context.permBuffer, offset: 0, at: 2) 217 | commandEncoder.setBuffer(context.permMod12Buffer, offset: 0, at: 3) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGeneratorConstant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNGeneratorConstant.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 25/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | ///Generates a solid colour based on red, green and blue colour values. Can be used to colourise other noise modules. 15 | /// 16 | ///*Conforms to the `AHNTextureProvider` protocol.* 17 | open class AHNGeneratorConstant: AHNGenerator { 18 | 19 | 20 | // MARK:- Properties 21 | 22 | 23 | ///The red component of the colour to be output in the range `0.0 - 1.0`. The default value is `0.5`. 24 | open var red: Float = 0.5{ 25 | didSet{ 26 | dirty = true 27 | } 28 | } 29 | 30 | 31 | 32 | ///The green component of the colour to be output in the range `0.0 - 1.0`. The default value is `0.5`. 33 | open var green: Float = 0.5{ 34 | didSet{ 35 | dirty = true 36 | } 37 | } 38 | 39 | 40 | 41 | ///The blue component of the colour to be output in the range `0.0 - 1.0`. The default value is `0.5`. 42 | open var blue: Float = 0.5{ 43 | didSet{ 44 | dirty = true 45 | } 46 | } 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | // MARK:- Initialiser 59 | 60 | 61 | required public init(){ 62 | super.init(functionName: "uniformGenerator") 63 | } 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | // Argument table update 74 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 75 | var uniforms = vector_float3(red,green,blue) 76 | 77 | if uniformBuffer == nil{ 78 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: .storageModeShared) 79 | } 80 | 81 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 82 | 83 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGeneratorCylinder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNGeneratorCylinder.swift 3 | // Noise Studio 4 | // 5 | // Created by App Work on 23/06/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import simd 11 | 12 | 13 | ///Struct used to communicate properties to the GPU. 14 | internal struct GeometricInputs{ 15 | var offset: Float 16 | var frequency: Float 17 | var xPosition: Float 18 | var yPosition: Float 19 | var zValue: Float 20 | var offsetStrength: Float 21 | var rotations: vector_float3 22 | } 23 | 24 | 25 | ///Generates a texture representing a slice through a field of concentric cylinders. 26 | /// 27 | ///*Conforms to the `AHNTextureProvider` protocol.* 28 | open class AHNGeneratorCylinder: AHNGenerator { 29 | 30 | 31 | 32 | // MARK:- Properties 33 | 34 | 35 | ///The distance to offset the first cylinder from the centre by. The default value is `0.0`. 36 | open var offset: Float = 0{ 37 | didSet{ 38 | dirty = true 39 | } 40 | } 41 | 42 | 43 | 44 | ///The frequency of the cylinders, higher values result in more, closer packed cylinders. The default value is `1.0`. 45 | open var frequency: Float = 1{ 46 | didSet{ 47 | dirty = true 48 | } 49 | } 50 | 51 | 52 | 53 | ///The position along the x axis that the cylinders are centred on. A value of `0.0` corresponds to the left texture edge, and a value of `1.0` corresponds to the right texture edge. The default value is `0.5`. 54 | open var xPosition: Float = 0.5{ 55 | didSet{ 56 | dirty = true 57 | } 58 | } 59 | 60 | 61 | 62 | ///The position along the y axis that the cylinders are centred on. A value of `0.0` corresponds to the bottom texture edge, and a value of `1.0` corresponds to the top texture edge. The default value is `0.5`. 63 | open var yPosition: Float = 0.5{ 64 | didSet{ 65 | dirty = true 66 | } 67 | } 68 | 69 | 70 | 71 | ///The value along the z axis that the texture slice is taken. The default value is `0.0`. 72 | open var zValue: Float = 0{ 73 | didSet{ 74 | dirty = true 75 | } 76 | } 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | // MARK:- Initialiser 94 | 95 | 96 | required public init(){ 97 | super.init(functionName: "cylinderGenerator") 98 | } 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | // MARK:- Argument table update 114 | 115 | 116 | ///Encodes the required uniform values for this `AHNGenerator` subclass. This should never be called directly. 117 | override open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 118 | var uniforms = GeometricInputs(offset: offset, frequency: frequency, xPosition: xPosition, yPosition: yPosition, zValue: zValue, offsetStrength: offsetStrength, rotations: vector_float3(xRotation, yRotation, zRotation)) 119 | 120 | if uniformBuffer == nil{ 121 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: .storageModeShared) 122 | } 123 | 124 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 125 | 126 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGeneratorGradientBox.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNGeneratorGradientBox.swift 3 | // Noise Studio 4 | // 5 | // Created by App Work on 07/07/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import simd 11 | 12 | 13 | ///Generates a box gradient texture comprising four linear gradients originating from the four texture edges. 14 | /// 15 | ///*Conforms to the `AHNTextureProvider` protocol.* 16 | open class AHNGeneratorGradientBox: AHNGenerator { 17 | 18 | 19 | 20 | 21 | // MARK:- Properties 22 | 23 | 24 | ///The falloff of the gradients originating from the left and right hand texture edges. The default value of `0.0` results in the horizontal gradients terminating at the centre of the texture, higher values cause the gradient to terminate closer to its originating edge. 25 | open var xFallOff: Float = 0{ 26 | didSet{ 27 | dirty = true 28 | } 29 | } 30 | 31 | 32 | 33 | ///The falloff of the gradients originating from the top and bottom texture edges. The default value of `0.0` results in the vertical gradients terminating at the centre of the texture, higher values cause the gradient to terminate closer to its originating edge. 34 | open var yFallOff: Float = 0{ 35 | didSet{ 36 | dirty = true 37 | } 38 | } 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | // MARK:- Initialiser 53 | 54 | 55 | required public init(){ 56 | super.init(functionName: "boxGradientGenerator") 57 | } 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | // MARK:- Argument table update 73 | 74 | 75 | ///Encodes the required uniform values for this `AHNGenerator` subclass. This should never be called directly. 76 | override open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 77 | var uniforms = GradientInputs(positions: vector_float4(xFallOff, yFallOff, 0, 0), offsetStrength: offsetStrength, rotations: vector_float3(xRotation, yRotation, zRotation)) 78 | 79 | if uniformBuffer == nil{ 80 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: .storageModeShared) 81 | } 82 | 83 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 84 | 85 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGeneratorGradientLinear.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNGeneratorGradientLinear.swift 3 | // Noise Studio 4 | // 5 | // Created by App Work on 06/07/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import simd 11 | 12 | 13 | ///Struct used to communicate properties to the GPU. 14 | struct GradientInputs { 15 | var positions: vector_float4 16 | var offsetStrength: Float 17 | var rotations: vector_float3 18 | } 19 | 20 | 21 | ///Generates a linear gradient texture between two control points. 22 | /// 23 | ///*Conforms to the `AHNTextureProvider` protocol.* 24 | open class AHNGeneratorGradientLinear: AHNGenerator { 25 | 26 | 27 | // MARK:- Properties 28 | 29 | 30 | ///The location along the x axis of the first control point. A value of `0.0` corresponds to the left hand edge and a value of `1.0` corresponds to the right hand edge. The default value is `0.0`. 31 | open var startPositionX: Float = 0{ 32 | didSet{ 33 | dirty = true 34 | } 35 | } 36 | 37 | 38 | 39 | ///The location along the y axis of the first control point. A value of `0.0` corresponds to the bottom edge and a value of `1.0` corresponds to the top edge. The default value is `0.0`. 40 | open var startPositionY: Float = 0{ 41 | didSet{ 42 | dirty = true 43 | } 44 | } 45 | 46 | 47 | 48 | ///The location along the x axis of the second control point. A value of `0.0` corresponds to the left hand edge and a value of `1.0` corresponds to the right hand edge. The default value is `1.0`. 49 | open var endPositionX: Float = 1{ 50 | didSet{ 51 | dirty = true 52 | } 53 | } 54 | 55 | 56 | 57 | ///The location along the y axis of the second control point. A value of `0.0` corresponds to the bottom edge and a value of `1.0` corresponds to the top edge. The default value is `1.0`. 58 | open var endPositionY: Float = 1{ 59 | didSet{ 60 | dirty = true 61 | } 62 | } 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | // MARK:- Initialiser 76 | 77 | 78 | required public init(){ 79 | super.init(functionName: "linearGradientGenerator") 80 | } 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | // MARK:- Argument table update 96 | 97 | 98 | ///Encodes the required uniform values for this `AHNGenerator` subclass. This should never be called directly. 99 | override open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 100 | var uniforms = GradientInputs(positions: vector_float4(startPositionX, startPositionY, endPositionX, endPositionY), offsetStrength: offsetStrength, rotations: vector_float3(xRotation, yRotation, zRotation)) 101 | 102 | if uniformBuffer == nil{ 103 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: .storageModeShared) 104 | } 105 | 106 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 107 | 108 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGeneratorGradientRadial.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNGeneratorGradientRadial.swift 3 | // Noise Studio 4 | // 5 | // Created by App Work on 07/07/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import simd 11 | 12 | 13 | ///Generates a radial gradient texture originating from a control point. 14 | /// 15 | ///*Conforms to the `AHNTextureProvider` protocol.* 16 | open class AHNGeneratorGradientRadial: AHNGenerator { 17 | 18 | 19 | // MARK:- Properties 20 | 21 | 22 | ///The location along the x axis of the control point that the gradient is centred on. A value of `0.0` corresponds to the left hand edge and a value of `1.0` corresponds to the right hand edge. The default value is `0.5`. 23 | open var xPosition: Float = 0.5{ 24 | didSet{ 25 | dirty = true 26 | } 27 | } 28 | 29 | 30 | 31 | ///The location along the y axis of the control point that the gradient is centred on. A value of `0.0` corresponds to the bottom edge and a value of `1.0` corresponds to the top edge. The default value is `0.5`. 32 | open var yPosition: Float = 0.5{ 33 | didSet{ 34 | dirty = true 35 | } 36 | } 37 | 38 | 39 | 40 | ///The horizontal falloff of the radial gradient. A value of `1.0` results in the gradient terminating at the edges of the texture, lower values cause the gradient to extend beyond the edge of the texture and vice versa. 41 | open var xFallOff: Float = 1{ 42 | didSet{ 43 | dirty = true 44 | } 45 | } 46 | 47 | 48 | 49 | ///The vertical falloff of the radial gradient. A value of `1.0` results in the gradient terminating at the edges of the texture, lower values cause the gradient to extend beyond the edge of the texture and vice versa. 50 | open var yFallOff: Float = 1{ 51 | didSet{ 52 | dirty = true 53 | } 54 | } 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | // MARK:- Initialiser 70 | 71 | 72 | required public init(){ 73 | super.init(functionName: "radialGradientGenerator") 74 | } 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | // MARK:- Argument table update 90 | 91 | 92 | ///Encodes the required uniform values for this `AHNGenerator` subclass. This should never be called directly. 93 | override open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 94 | var uniforms = GradientInputs(positions: vector_float4(xPosition, yPosition, xFallOff, yFallOff), offsetStrength: offsetStrength, rotations: vector_float3(xRotation, yRotation, zRotation)) 95 | 96 | if uniformBuffer == nil{ 97 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: .storageModeShared) 98 | } 99 | 100 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 101 | 102 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGeneratorRidgedMulti.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNRidgedMultiGenerator.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 24/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | ///Generates cohesive ridges, useful as height maps for mountainous regions. The noise created lies within the range `0.0 - 1.0`. 15 | /// 16 | ///*Conforms to the `AHNTextureProvider` protocol.* 17 | open class AHNGeneratorRidgedMulti: AHNGeneratorCoherent { 18 | 19 | 20 | // MARK:- Initialiser 21 | 22 | 23 | required public init(){ 24 | super.init(functionName: "ridgedMultiGenerator") 25 | } 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | // MARK:- Argument table update 43 | 44 | 45 | ///Encodes the required uniform values for this `AHNGenerator` subclass. This should never be called directly. 46 | override open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 47 | super.configureArgumentTableWithCommandencoder(commandEncoder) 48 | var uniforms = CoherentInputs(pos: vector_float2(xValue, yValue), rotations: vector_float3(xRotation, yRotation, zRotation), octaves: Int32(octaves), persistence: persistence, frequency: frequency, lacunarity: lacunarity, zValue: zValue, wValue: wValue, offsetStrength: offsetStrength, use4D: Int32(use4D || seamless || sphereMap ? 1 : 0), sphereMap: Int32(sphereMap ? 1 : 0), seamless: Int32(seamless ? 1 : 0)) 49 | 50 | if uniformBuffer == nil{ 51 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: .storageModeShared) 52 | } 53 | 54 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 55 | 56 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 4) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGeneratorSimplex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNSimplex3DGenerator.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 23/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | ///Generates standard Simplex Noise. The noise created lies within the range `0.0 - 1.0`. 15 | /// 16 | ///*Conforms to the `AHNTextureProvider` protocol.* 17 | open class AHNGeneratorSimplex: AHNGeneratorCoherent { 18 | 19 | 20 | // MARK:- Initialiser 21 | 22 | 23 | required public init(){ 24 | super.init(functionName: "simplexGenerator") 25 | } 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | // MARK:- Argument table update 47 | 48 | 49 | ///Encodes the required uniform values for this `AHNGenerator` subclass. This should never be called directly. 50 | override open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 51 | super.configureArgumentTableWithCommandencoder(commandEncoder) 52 | var uniforms = CoherentInputs(pos: vector_float2(xValue, yValue), rotations: vector_float3(xRotation, yRotation, zRotation), octaves: Int32(octaves), persistence: persistence, frequency: frequency, lacunarity: lacunarity, zValue: zValue, wValue: wValue, offsetStrength: offsetStrength, use4D: Int32(use4D || seamless || sphereMap ? 1 : 0), sphereMap: Int32(sphereMap ? 1 : 0), seamless: Int32(seamless ? 1 : 0)) 53 | 54 | if uniformBuffer == nil{ 55 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: .storageModeShared) 56 | } 57 | 58 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 59 | 60 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 4) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGeneratorSphere.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNGeneratorphere.swift 3 | // Noise Studio 4 | // 5 | // Created by App Work on 23/06/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import simd 11 | 12 | 13 | ///Generates a texture representing a slice through a field of concentric spheres. 14 | /// 15 | ///*Conforms to the `AHNTextureProvider` protocol.* 16 | open class AHNGeneratorSphere: AHNGenerator { 17 | 18 | 19 | // MARK:- Properties 20 | 21 | 22 | ///The distance to offset the first sphere from the centre by. The default value is `0.0`. 23 | open var offset: Float = 0{ 24 | didSet{ 25 | dirty = true 26 | } 27 | } 28 | 29 | 30 | 31 | ///The frequency of the spheres, higher values result in more, closer packed spheres. The default value is `1.0`. 32 | open var frequency: Float = 1{ 33 | didSet{ 34 | dirty = true 35 | } 36 | } 37 | 38 | 39 | 40 | ///The position along the x axis that the spheres are centred on. A value of `0.0` corresponds to the left texture edge, and a value of `1.0` cooresponds to the right texture edge. The default value is `0.5`. 41 | open var xPosition: Float = 0.5{ 42 | didSet{ 43 | dirty = true 44 | } 45 | } 46 | 47 | 48 | 49 | ///The position along the y axis that the spheres are centred on. A value of `0.0` corresponds to the bottom texture edge, and a value of `1.0` cooresponds to the top texture edge. The default value is `0.5`. 50 | open var yPosition: Float = 0.5{ 51 | didSet{ 52 | dirty = true 53 | } 54 | } 55 | 56 | 57 | 58 | ///The value along the z axis that the texture slice is taken. The default value is `0.0`. 59 | open var zValue: Float = 0{ 60 | didSet{ 61 | dirty = true 62 | } 63 | } 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | // MARK:- Initialiser 78 | 79 | 80 | required public init(){ 81 | super.init(functionName: "sphereGenerator") 82 | } 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | // MARK:- Argument table update 98 | 99 | 100 | ///Encodes the required uniform values for this `AHNGenerator` subclass. This should never be called directly. 101 | override open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 102 | var uniforms = GeometricInputs(offset: offset, frequency: frequency, xPosition: xPosition, yPosition: yPosition, zValue: zValue, offsetStrength: offsetStrength, rotations: vector_float3(xRotation, yRotation, zRotation)) 103 | 104 | if uniformBuffer == nil{ 105 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: .storageModeShared) 106 | } 107 | 108 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 109 | 110 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGeneratorVoronoi.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNGeneratorVoronoi.swift 3 | // Noise Studio 4 | // 5 | // Created by App Work on 23/06/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import simd 11 | 12 | 13 | ///Struct used to communicate properties to the GPU. 14 | struct VoronoiInputs { 15 | var pos: vector_float2 16 | var offsetStrength: Float 17 | var rotations: vector_float3 18 | var octaves: Int32 19 | var persistence: Float 20 | var frequency: Float 21 | var lacunarity: Float 22 | var zValue: Float 23 | var wValue: Float 24 | var sphereMap: Int32 25 | var seamless: Int32 26 | } 27 | 28 | 29 | ///Generates a texture of discrete cells, useful for representing crystals or dried mud. The noise created lies within the range `0.0 - 1.0`. 30 | /// 31 | ///*Conforms to the `AHNTextureProvider` protocol.* 32 | open class AHNGeneratorVoronoi: AHNGeneratorCoherent { 33 | 34 | 35 | // MARK:- Initialiser 36 | 37 | 38 | required public init(){ 39 | super.init(functionName: "voronoiGenerator") 40 | octaves = 1 41 | } 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | // MARK:- Argument table update 58 | 59 | 60 | ///Encodes the required uniform values for this `AHNGenerator` subclass. This should never be called directly. 61 | override open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 62 | super.configureArgumentTableWithCommandencoder(commandEncoder) 63 | var uniforms = VoronoiInputs(pos: vector_float2(xValue, yValue), offsetStrength: offsetStrength, rotations: vector_float3(xRotation, yRotation, zRotation), octaves: Int32(octaves), persistence: persistence, frequency: frequency, lacunarity: lacunarity, zValue: zValue, wValue: wValue, sphereMap: sphereMap ? 1 : 0, seamless: seamless ? 1 : 0) 64 | if uniformBuffer == nil{ 65 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: .storageModeShared) 66 | } 67 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 68 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 4) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNGeneratorWave.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNGeneratorWave.swift 3 | // Noise Studio 4 | // 5 | // Created by App Work on 07/07/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import simd 11 | 12 | 13 | ///Struct used to communicate properties to the GPU. 14 | struct WaveInputs { 15 | var frequency: Float 16 | var offsetStrength: Float 17 | var rotations: vector_float3 18 | } 19 | 20 | 21 | ///Generates a series of sinusoidal waves represented by black and white lines. 22 | /// 23 | ///*Conforms to the `AHNTextureProvider` protocol.* 24 | open class AHNGeneratorWave: AHNGenerator { 25 | 26 | 27 | // MARK:- Properties 28 | 29 | 30 | 31 | ///Increases the number and compactness of waves visible in the texture. The default value is `1.0`. 32 | open var frequency: Float = 1{ 33 | didSet{ 34 | dirty = true 35 | } 36 | } 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | // MARK:- Initialiser 48 | 49 | required public init(){ 50 | super.init(functionName: "waveGenerator") 51 | } 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | // MARK:- Argument table update 67 | 68 | 69 | ///Encodes the required uniform values for this `AHNGenerator` subclass. This should never be called directly. 70 | override open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 71 | var uniforms = WaveInputs(frequency: frequency, offsetStrength: offsetStrength, rotations: vector_float3(xRotation, yRotation, zRotation)) 72 | 73 | if uniformBuffer == nil{ 74 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: .storageModeShared) 75 | } 76 | 77 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 78 | 79 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifier.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 24/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | The general class to modify the outputs of any class that adheres to the `AHNTextureProvider` protocol. This class is not instantiated directly, but is used by various subclasses. 16 | 17 | *Conforms to the `AHNTextureProvider` protocol.* 18 | */ 19 | open class AHNModifier: NSObject, AHNTextureProvider { 20 | 21 | 22 | // MARK:- Properties 23 | 24 | 25 | ///The `AHNContext` that is being used by the `AHNTextureProvider` to communicate with the GPU. This is recovered from the first `AHNGenerator` class that is encountered in the chain of classes. 26 | open var context: AHNContext 27 | 28 | 29 | 30 | ///The `MTLComputePipelineState` used to run the `Metal` compute kernel on the GPU. 31 | let pipeline: MTLComputePipelineState 32 | 33 | 34 | 35 | ///The `MTLBuffer` used to transfer the constant values used by the compute kernel to the GPU. 36 | open var uniformBuffer: MTLBuffer? 37 | 38 | 39 | 40 | ///The `MTLTexture` that the compute kernel writes to as an output. 41 | var internalTexture: MTLTexture? 42 | 43 | 44 | 45 | /** 46 | The `MTLFunction` compute kernel that modifies the input `MTLTexture`s and writes the output to the `internalTexture` property. 47 | 48 | The function used is specific to each class. 49 | */ 50 | let kernelFunction: MTLFunction 51 | 52 | 53 | 54 | ///Indicates whether or not the `internalTexture` needs updating. 55 | open var dirty: Bool = true 56 | 57 | 58 | 59 | ///The input that will be modified to provide the output. 60 | open var provider: AHNTextureProvider? 61 | 62 | 63 | 64 | ///Indicates whether this modifier makes use of a `Metal Performance Shader` 65 | open var usesMPS = false 66 | 67 | 68 | 69 | /** 70 | The width of the output `MTLTexure`. 71 | 72 | This is dictated by the width of the texture of the input `AHNTextureProvider`. If there is no input, the default width is `128` pixels. 73 | */ 74 | open var width: Int{ 75 | get{ 76 | return provider?.textureSize().width ?? 128 77 | } 78 | } 79 | 80 | 81 | 82 | /** 83 | The height of the output `MTLTexure`. 84 | 85 | This is dictated by the height of the texture of the input `AHNTextureProvider`. If there is no input, the default height is `128` pixels. 86 | */ 87 | open var height: Int{ 88 | get{ 89 | return provider?.textureSize().height ?? 128 90 | } 91 | } 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | // MARK:- Initialiser 104 | 105 | 106 | /** 107 | Creates a new `AHNModifier` object. 108 | 109 | To be called when instantiating a subclass. 110 | 111 | - parameter functionName: The name of the kernel function that this modifier will use to modify the input. 112 | */ 113 | init(functionName: String) { 114 | context = AHNContext.SharedContext 115 | 116 | guard let kernelFunction = context.library.makeFunction(name: functionName) else{ 117 | fatalError("AHNoise: Error loading function \(functionName).") 118 | } 119 | self.kernelFunction = kernelFunction 120 | 121 | do{ 122 | try pipeline = context.device.makeComputePipelineState(function: kernelFunction) 123 | }catch let error{ 124 | fatalError("AHNoise: Error creating pipeline state for \(functionName).\n\(error)") 125 | } 126 | super.init() 127 | } 128 | 129 | 130 | override public required init(){ 131 | context = AHNContext.SharedContext 132 | 133 | // Load the kernel function and compute pipeline state 134 | guard let kernelFunction = context.library.makeFunction(name: "simplexGenerator") else{ 135 | fatalError("AHNoise: Error loading function simplexGenerator.") 136 | } 137 | self.kernelFunction = kernelFunction 138 | 139 | do{ 140 | try pipeline = context.device.makeComputePipelineState(function: kernelFunction) 141 | }catch let error{ 142 | fatalError("AHNoise: Error creating pipeline state for simplexGenerator.\n\(error)") 143 | } 144 | 145 | super.init() 146 | } 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | // MARK:- Configure Uniforms 160 | 161 | 162 | /** 163 | This function is overridden by subclasses to write class specific variables to the `uniformBuffer`. 164 | 165 | - parameter commandEncoder: The `MTLComputeCommandEncoder` used to run the kernel. This can be used to lazily create a buffer of data and add it to the argument table. Any buffer index can be used without affecting the rest of this class. 166 | */ 167 | open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder){ 168 | } 169 | 170 | 171 | 172 | /** 173 | Override this method in subclasses to configure a `Metal Performance Shader` to be used instead of a custom kernel. 174 | 175 | - parameter commandBuffer: The `MTLCommandBuffer` used to run the `Metal Performance Shader`. 176 | */ 177 | open func addMetalPerformanceShaderToBuffer(_ commandBuffer: MTLCommandBuffer){ 178 | } 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | // MARK:- Texture Functions 195 | 196 | 197 | /** 198 | Updates the output `MTLTexture`. 199 | 200 | This should not need to be called manually as it is called by the `texture()` method automatically if the texture does not represent the current properties. 201 | */ 202 | open func updateTexture(){ 203 | 204 | if provider?.texture() == nil {return} 205 | 206 | if internalTexture == nil{ 207 | newInternalTexture() 208 | } 209 | if internalTexture!.width != width || internalTexture!.height != height{ 210 | newInternalTexture() 211 | } 212 | 213 | let threadGroupsCount = MTLSizeMake(8, 8, 1) 214 | let threadGroups = MTLSizeMake(width / threadGroupsCount.width, height / threadGroupsCount.height, 1) 215 | 216 | let commandBuffer = context.commandQueue.makeCommandBuffer() 217 | 218 | // If an MPS is being used, encode it to the command buffer, else create a command encoder for a custom kernel 219 | if usesMPS{ 220 | addMetalPerformanceShaderToBuffer(commandBuffer) 221 | }else{ 222 | let commandEncoder = commandBuffer.makeComputeCommandEncoder() 223 | commandEncoder.setComputePipelineState(pipeline) 224 | commandEncoder.setTexture(provider!.texture(), at: 0) 225 | commandEncoder.setTexture(internalTexture, at: 1) 226 | configureArgumentTableWithCommandencoder(commandEncoder) 227 | commandEncoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupsCount) 228 | commandEncoder.endEncoding() 229 | } 230 | 231 | commandBuffer.commit() 232 | commandBuffer.waitUntilCompleted() 233 | dirty = false 234 | } 235 | 236 | 237 | 238 | ///Create a new `internalTexture` for the first time or whenever the texture is resized. 239 | func newInternalTexture(){ 240 | let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: width, height: height, mipmapped: false) 241 | internalTexture = context.device.makeTexture(descriptor: textureDescriptor) 242 | } 243 | 244 | 245 | 246 | ///- returns: The updated output `MTLTexture` for this module. 247 | open func texture() -> MTLTexture?{ 248 | if isDirty(){ 249 | updateTexture() 250 | } 251 | return internalTexture 252 | } 253 | 254 | 255 | 256 | ///- returns: The MTLSize of the the output `MTLTexture`. If no size has been explicitly set, the default value returned is `128`x`128` pixels. 257 | open func textureSize() -> MTLSize{ 258 | return MTLSizeMake(width, height, 1) 259 | } 260 | 261 | 262 | 263 | ///- returns: The input `AHNTextureProvider` that provides the input `MTLTexture` to the `AHNModifier`. This is taken from the `input`. If there is no `input`, returns `nil`. 264 | open func textureProvider() -> AHNTextureProvider?{ 265 | return provider 266 | } 267 | 268 | 269 | 270 | ///- returns: `False` if the input and the `internalTexture` do not need updating. 271 | open func isDirty() -> Bool { 272 | if let p = provider{ 273 | return p.isDirty() || dirty 274 | }else{ 275 | return dirty 276 | } 277 | } 278 | 279 | 280 | 281 | ///- returns: `False` if the required texture input is `nil`. 282 | open func canUpdate() -> Bool { 283 | return provider != nil 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierAbsolute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierAbsolute.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 24/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | import Metal 10 | import simd 11 | 12 | 13 | /** 14 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and performs a mathematical `abs()` function on the pixel values. 15 | 16 | Pixel values are in the range `0.0 - 1.0`, and apply the `abs()` function to this range would have no effect. This means the pixel valeus must be converted back to the original `-1.0 - 1.0` noise range, then perform the `abs()` function, then finally convert back into the colour range `0.0 - 1.0`. This results in outputs in the range `0.5 - 1.0`. 17 | 18 | If the `normalise` property is `true` (`false` by default) then the output values will be remapped to `0.0 - 1.0`, essentially stretching the to fit the original range. 19 | 20 | *Conforms to the `AHNTextureProvider` protocol.* 21 | */ 22 | open class AHNModifierAbsolute: AHNModifier { 23 | 24 | 25 | // MARK:- Properties 26 | 27 | 28 | ///If `false` (the default), the output is within the range `0.5 - 1.0`, if `true` the output is remapped to cover the whole `0.0 - 1.0` range of the input. 29 | open var normalise: Bool = false{ 30 | didSet{ 31 | dirty = true 32 | } 33 | } 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | // MARK:- Initialiser 44 | 45 | 46 | required public init(){ 47 | super.init(functionName: "absoluteModifier") 48 | } 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | // MARK:- Argument table update 59 | 60 | 61 | ///Encodes the required uniform values for this `AHNModifier` subclass. This should never be called directly. 62 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 63 | var uniforms = normalise 64 | 65 | if uniformBuffer == nil{ 66 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: MTLResourceOptions()) 67 | } 68 | 69 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 70 | 71 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierBlur.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierBlur.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 19/05/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | import Metal 10 | import simd 11 | import MetalPerformanceShaders 12 | 13 | 14 | /** 15 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and blurs its texture. 16 | 17 | The input undergoes a Gaussian blur with a specified `radius`. Note that this is computationally expensive for larger radii. 18 | 19 | *Conforms to the `AHNTextureProvider` protocol.* 20 | */ 21 | open class AHNModifierBlur: AHNModifier { 22 | 23 | 24 | // MARK:- Properties 25 | 26 | 27 | ///The radius of the Gaussian blur. The default value is `3.0`. 28 | open var radius: Float = 3{ 29 | didSet{ 30 | dirty = true 31 | } 32 | } 33 | 34 | 35 | 36 | ///The `Metal Performance Shader` used to perform the blur. 37 | var kernel: MPSImageGaussianBlur! 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | // MARK:- Initialiser 51 | 52 | 53 | required public init(){ 54 | super.init(functionName: "normalMapModifier") 55 | usesMPS = true 56 | kernel = MPSImageGaussianBlur(device: context.device, sigma: radius) 57 | kernel.edgeMode = .clamp 58 | } 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | // MARK:- Argument table update 69 | 70 | 71 | ///Encodes the `Metal Performance Shader` into the command buffer. This is called by the superclass and should not be called manually. 72 | override open func addMetalPerformanceShaderToBuffer(_ commandBuffer: MTLCommandBuffer) { 73 | guard let texture = provider?.texture() else { return } 74 | kernel = MPSImageGaussianBlur(device: context.device, sigma: radius) 75 | kernel.edgeMode = .clamp 76 | kernel.encode(commandBuffer: commandBuffer, sourceTexture: texture, destinationTexture: internalTexture!) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierClamp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierClamp.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 24/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | ///The struct used to encode user defined properties (uniforms) to the GPU. 15 | struct ClampModifierUniforms { 16 | var normalise: Bool 17 | var clampValues: vector_float2 18 | } 19 | 20 | 21 | /** 22 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and performs a `clamp()` function on the pixel values. 23 | 24 | The output of the `AHNGenerator` classes returns value in the range `0.0 - 1.0`, this module will perform a clamp function on the input, reverting any values over a specified maximum value to that maximum value, and the same for any values less than a specified minimum value. If the `normalise` property is true (false by default) then the output values will be remapped to `0.0 - 1.0`, essentially stretching the to fit the original range. 25 | 26 | For example if a pixel has a value of `0.9` and the `maximum` property is set to `0.75`, the returned value will be `0.75`. The same applies for a value less than the minimum value. 27 | 28 | *Conforms to the `AHNTextureProvider` protocol.* 29 | */ 30 | open class AHNModifierClamp: AHNModifier { 31 | 32 | 33 | // MARK:- Properties 34 | 35 | 36 | ///If `false` (default), the output is within the range `minimum - maximum, if `true` the output is remapped to cover the whole `0.0 - 1.0` range of the input. 37 | open var normalise: Bool = false{ 38 | didSet{ 39 | dirty = true 40 | } 41 | } 42 | 43 | 44 | 45 | ///The maximum value of the range to clamp to. Values larger than this will be written to the output as this value. The default value is `1.0`. 46 | open var minimum: Float = 0{ 47 | didSet{ 48 | dirty = true 49 | } 50 | } 51 | 52 | 53 | 54 | ///The minimum value of the range to clamp to. Values smaller than this will be written to the output as this value. The default value is `0.0`. 55 | open var maximum: Float = 1{ 56 | didSet{ 57 | dirty = true 58 | } 59 | } 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | // MARK:- Initialiser 74 | 75 | 76 | required public init(){ 77 | super.init(functionName: "clampModifier") 78 | } 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | // MARK:- Argument table update 93 | 94 | 95 | ///Encodes the required uniform values for this `AHNModifier` subclass. This should never be called directly. 96 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 97 | var uniforms = ClampModifierUniforms(normalise: normalise, clampValues: vector_float2(minimum, maximum)) 98 | 99 | if uniformBuffer == nil{ 100 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: MTLResourceOptions()) 101 | } 102 | 103 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 104 | 105 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierColour.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierColour.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 26/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | import Metal 12 | import simd 13 | 14 | 15 | /** 16 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and colourises it. 17 | 18 | Applies a colouring to a specific range of values in a texture. Each `colour` has a position and an intensity dictating which values in the input texture are colourised and by how much. Colours and intensities are interpolated between positions. 19 | 20 | *Conforms to the `AHNTextureProvider` protocol.* 21 | */ 22 | open class AHNModifierColour: AHNModifier { 23 | 24 | 25 | // MARK:- Properties 26 | 27 | 28 | ///A buffer to contain colour positions. 29 | var positionBuffer: MTLBuffer? 30 | 31 | 32 | 33 | ///A buffer to contain the number of colours to use. 34 | var countBuffer: MTLBuffer? 35 | 36 | 37 | 38 | ///A buffer to contain the intensities of the colour application. 39 | var intensityBuffer: MTLBuffer? 40 | 41 | 42 | 43 | ///A boolean to detect whether or not default colours are being used to avoid a crash 44 | var defaultsUsed: Bool = false 45 | 46 | 47 | 48 | ///The number of colours in use 49 | var colourCount: Int32{ 50 | get{ 51 | return Int32(_colours.count) 52 | } 53 | } 54 | 55 | 56 | 57 | ///The colour to apply to the input. Do not set directly, use the `colours` property. 58 | fileprivate var _colours: [UIColor] = []{ 59 | didSet{ 60 | dirty = true 61 | } 62 | } 63 | 64 | 65 | 66 | ///The central position of the colouring range. Pixels with this value in the texture will output the `colour` due to a mix value of 1.0. Do not set directly, use the `colours` property. Default value is 0.5. 67 | fileprivate var _positions: [Float] = []{ 68 | didSet{ 69 | dirty = true 70 | } 71 | } 72 | 73 | 74 | 75 | ///The intensities with which to apply the colours to in input. Do not set directly, use the `colours` property. 76 | fileprivate var _intensities: [Float] = []{ 77 | didSet{ 78 | dirty = true 79 | } 80 | } 81 | 82 | 83 | 84 | ///The colour to apply to the input, with associated positions and intensities. 85 | open var colours: [(colour: UIColor, position: Float, intensity: Float)]{ 86 | get{ 87 | var tuples: [(colour: UIColor, position: Float, intensity: Float)] = [] 88 | for (i, colour) in _colours.enumerated(){ 89 | let tuple = (colour, _positions[i], _intensities[i]) 90 | tuples.append(tuple) 91 | } 92 | return tuples 93 | } 94 | set{ 95 | var newValue = newValue 96 | defaultsUsed = false 97 | if newValue.count == 0{ 98 | newValue = [(UIColor.white, 0.5, 0.0)] 99 | defaultsUsed = true 100 | } 101 | var colours: [UIColor] = [] 102 | var positions: [Float] = [] 103 | var intensities: [Float] = [] 104 | for tuple in newValue{ 105 | colours.append(tuple.colour) 106 | positions.append(tuple.position) 107 | intensities.append(tuple.intensity) 108 | } 109 | _colours = colours 110 | _positions = positions 111 | _intensities = intensities 112 | dirty = true 113 | organiseColoursInOrder() 114 | } 115 | } 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | // MARK:- Initialiser 135 | 136 | 137 | required public init(){ 138 | super.init(functionName: "colourModifier") 139 | colours = [] 140 | } 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | // MARK:- Argument table update 156 | 157 | 158 | ///Encodes the required uniform values for this `AHNModifier` subclass. This should never be called directly. 159 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 160 | assert(_positions.count == _colours.count && _colours.count == _intensities.count, "AHNoise: ERROR - Number of colours to use must match the number of positions and intensities.") 161 | 162 | var red: CGFloat = 0 163 | var green: CGFloat = 0 164 | var blue: CGFloat = 0 165 | var alpha: CGFloat = 0 166 | 167 | // Convert UIColours to vector_float4 168 | var uniformsColours: [vector_float4] = [] 169 | for colour in _colours{ 170 | colour.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 171 | uniformsColours.append(vector_float4(Float(red), Float(green), Float(blue), Float(alpha))) 172 | } 173 | 174 | // Create colour buffer and copy data 175 | var bufferSize = MemoryLayout.stride * _colours.count 176 | if uniformBuffer == nil || uniformBuffer?.length != bufferSize{ 177 | uniformBuffer = context.device.makeBuffer(length: bufferSize, options: MTLResourceOptions()) 178 | } 179 | memcpy(uniformBuffer!.contents(), &uniformsColours, bufferSize) 180 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 181 | 182 | // Create positions buffer and copy data 183 | bufferSize = MemoryLayout.stride * _positions.count 184 | if positionBuffer == nil || positionBuffer?.length != bufferSize{ 185 | positionBuffer = context.device.makeBuffer(length: bufferSize, options: MTLResourceOptions()) 186 | } 187 | memcpy(positionBuffer!.contents(), &_positions, bufferSize) 188 | commandEncoder.setBuffer(positionBuffer, offset: 0, at: 1) 189 | 190 | // Create intensities buffer and copy data 191 | bufferSize = MemoryLayout.stride * _intensities.count 192 | if intensityBuffer == nil || intensityBuffer?.length != bufferSize{ 193 | intensityBuffer = context.device.makeBuffer(length: bufferSize, options: MTLResourceOptions()) 194 | } 195 | memcpy(intensityBuffer!.contents(), &_intensities, bufferSize) 196 | commandEncoder.setBuffer(intensityBuffer, offset: 0, at: 2) 197 | 198 | // Create the colour count buffer and copy data 199 | bufferSize = MemoryLayout.stride 200 | if countBuffer == nil{ 201 | countBuffer = context.device.makeBuffer(length: bufferSize, options: MTLResourceOptions()) 202 | } 203 | var count = colourCount 204 | memcpy(countBuffer!.contents(), &count, bufferSize) 205 | commandEncoder.setBuffer(countBuffer, offset: 0, at: 3) 206 | } 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | // MARK:- Colour Handling 216 | 217 | 218 | ///Organise the colours, positions and intensities arrays. 219 | fileprivate func organiseColoursInOrder(){ 220 | assert(_positions.count == _colours.count && _colours.count == _intensities.count, "AHNoise: ERROR - Number of colours to use must match the number of positions and intensities.") 221 | var tuples: [(colour: UIColor, position: Float, intensity: Float)] = [] 222 | 223 | for (i, colour) in _colours.enumerated(){ 224 | let tuple = (colour, _positions[i], _intensities[i]) 225 | tuples.append(tuple) 226 | } 227 | 228 | tuples = tuples.sorted(by: { $0.position < $1.position }) 229 | 230 | var sortedColours: [UIColor] = [] 231 | var sortedPositions: [Float] = [] 232 | var sortedIntensities: [Float] = [] 233 | for tuple in tuples{ 234 | sortedColours.append(tuple.colour) 235 | sortedPositions.append(tuple.position) 236 | sortedIntensities.append(tuple.intensity) 237 | } 238 | 239 | _colours = sortedColours 240 | _positions = sortedPositions 241 | _intensities = sortedIntensities 242 | } 243 | 244 | 245 | ///Add a new colour, with corresponding position and intensity. 246 | open func addColour(_ colour: UIColor, position: Float, intensity: Float){ 247 | if defaultsUsed{ 248 | colours = [(colour, position, intensity)] 249 | }else{ 250 | colours.append((colour, position, intensity)) 251 | } 252 | } 253 | 254 | 255 | 256 | ///Remove all colours. 257 | open func removeAllColours(){ 258 | colours = [] 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierInvert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierInvert.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 25/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | import Metal 10 | import simd 11 | 12 | 13 | /** 14 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and inverts the values. 15 | 16 | For example if a pixel has a value of `0.6`, the output will be `0.4`. The values are flipped around `0.5`. 17 | 18 | *Conforms to the `AHNTextureProvider` protocol.* 19 | */ 20 | open class AHNModifierInvert: AHNModifier { 21 | 22 | 23 | // MARK:- Initialiser 24 | 25 | 26 | required public init(){ 27 | super.init(functionName: "invertModifier") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierLoop.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierLoop.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 26/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | ///The struct used to encode user defined properties (uniforms) to the GPU. 15 | struct LoopModifierUniforms { 16 | var normalise: Bool 17 | var loopValue: Float 18 | } 19 | 20 | 21 | /** 22 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and loops or wraps pixel values to never exceed a specified `loopValue` property. 23 | 24 | Values above the `boundary` are replaced with remainder from the result of dividing the value by the `boundary`. 25 | 26 | *Conforms to the `AHNTextureProvider` protocol.* 27 | */ 28 | open class AHNModifierLoop: AHNModifier { 29 | 30 | 31 | // MARK:- Properties 32 | 33 | 34 | ///The value to loop at. No texture value will exceed this value (unless `normalise` is set to `true`). The default value is `0.5`. 35 | open var boundary: Float = 0.5{ 36 | didSet{ 37 | dirty = true 38 | } 39 | } 40 | 41 | 42 | 43 | ///If `false`, the output is within the range `0.0 - loopValue`, if `true` the output is remapped to cover the whole `0.0 - 1.0` range. The default value is `false`. 44 | open var normalise: Bool = false{ 45 | didSet{ 46 | dirty = true 47 | } 48 | } 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | // MARK:- Initialiser 63 | 64 | 65 | required public init(){ 66 | super.init(functionName: "loopModifier") 67 | } 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | // MARK:- Argument table update 77 | 78 | 79 | ///Encodes the required uniform values for this `AHNModifier` subclass. This should never be called directly. 80 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 81 | var uniforms = LoopModifierUniforms(normalise: normalise, loopValue: boundary) 82 | 83 | if uniformBuffer == nil{ 84 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: MTLResourceOptions()) 85 | } 86 | 87 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 88 | 89 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierMapNormal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierNormalMap.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 26/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import SpriteKit 12 | import MetalKit 13 | 14 | 15 | /** 16 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and creates a normal map for it. 17 | 18 | The input pixels are analysed to detect colour variations that are interpreted as gradients, these gradients are then converted into a normal map for use in 3D lighting. 19 | 20 | *Conforms to the `AHNTextureProvider` protocol.* 21 | */ 22 | open class AHNModifierMapNormal: NSObject, AHNTextureProvider { 23 | 24 | 25 | // MARK:- Properties 26 | 27 | 28 | ///A value that magnifies the effect of the generated normal map. The default value of `1.0` indicates no magnification. 29 | open var intensity: Float = 1.0{ 30 | didSet{ 31 | dirty = true 32 | } 33 | } 34 | 35 | 36 | 37 | ///A value in teh range `0.0 - 1.0` indicating how much the input should be smoothed before the normal map is generated. The default value is `0.0`. 38 | open var smoothing: Float = 0{ 39 | didSet{ 40 | dirty = true 41 | } 42 | } 43 | 44 | 45 | 46 | ///The `AHNContext` that is being used by the `AHNTextureProvider` to communicate with the GPU. This is recovered from the first `AHNGenerator` class that is encountered in the chain of classes. 47 | open var context: AHNContext 48 | 49 | 50 | 51 | ///The `MTLTexture` that the compute kernel writes to as an output. 52 | var internalTexture: MTLTexture? 53 | 54 | 55 | 56 | ///Indicates whether or not the `internalTexture` needs updating. 57 | open var dirty: Bool = true 58 | 59 | 60 | 61 | ///The input that will be used to generator the normal map. 62 | var provider: AHNTextureProvider? 63 | 64 | 65 | 66 | /** 67 | The width of the output `MTLTexure`. 68 | 69 | This is dictated by the width of the texture of the input `AHNTextureProvider`. If there is no input, the default width is `128` pixels. 70 | */ 71 | open var textureWidth: Int{ 72 | get{ 73 | return provider?.textureSize().width ?? 128 74 | } 75 | } 76 | 77 | 78 | 79 | /** 80 | The height of the output `MTLTexure`. 81 | 82 | This is dictated by the height of the texture of the input `AHNTextureProvider`. If there is no input, the default height is `128` pixels. 83 | */ 84 | open var textureHeight: Int{ 85 | get{ 86 | return provider?.textureSize().height ?? 128 87 | } 88 | } 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | // MARK:- Initialiser 100 | 101 | 102 | override public required init(){ 103 | context = AHNContext.SharedContext 104 | super.init() 105 | } 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | // MARK:- Texture Functions 122 | 123 | 124 | /** 125 | Updates the output `MTLTexture`. 126 | 127 | This should not need to be called manually as it is called by the `texture()` method automatically if the texture does not represent the current `AHNTextureProvider` properties. 128 | */ 129 | open func updateTexture(){ 130 | if provider == nil {return} 131 | 132 | if internalTexture == nil{ 133 | newInternalTexture() 134 | } 135 | if internalTexture!.width != textureWidth || internalTexture!.height != textureHeight{ 136 | newInternalTexture() 137 | } 138 | 139 | 140 | guard var image = provider?.uiImage() else { return } 141 | guard var ciImage = CIImage(image: image) else { return } 142 | ciImage = ciImage.applying(CGAffineTransform(scaleX: 1, y: -1)) 143 | let ciContext = CIContext(options: nil) 144 | image = UIImage(cgImage: ciContext.createCGImage(ciImage, from: ciImage.extent)!) 145 | 146 | let sprite = SKTexture(image: image) 147 | 148 | let normal = sprite.generatingNormalMap(withSmoothness: CGFloat(smoothing), contrast: CGFloat(intensity)) 149 | let loader = MTKTextureLoader(device: context.device) 150 | do{ 151 | try internalTexture = loader.newTexture(with: normal.cgImage(), options: nil) 152 | }catch{ 153 | fatalError() 154 | } 155 | dirty = false 156 | } 157 | 158 | 159 | 160 | ///Create a new `internalTexture` for the first time or whenever the texture is resized. 161 | func newInternalTexture(){ 162 | let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: textureWidth, height: textureHeight, mipmapped: false) 163 | internalTexture = context.device.makeTexture(descriptor: textureDescriptor) 164 | } 165 | 166 | 167 | 168 | ///- returns: The updated output `MTLTexture` for this module. 169 | open func texture() -> MTLTexture?{ 170 | if isDirty(){ 171 | updateTexture() 172 | } 173 | return internalTexture 174 | } 175 | 176 | 177 | 178 | ///- returns: The MTLSize of the the output `MTLTexture`. If no size has been explicitly set, the default value returned is `128x128` pixels. 179 | open func textureSize() -> MTLSize{ 180 | return MTLSizeMake(textureWidth, textureHeight, 1) 181 | } 182 | 183 | 184 | 185 | ///- returns: The input `AHNTextureProvider` that provides the input `MTLTexture` to the `AHNModifier`. This is taken from the `input`. If there is no `input`, returns `nil`. 186 | open func textureProvider() -> AHNTextureProvider?{ 187 | return provider 188 | } 189 | 190 | 191 | 192 | ///- returns: `False` if the input and the `internalTexture` do not need updating. 193 | open func isDirty() -> Bool { 194 | if let p = provider{ 195 | return p.isDirty() || dirty 196 | }else{ 197 | return dirty 198 | } 199 | } 200 | 201 | 202 | 203 | ///- returns: `False` if the `provider` property is not set. 204 | open func canUpdate() -> Bool { 205 | return provider != nil 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierPerspective.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierPerspective.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 29/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | /** 14 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and applies a perspective transform. 15 | 16 | The `xCompression` property determines how much the upper portion of the input is compressed horizontally to give the impression of stretching into the distance. Values over `3.3` will result in the texture wrapping. A value of `2 - 2.5` is a good place to start. 17 | 18 | The `yScale` property determines how much the input is scaled in the vertical axis to give an impression of looking at the canvas at a shallow angle. This can range from `0.0 - 1.0`. at `0.0` the canvas has zero height, at `1.0` it retains its original height. 19 | 20 | The `direction` property allows the direction of the perspective to be skewed left (using negative values) or right (using positive values) to give the impression a horizontal receding angle. 21 | 22 | Values are interpolated to avoid pixellation. 23 | 24 | *Conforms to the `AHNTextureProvider` protocol.* 25 | */ 26 | open class AHNModifierPerspective: AHNModifier { 27 | 28 | 29 | // MARK:- Properties 30 | 31 | 32 | ///The amount to compress the texture horizontally to give the impression of stretching into the distance. Values over `3.3` will result in the texture wrapping. A value of `2 - 2.5` is a good place to start. The default value is `2`. 33 | open var xCompression: Float = 2{ 34 | didSet{ 35 | dirty = true 36 | } 37 | } 38 | 39 | 40 | 41 | ///The amount to scale the texture vertically to give an impression of looking at the canvas at a shallow angle. This can range from `0.0 - 1.0`. at `0.0` the canvas has zero height, at `1.0` it retains its original height. The default value is `0.5`. 42 | open var yScale: Float = 0.5{ 43 | didSet{ 44 | dirty = true 45 | } 46 | } 47 | 48 | 49 | 50 | ///Allows the direction of the perspective to be skewed left (using negative values) or right (using positive values) to give the impression a horizontal receding angle. The default value is `0.0.` 51 | open var direction: Float = 0{ 52 | didSet{ 53 | dirty = true 54 | } 55 | } 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | // MARK:- Initialiser 73 | 74 | 75 | required public init(){ 76 | super.init(functionName: "perspectiveModifier") 77 | } 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | // MARK:- Argument table update 94 | 95 | 96 | ///Encodes the required uniform values for this `AHNModifier` subclass. This should never be called directly. 97 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 98 | var uniforms = vector_float3(xCompression, yScale, direction) 99 | 100 | if uniformBuffer == nil{ 101 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: MTLResourceOptions()) 102 | } 103 | 104 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 105 | 106 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierRotate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierRotate.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 29/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and rotates its output. 16 | 17 | The `angle` property defines how much to rotate the input in radians. 18 | 19 | The result will be clipped to fit within the same frame as the input, the size of the canvas does not change. Corners may be clipped because of this, to avoid losing the corners, resize the canvas first by using an `AHNModifierScaleCanvas` object to provide more room for rotation. 20 | 21 | Values are interpolated to avoid pixellation. 22 | 23 | The centre point about which the rotation takes place can be defined by the `xAnchor` and `yAnchor` properties. These can vary from `(0.0,0.0)` for the bottom left to `(1.0,1.0)` for the top right. The default is `(0.5,0.5)`. 24 | 25 | Where the rotation results in the canvas being partially empty, this can be either left blank by setting `cutEdges` to `true`, or filled in black if set to `false`. 26 | 27 | *Conforms to the `AHNTextureProvider` protocol.* 28 | */ 29 | open class AHNModifierRotate: AHNModifier { 30 | 31 | 32 | // MARK:- Properties 33 | 34 | 35 | ///The anchor point for horizontal axis about which to rotate the input. The default value is `0.5`. 36 | open var xAnchor: Float = 0.5{ 37 | didSet{ 38 | dirty = true 39 | } 40 | } 41 | 42 | 43 | 44 | ///The anchor point for vertical axis about which to rotate the input. The default value is `0.5`. 45 | open var yAnchor: Float = 0.5{ 46 | didSet{ 47 | dirty = true 48 | } 49 | } 50 | 51 | 52 | 53 | ///The angle to rotate the input by in radians. The default value is `0.0`. 54 | open var angle: Float = 0.0{ 55 | didSet{ 56 | dirty = true 57 | } 58 | } 59 | 60 | 61 | 62 | ///When true, the edges of the input are "cut" before the rotation, meaning the black areas off the the canvas are not rotated and any area not covered by the input after rotation is clear. If false, these areas are filled black. The default value is `true`. 63 | open var cutEdges: Bool = true{ 64 | didSet{ 65 | dirty = true 66 | } 67 | } 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | // MARK:- Initialiser 84 | 85 | 86 | required public init(){ 87 | super.init(functionName: "rotateModifier") 88 | } 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | // MARK:- Argument table update 106 | 107 | 108 | ///Encodes the required uniform values for this `AHNModifier` subclass. This should never be called directly. 109 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 110 | var uniforms = vector_float4(xAnchor, yAnchor, angle, cutEdges ? 1 : 0) 111 | 112 | if uniformBuffer == nil{ 113 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: MTLResourceOptions()) 114 | } 115 | 116 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 117 | 118 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierRound.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierRound.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 26/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and rounds pixel values to an integer multiple of the`roundValue` property. 16 | 17 | Where `i` is the input value, `o` is the output value and `r` is the value to round to, the function is: `o = r*(round(i/r))`. 18 | 19 | For example if a pixel has a value of `0.6` and the `roundValue` property is set to `0.5`, the returned value will be `0.5`. 20 | 21 | *Conforms to the `AHNTextureProvider` protocol.* 22 | */ 23 | open class AHNModifierRound: AHNModifier { 24 | 25 | 26 | // MARK:- Properties 27 | 28 | 29 | /** 30 | The value that the texture values will be rounded to multiples of. 31 | 32 | Default value is `1.0`, causing no effect. 33 | */ 34 | open var roundValue: Float = 1{ 35 | didSet{ 36 | dirty = true 37 | } 38 | } 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | // MARK:- Initialiser 52 | 53 | 54 | required public init(){ 55 | super.init(functionName: "roundModifier") 56 | } 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | // MARK:- Argument table update 73 | 74 | 75 | ///Encodes the required uniform values for this `AHNModifier` subclass. This should never be called directly. 76 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 77 | var uniforms = roundValue 78 | 79 | if uniformBuffer == nil{ 80 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: MTLResourceOptions()) 81 | } 82 | 83 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 84 | 85 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierScaleBias.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierScaleBias.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 25/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and applies a scale (multiplier) and a bias (constant). 16 | 17 | Where `o` is the output, `i` is the `input`, `s` is the `scale` and `b` is the `bias`: `o=(i*s)+b`. 18 | 19 | For example if a pixel has a value of `0.6`, with a `scale` of `0.5` and a `bias` of `0.6`, the output would be `(0.6*0.5)+0.6` which equals `0.9`. 20 | 21 | This can be used to shift the range of values an `AHNTextureProvider` has. 22 | 23 | *Conforms to the `AHNTextureProvider` protocol.* 24 | */ 25 | open class AHNModifierScaleBias: AHNModifier { 26 | 27 | 28 | // MARK:- Properties 29 | 30 | 31 | ///The multiplier to apply to the `input` value before the addition of `bias`. Default value is `1.0`. 32 | open var scale: Float = 1{ 33 | didSet{ 34 | dirty = true 35 | } 36 | } 37 | 38 | 39 | 40 | ///The constant to add to the `input` after it has been multiplied by `scale`. Can be negative. Default Value is `0.0`. 41 | open var bias: Float = 0{ 42 | didSet{ 43 | dirty = true 44 | } 45 | } 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | // MARK:- Initialiser 60 | 61 | 62 | required public init(){ 63 | super.init(functionName: "scaleBiasModifier") 64 | } 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | // MARK:- Argument table update 83 | 84 | 85 | ///Encodes the required uniform values for this `AHNModifier` subclass. This should never be called directly. 86 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 87 | var uniforms = vector_float2(scale, bias) 88 | 89 | if uniformBuffer == nil{ 90 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: MTLResourceOptions()) 91 | } 92 | 93 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 94 | 95 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierScaleCanvas.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierScaleCanvas.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 29/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | import Metal 12 | import simd 13 | 14 | 15 | ///The struct used to encode user defined properties (uniforms) to the GPU. 16 | struct AHNScaleCanvasProperties{ 17 | var scale: vector_float4 18 | var oldSize: vector_int4 19 | } 20 | 21 | 22 | /** 23 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and scales and repositions it in its texture. 24 | 25 | The `xScale` and `yScale` (`1.0, 1.0` by default) properties allow you to stretch or shrink a texture within the new canvas, and the `xAnchor` and `yAnchor` (`0.0,0.0` by default) properties allow you to move the bottom left hand corner of the input to reposition it within the new canvas. An anchor value of `0.0` leaves the input at the origin, whereas a value of `1.0` moves it to the other extreme of the canvas. 26 | 27 | *Conforms to the `AHNTextureProvider` protocol.* 28 | */ 29 | open class AHNModifierScaleCanvas: NSObject, AHNTextureProvider { 30 | 31 | 32 | //MARK:- Properties 33 | 34 | 35 | ///The `AHNContext` that is being used by the `AHNTextureProvider` to communicate with the GPU. This is recovered from the first `AHNGenerator` class that is encountered in the chain of classes. 36 | open var context: AHNContext 37 | 38 | 39 | 40 | ///The `MTLComputePipelineState` used to run the `Metal` compute kernel on the GPU. 41 | let pipeline: MTLComputePipelineState 42 | 43 | 44 | 45 | ///The `MTLBuffer` used to transfer the constant values used by the compute kernel to the GPU. 46 | open var uniformBuffer: MTLBuffer? 47 | 48 | 49 | 50 | ///The `MTLTexture` that the compute kernel writes to as an output. 51 | var internalTexture: MTLTexture? 52 | 53 | 54 | 55 | /** 56 | The `MTLFunction` compute kernel that modifies the input `MTLTexture`s and writes the output to the `internalTexture` property. 57 | 58 | The function used is specific to each class. 59 | */ 60 | let kernelFunction: MTLFunction 61 | 62 | 63 | 64 | ///Indicates whether or not the `internalTexture` needs updating. 65 | open var dirty: Bool = true 66 | 67 | 68 | 69 | ///The input that will be modified using to provide the output. 70 | open var provider: AHNTextureProvider?{ 71 | didSet{ 72 | dirty = true 73 | } 74 | } 75 | 76 | 77 | 78 | ///The width of the new `MTLTexture` 79 | open var textureWidth: Int = 128{ 80 | didSet{ 81 | dirty = true 82 | } 83 | } 84 | 85 | 86 | 87 | ///The height of the new `MTLTexture` 88 | open var textureHeight: Int = 128{ 89 | didSet{ 90 | dirty = true 91 | } 92 | } 93 | 94 | 95 | 96 | ///The position along the horizontal axis of the bottom left corner of the input in the new canvas. Ranges from `0.0` for far left to `1.0` for far right, though values beyond this can be used. Default value is `0.0`. 97 | open var xAnchor: Float = 0{ 98 | didSet{ 99 | dirty = true 100 | } 101 | } 102 | 103 | 104 | 105 | ///The position along the vertical axis of the bottom left corner of the input in the new canvas. Ranges from `0.0` for the bottom to `1.0` for the top, though values beyond this can be used. Default value is `0.0`. 106 | open var yAnchor: Float = 0{ 107 | didSet{ 108 | dirty = true 109 | } 110 | } 111 | 112 | 113 | 114 | ///The scale of the input when inserted into the canvas. If an input had a width of `256`, which is being resized to `512` with a scale of `0.5`, the width of the input would be `128` in the canvas of `512`. Default value is `1.0`. 115 | open var xScale: Float = 1{ 116 | didSet{ 117 | dirty = true 118 | } 119 | } 120 | 121 | 122 | 123 | ///The scale of the input when inserted into the canvas. If an input had a height of `256`, which is being resized to `512` with a scale of `0.5`, the height of the input would be `128` in the canvas of `512`. Default value is `1.0`. 124 | open var yScale: Float = 1{ 125 | didSet{ 126 | dirty = true 127 | } 128 | } 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | // MARK:- Initialiser 139 | 140 | 141 | override public required init(){ 142 | context = AHNContext.SharedContext 143 | let functionName = "scaleCanvasModifier" 144 | 145 | guard let kernelFunction = context.library.makeFunction(name: functionName) else{ 146 | fatalError("AHNoise: Error loading function \(functionName).") 147 | } 148 | self.kernelFunction = kernelFunction 149 | 150 | do{ 151 | try pipeline = context.device.makeComputePipelineState(function: kernelFunction) 152 | }catch{ 153 | fatalError("AHNoise: Error creating pipeline state for \(functionName).\n\(error)") 154 | } 155 | super.init() 156 | } 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | // MARK:- Argument table update 169 | 170 | 171 | ///Encodes the required uniform values for this `AHNModifier`. This should never be called directly. 172 | open func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 173 | var uniforms = AHNScaleCanvasProperties(scale: vector_float4(xAnchor, yAnchor, xScale, yScale), oldSize: vector_int4(Int32(provider!.textureSize().width), Int32(provider!.textureSize().height),0,0)) 174 | 175 | if uniformBuffer == nil{ 176 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: MTLResourceOptions()) 177 | } 178 | 179 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 180 | 181 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 182 | } 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | // MARK:- Texture Functions 197 | 198 | 199 | /** 200 | Updates the output `MTLTexture`. 201 | 202 | This should not need to be called manually as it is called by the `texture()` method automatically if the texture does not represent the current `AHNTextureProvider` properties. 203 | */ 204 | open func updateTexture(){ 205 | if provider == nil {return} 206 | 207 | if internalTexture == nil{ 208 | newInternalTexture() 209 | } 210 | if internalTexture!.width != textureWidth || internalTexture!.height != textureHeight{ 211 | newInternalTexture() 212 | } 213 | 214 | let threadGroupsCount = MTLSizeMake(8, 8, 1) 215 | let threadGroups = MTLSizeMake(textureWidth / threadGroupsCount.width, textureHeight / threadGroupsCount.height, 1) 216 | 217 | let commandBuffer = context.commandQueue.makeCommandBuffer() 218 | 219 | let commandEncoder = commandBuffer.makeComputeCommandEncoder() 220 | commandEncoder.setComputePipelineState(pipeline) 221 | commandEncoder.setTexture(provider!.texture(), at: 0) 222 | commandEncoder.setTexture(internalTexture, at: 1) 223 | configureArgumentTableWithCommandencoder(commandEncoder) 224 | commandEncoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupsCount) 225 | commandEncoder.endEncoding() 226 | 227 | commandBuffer.commit() 228 | commandBuffer.waitUntilCompleted() 229 | dirty = false 230 | } 231 | 232 | 233 | 234 | ///Create a new `internalTexture` for the first time or whenever the texture is resized. 235 | func newInternalTexture(){ 236 | let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: textureWidth, height: textureHeight, mipmapped: false) 237 | internalTexture = context.device.makeTexture(descriptor: textureDescriptor) 238 | } 239 | 240 | 241 | 242 | ///- returns: The updated output `MTLTexture` for this module. 243 | open func texture() -> MTLTexture?{ 244 | if isDirty(){ 245 | updateTexture() 246 | } 247 | return internalTexture 248 | } 249 | 250 | 251 | 252 | ///- returns: The MTLSize of the the output `MTLTexture`. If no size has been explicitly set, the default value returned is `128x128` pixels. 253 | open func textureSize() -> MTLSize{ 254 | return MTLSizeMake(textureWidth, textureHeight, 1) 255 | } 256 | 257 | 258 | 259 | ///- returns: The input `AHNTextureProvider` that provides the input `MTLTexture` to the `AHNModifier`. This is taken from the `input`. If there is no `input`, returns `nil`. 260 | open func textureProvider() -> AHNTextureProvider?{ 261 | return provider 262 | } 263 | 264 | 265 | 266 | ///- returns: `False` if the input and the `internalTexture` do not need updating. 267 | open func isDirty() -> Bool { 268 | if let p = provider{ 269 | return p.isDirty() || dirty 270 | }else{ 271 | return dirty 272 | } 273 | } 274 | 275 | 276 | 277 | ///-returns: `False` if the `provider` is not set. 278 | open func canUpdate() -> Bool { 279 | return provider != nil 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierStep.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierStep.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 24/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and maps values larger than the `boundary` value to the `highValue`, and those below to the `lowValue`. 16 | 17 | For example if a pixel has a value of `0.6`, the `boundary` is set to `0.5`, the `highValue` set to `0.7` and the `lowValue` set to `0.1`, the returned value will be `0.1`. 18 | 19 | The output of this module will always be greyscale as the output value is written to all three colour channels equally. 20 | 21 | *Conforms to the `AHNTextureProvider` protocol.* 22 | */ 23 | open class AHNModifierStep: AHNModifier{ 24 | 25 | 26 | // MARK:- Properties 27 | 28 | 29 | ///The low value (default value is `0.0`) to output if the noise value is lower than the `boundary`. 30 | open var lowValue: Float = 0{ 31 | didSet{ 32 | dirty = true 33 | } 34 | } 35 | 36 | 37 | 38 | ///The hight value (default value is `1.0`) to output if the noise value is higher than the `boundary`. 39 | open var highValue: Float = 1{ 40 | didSet{ 41 | dirty = true 42 | } 43 | } 44 | 45 | 46 | 47 | ///The value at which to perform the step. Texture values lower than this are returned as `lowValue` and those above are returned as `highValue`. The default value is `0.5`. 48 | open var boundary: Float = 0.5{ 49 | didSet{ 50 | dirty = true 51 | } 52 | } 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | // MARK:- Initialiser 65 | 66 | 67 | required public init(){ 68 | super.init(functionName: "stepModifier") 69 | } 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | // MARK:- Argument table update 88 | 89 | 90 | ///Encodes the required uniform values for this `AHNModifier` subclass. This should never be called directly. 91 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 92 | var uniforms = vector_float3(lowValue, highValue, boundary) 93 | 94 | if uniformBuffer == nil{ 95 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: MTLResourceOptions()) 96 | } 97 | 98 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 99 | 100 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierStretch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierStretch.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 26/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and stretches its output. 16 | 17 | The `xFactor` and `yFactor` properties define how much to stretch the input in each direction. A factor of `1.0` will result in no change in that axis, but a factor of `2.0` will result in the dimension of that axis being doubled. Factors less than `1.0` can be used to shrink a canvas. The default is (`1.0,1.0`) 18 | 19 | The result will be clipped to fit within the same frame as the input, the size of the canvas does not change. 20 | 21 | Values are interpolated to avoid pixellation. 22 | 23 | The centre point about which the stretch takes place can be defined by the `xAnchor` and `yAnchor` properties. These can vary from `(0.0,0.0)` for the bottom left to `(1.0,1.0)` for the top right. The default is `(0.5,0.5)` 24 | 25 | *Conforms to the `AHNTextureProvider` protocol.* 26 | */ 27 | open class AHNModifierStretch: AHNModifier { 28 | 29 | 30 | // MARK:- Properties 31 | 32 | 33 | ///The factor to stretch the input by in the horizontal axis. Default value is `1.0`. 34 | open var xFactor: Float = 1{ 35 | didSet{ 36 | dirty = true 37 | } 38 | } 39 | 40 | 41 | 42 | ///The factor to stretch the input by in the vertical axis. Default value is `1.0`. 43 | open var yFactor: Float = 1{ 44 | didSet{ 45 | dirty = true 46 | } 47 | } 48 | 49 | 50 | 51 | ///The anchor point for horizontal axis about which to stretch the input. Default is `0.5`. 52 | open var xAnchor: Float = 0.5{ 53 | didSet{ 54 | dirty = true 55 | } 56 | } 57 | 58 | 59 | 60 | ///The anchor point for vertical axis about which to stretch the input. Default is `0.5`. 61 | open var yAnchor: Float = 0.5{ 62 | didSet{ 63 | dirty = true 64 | } 65 | } 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | // MARK:- Initialiser 81 | 82 | 83 | required public init(){ 84 | super.init(functionName: "stretchModifier") 85 | } 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | // MARK:- Argument table update 101 | 102 | 103 | ///Encodes the required uniform values for this `AHNModifier` subclass. This should never be called directly. 104 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 105 | var uniforms = vector_float4(xFactor, yFactor, xAnchor, yAnchor) 106 | 107 | if uniformBuffer == nil{ 108 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: MTLResourceOptions()) 109 | } 110 | 111 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 112 | 113 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNModifierSwirl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNModifierSwirl.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 29/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Takes the outputs of any class that adheres to the `AHNTextureProvider` protocol and swirls its output. 16 | 17 | The `angle` property defines how much to swirl the input in radians. The amount each pixel is rotated about the anchor is proportional to its distance from the anchor point. 18 | 19 | The result will be clipped to fit within the same frame as the input, the size of the canvas does not change. Corners may be clipped because of this, to avoid losing the corners, resize the canvas first by using an `AHNModifierScaleCanvas` object to provide more room for rotation. 20 | 21 | Values are interpolated to avoid pixellation. 22 | 23 | The centre point about which the swirl takes place can be defined by the `xAnchor` and `yAnchor` properties. These can vary from `(0.0,0.0)` for the bottom left to `(1.0,1.0)` for the top right. The default is (`0.5,0.5`). 24 | 25 | Where the rotation results in the canvas being partially empty, this can be either left blank by setting `cutEdges` to `true`, or filled in black if set to `false`. 26 | 27 | *Conforms to the `AHNTextureProvider` protocol.* 28 | */ 29 | open class AHNModifierSwirl: AHNModifier { 30 | 31 | 32 | // MARK:- Properties 33 | 34 | 35 | 36 | ///The anchor point for horizontal axis about which to swirl the input. Default is `0.5`. 37 | open var xAnchor: Float = 0.5{ 38 | didSet{ 39 | dirty = true 40 | } 41 | } 42 | 43 | 44 | 45 | ///The anchor point for vertical axis about which to swirl the input. Default is `0.5`. 46 | open var yAnchor: Float = 0.5{ 47 | didSet{ 48 | dirty = true 49 | } 50 | } 51 | 52 | 53 | 54 | ///The intensity of the swirl. Default is `0.5`. 55 | open var intensity: Float = 0.5{ 56 | didSet{ 57 | dirty = true 58 | } 59 | } 60 | 61 | 62 | 63 | ///When `true`, the edges of the input are "cut" before the swirl, meaning the black areas off the the canvas are not rotated and any area not covered by the input after rotation is clear. If `false`, these areas are filled black. 64 | open var cutEdges: Bool = true{ 65 | didSet{ 66 | dirty = true 67 | } 68 | } 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | // MARK:- Initialiser 85 | 86 | 87 | required public init(){ 88 | super.init(functionName: "swirlModifier") 89 | } 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | // MARK:- Argument table update 98 | 99 | 100 | ///Encodes the required uniform values for this `AHNModifier` subclass. This should never be called directly. 101 | open override func configureArgumentTableWithCommandencoder(_ commandEncoder: MTLComputeCommandEncoder) { 102 | var uniforms = vector_float4(xAnchor, yAnchor, intensity, cutEdges ? 1 : 0) 103 | 104 | if uniformBuffer == nil{ 105 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: MTLResourceOptions()) 106 | } 107 | 108 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 109 | 110 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNSelector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNSelector.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 25/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | The general class used to select between two input `AHNTextureProvider`s using a third input `AHNTextureProvider` and write it to an output. This class is not instantiated directly, but is used by various subclasses. 16 | 17 | *Conforms to the `AHNTextureProvider` protocol.* 18 | */ 19 | open class AHNSelector: NSObject, AHNTextureProvider{ 20 | 21 | 22 | // MARK:- Properties 23 | 24 | 25 | ///The `AHNContext` that is being used by the `AHNTextureProvider` to communicate with the GPU. This is recovered from the first `AHNGenerator` class that is encountered in the chain of classes. 26 | open var context: AHNContext 27 | 28 | 29 | 30 | ///The `MTLComputePipelineState` used to run the `Metal` compute kernel on the GPU. 31 | let pipeline: MTLComputePipelineState 32 | 33 | 34 | 35 | ///The `MTLBuffer` used to transfer the constant values used by the compute kernel to the GPU. 36 | open var uniformBuffer: MTLBuffer? 37 | 38 | 39 | 40 | ///The `MTLTexture` that the compute kernel writes to as an output. 41 | var internalTexture: MTLTexture? 42 | 43 | 44 | 45 | /** 46 | The `MTLFunction` compute kernel that modifies the input `MTLTexture`s and writes the output to the `internalTexture` property. 47 | 48 | The function used is specific to each class. 49 | */ 50 | let kernelFunction: MTLFunction 51 | 52 | 53 | 54 | ///Indicates whether or not the `internalTexture` needs updating. 55 | open var dirty: Bool = true 56 | 57 | 58 | 59 | ///The first input that will be combined with `provider2` using `selector` to provide the output. 60 | open var provider: AHNTextureProvider?{ 61 | didSet{ 62 | dirty = true 63 | } 64 | } 65 | 66 | 67 | 68 | ///The second input that will be combined with `provider` using `selector` to provide the output. 69 | open var provider2: AHNTextureProvider?{ 70 | didSet{ 71 | dirty = true 72 | } 73 | } 74 | 75 | 76 | 77 | ///The `AHNTextureProvider` that selects which input to write to the output `MTLTexture` depending on its value at each pixel. 78 | open var selector: AHNTextureProvider?{ 79 | didSet{ 80 | dirty = true 81 | } 82 | } 83 | 84 | 85 | 86 | /** 87 | The width of the output `MTLTexure`. 88 | 89 | This is dictated by the width of the texture of the first input `AHNTextureProvider`. If there is no input, the default width is `128` pixels. 90 | */ 91 | open var textureWidth: Int{ 92 | get{ 93 | if let provider = provider, let provider2 = provider2, let selector = selector{ 94 | return max(provider.textureSize().width, provider2.textureSize().width, selector.textureSize().width) 95 | }else{ 96 | return 128 97 | } 98 | } 99 | } 100 | 101 | 102 | 103 | /** 104 | The height of the output `MTLTexure`. 105 | 106 | This is dictated by the height of the texture of the first input `AHNTextureProvider`. If there is no input, the default height is `128` pixels. 107 | */ 108 | open var textureHeight: Int{ 109 | get{ 110 | if let provider = provider, let provider2 = provider2, let selector = selector{ 111 | return max(provider.textureSize().height, provider2.textureSize().height, selector.textureSize().height) 112 | }else{ 113 | return 128 114 | } 115 | } 116 | } 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | // MARK:- Initialiser 140 | 141 | 142 | /** 143 | Creates a new `AHNSelector` object. 144 | 145 | To be called when instantiating a subclass. 146 | 147 | - parameter functionName: The name of the kernel function that this selector will use to modify the inputs. 148 | */ 149 | init(functionName: String) { 150 | // Gather the context to use from the first input 151 | context = AHNContext.SharedContext 152 | 153 | // Load the kernel function and compute pipeline state 154 | guard let kernelFunction = context.library.makeFunction(name: functionName) else{ 155 | fatalError("AHNoise: Error loading function \(functionName).") 156 | } 157 | self.kernelFunction = kernelFunction 158 | 159 | do{ 160 | try pipeline = context.device.makeComputePipelineState(function: kernelFunction) 161 | }catch let error{ 162 | fatalError("AHNoise: Error creating pipeline state for \(functionName).\n\(error)") 163 | } 164 | 165 | super.init() 166 | } 167 | 168 | 169 | 170 | override public required init(){ 171 | context = AHNContext.SharedContext 172 | 173 | // Load the kernel function and compute pipeline state 174 | guard let kernelFunction = context.library.makeFunction(name: "simplexGenerator") else{ 175 | fatalError("AHNoise: Error loading function simplexGenerator.") 176 | } 177 | self.kernelFunction = kernelFunction 178 | 179 | do{ 180 | try pipeline = context.device.makeComputePipelineState(function: kernelFunction) 181 | }catch let error{ 182 | fatalError("AHNoise: Error creating pipeline state for simplexGenerator.\n\(error)") 183 | } 184 | 185 | super.init() 186 | } 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | // MARK:- Configure Uniforms 201 | 202 | 203 | /** 204 | Override this method in subclasses to configure a uniform buffer to be sent to the kernel. 205 | 206 | - parameter commandEncoder: The `MTLComputeCommandEncoder` used to run the kernel. This can be used to lazily create a buffer of data and add it to the argument table. Any buffer index can be used without affecting the rest of this class. 207 | */ 208 | open func configureArgumentTableWithCommandEncoder(_ commandEncoder: MTLComputeCommandEncoder){ 209 | } 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | // MARK:- Texture Functions 224 | 225 | 226 | /** 227 | Updates the output `MTLTexture`. 228 | 229 | This should not need to be called manually as it is called by the `texture()` method automatically if the texture does not represent the current `AHNTextureProvider` properties. 230 | */ 231 | open func updateTexture(){ 232 | guard let provider1 = provider?.texture(), let provider2 = provider2?.texture(), let selector = selector?.texture() else { return } 233 | 234 | // Create the internalTexture if it equals nil or is the wrong size. 235 | if internalTexture == nil{ 236 | newInternalTexture() 237 | } 238 | if internalTexture!.width != textureWidth || internalTexture!.height != textureHeight{ 239 | newInternalTexture() 240 | } 241 | 242 | let threadGroupsCount = MTLSizeMake(8, 8, 1) 243 | let threadGroups = MTLSizeMake(textureWidth / threadGroupsCount.width, textureHeight / threadGroupsCount.height, 1) 244 | 245 | let commandBuffer = context.commandQueue.makeCommandBuffer() 246 | 247 | let commandEncoder = commandBuffer.makeComputeCommandEncoder() 248 | commandEncoder.setComputePipelineState(pipeline) 249 | commandEncoder.setTexture(provider1, at: 0) 250 | commandEncoder.setTexture(provider2, at: 1) 251 | commandEncoder.setTexture(selector, at: 2) 252 | commandEncoder.setTexture(internalTexture, at: 3) 253 | 254 | // Encode the uniform buffer 255 | configureArgumentTableWithCommandEncoder(commandEncoder) 256 | commandEncoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupsCount) 257 | commandEncoder.endEncoding() 258 | 259 | commandBuffer.commit() 260 | commandBuffer.waitUntilCompleted() 261 | dirty = false 262 | } 263 | 264 | 265 | 266 | ///Create a new `internalTexture` for the first time or whenever the texture is resized. 267 | func newInternalTexture(){ 268 | let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: textureWidth, height: textureHeight, mipmapped: false) 269 | internalTexture = context.device.makeTexture(descriptor: textureDescriptor) 270 | } 271 | 272 | 273 | 274 | ///- returns: The updated output `MTLTexture` for the `AHNSelector`. 275 | open func texture() -> MTLTexture?{ 276 | if isDirty(){ 277 | updateTexture() 278 | } 279 | return internalTexture 280 | } 281 | 282 | 283 | 284 | ///- returns: The MTLSize of the the output `MTLTexture`. If no size has been explicitly set, the default value returned is `128x128` pixels. 285 | open func textureSize() -> MTLSize{ 286 | return MTLSizeMake(textureWidth, textureHeight, 1) 287 | } 288 | 289 | 290 | 291 | ///- returns: The input `AHNTextureProvider` that provides the input `MTLTexture` to the `AHNSelector`. This is taken from `input1`. If there is no input, returns `nil`. 292 | open func textureProvider() -> AHNTextureProvider?{ 293 | return provider 294 | } 295 | 296 | 297 | 298 | ///- returns: `False` if all inputs and the `internalTexture` do not need updating. 299 | open func isDirty() -> Bool { 300 | let dirtyProvider1 = provider?.isDirty() ?? false 301 | let dirtyProvider2 = provider2?.isDirty() ?? false 302 | let dirtySelector = selector?.isDirty() ?? false 303 | return dirtyProvider1 || dirtyProvider2 || dirtySelector || dirty 304 | } 305 | 306 | 307 | 308 | 309 | ///- returns: `False` if either of the two inputs or the selector is not set. 310 | open func canUpdate() -> Bool { 311 | return provider != nil && provider2 != nil && selector != nil 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNSelectorBlend.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNSelectorBlend.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 25/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Blends two input `AHNTextureProvider`s together using a weight from a third input `AHNTextureProvider` used as the `selector`. 16 | 17 | The input `AHNTextureProvider`s may range from a value of `0.0 - 1.0`. This value is taken from the `selector` for each pixel to provide a mixing weight for the two `provider`s. A value of `0.0` will output 100% `provider` and 0% `provider2`, while a value of `1.0` will output 100% `provider2` and 0% `provider`. A value of `0.25` will output a mixture of 75% `provider` and 25% `provider2`. 18 | 19 | *Conforms to the `AHNTextureProvider` protocol.* 20 | */ 21 | open class AHNSelectorBlend: AHNSelector { 22 | 23 | 24 | // MARK:- Initialiser 25 | 26 | 27 | required public init(){ 28 | super.init(functionName: "blendSelector") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNSelectorSelect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNSelectorSelect.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 25/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import simd 12 | 13 | 14 | /** 15 | Selects one of two input `AHNTextureProvider`s to write to the output using a weight from a third input `AHNTextureProvider` used as the `selector`. 16 | 17 | The input `AHNTextureProvider`s may range from a value of `0.0 - 1.0`. This value is taken from the `selector` `AHNTextureProvider` for each pixel to select which input to write to the output `MTLTexture`. A `selector` value between `0.0 - boundary` will result in `provider` being written to the output, whereas a `selector` value between `boundary - 1.0` will result in `provider2` being written to the output. 18 | 19 | The `edgeTransition` property is used to define how abruptly the transition occurs between the two inputs. A value of `0.0` will result in no transition. Higher values cause the transition to be softened by interpolating between the two inputs at the border between them. A maximum value of `1.0` results in the edge transition covering the whole of the two inputs. 20 | 21 | *Conforms to the `AHNTextureProvider` protocol.* 22 | */ 23 | open class AHNSelectorSelect: AHNSelector { 24 | 25 | 26 | // MARK:- Properties 27 | 28 | 29 | /** 30 | The amount the transition between the two inputs should be softened `(0.0 - 1.0)`. 31 | 32 | Values outside the range `(0.0 - 1.0)` may result in undesired behaviour. 33 | 34 | Default value is `0.0`. 35 | */ 36 | var transition: Float = 0{ 37 | didSet{ 38 | dirty = true 39 | } 40 | } 41 | 42 | 43 | 44 | ///The boundary that the selector value is compared to. Selector values larger than this boundary will output `provider2`, and less than this will output `provider`. The default value is `0.5`. 45 | var boundary: Float = 0.5{ 46 | didSet{ 47 | dirty = true 48 | } 49 | } 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | // MARK:- Initialiser 61 | 62 | 63 | required public init(){ 64 | super.init(functionName: "selectSelector") 65 | } 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | // MARK:- Argument table update 78 | 79 | 80 | ///Encodes the required uniform values for this `AHNSelector` subclass. This should never be called directly. 81 | open override func configureArgumentTableWithCommandEncoder(_ commandEncoder: MTLComputeCommandEncoder) { 82 | var uniforms = vector_float2(transition, boundary) 83 | 84 | // Create the uniform buffer 85 | if uniformBuffer == nil{ 86 | uniformBuffer = context.device.makeBuffer(length: MemoryLayout.stride, options: MTLResourceOptions()) 87 | } 88 | 89 | // Copy latest arguments 90 | memcpy(uniformBuffer!.contents(), &uniforms, MemoryLayout.stride) 91 | 92 | // Set the buffer in the argument table 93 | commandEncoder.setBuffer(uniformBuffer, offset: 0, at: 0) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Frameworks/AHNoise/AHNTextureProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AHNTextureProvider.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 22/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | 10 | import Metal 11 | import UIKit 12 | import simd 13 | 14 | 15 | 16 | // MARK:- AHNTextureProvider 17 | 18 | 19 | ///Implemented by classes that output an `MTLTexture`. Provides references to textures and helper functions. 20 | public protocol AHNTextureProvider: class{ 21 | 22 | 23 | /** 24 | - returns: The updated output `MTLTexture` for the `AHNTextureProvider`. 25 | */ 26 | func texture() -> MTLTexture? 27 | 28 | 29 | 30 | ///- returns: The input `AHNTextureProvider` that provides the input `MTLTexture` to this `AHNTextureProvider`. If there is no input, returns `nil`. 31 | func textureProvider() -> AHNTextureProvider? 32 | 33 | 34 | 35 | ///- returns: A `UIImage` created from the output `MTLTexture` provided by the `texture()` function. 36 | func uiImage() -> UIImage? 37 | 38 | 39 | 40 | ///- returns: The MTLSize of the the output `MTLTexture`. If no size has been explicitly set, the default value returned is `128x128` pixels. 41 | func textureSize() -> MTLSize 42 | 43 | 44 | 45 | /** 46 | Updates the output `MTLTexture`. 47 | 48 | This should not need to be called manually as it is called by the `texture()` method automatically if the texture does not represent the current `AHNTextureProvider` properties. 49 | */ 50 | func updateTexture() 51 | 52 | 53 | 54 | ///The `AHNContext` that is being used by the `AHNTextureProvider` to communicate with the GPU. 55 | var context: AHNContext {get set} 56 | 57 | 58 | 59 | ///- returns: Returns `true` if the output `MTLTexture` needs updating to represent the current `AHNTextureProvider` properties. 60 | func isDirty() -> Bool 61 | 62 | 63 | 64 | 65 | var dirty: Bool {get set} 66 | 67 | 68 | 69 | ///Returns a new `AHNTextureProvider` object. 70 | init() 71 | 72 | 73 | 74 | ///- returns: `True` if the object has enough inputs to provide an output. 75 | func canUpdate() -> Bool 76 | 77 | 78 | 79 | /** 80 | Returns the greyscale values in the texture for specified positions, useful for using the texture as a heightmap. 81 | 82 | - parameter positions: The 2D positions in the texture for which to return greyscale values between `0.0 - 1.0`. 83 | - returns: The greyscale values between `0.0 - 1.0` for the specified pixel locations. 84 | */ 85 | func greyscaleValuesAtPositions(_ positions: [CGPoint]) -> [Float] 86 | 87 | 88 | 89 | /** 90 | Returns the colour values in the texture for specified positions. 91 | 92 | - parameter positions: The 2D positions in the texture for which to return colour values for red, green, blue and alpha between `0.0 - 1.0`. 93 | - returns: The colour values between `0.0 - 1.0` for the specified pixel locations. 94 | */ 95 | func colourValuesAtPositions(_ positions: [CGPoint]) -> [(red: Float, green: Float, blue: Float, alpha: Float)] 96 | 97 | 98 | 99 | ///- returns: All points in the texture, use this as the input parameter for `colourValuesAtPositions` or `greyscaleValuesAtPositions` to return the values for the whole texture. 100 | func allTexturePositions() -> [CGPoint] 101 | } 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | // MARK:- Default AHNTextureProvider Implementation 115 | 116 | 117 | extension AHNTextureProvider{ 118 | 119 | ///- returns: The input `AHNTextureProvider` that provides the input `MTLTexture` to this `AHNTextureProvider`. If there is no input, returns `nil`. 120 | public func textureProvider() -> AHNTextureProvider?{ 121 | return nil 122 | } 123 | 124 | 125 | 126 | ///- returns: A UIImage created from the output `MTLTexture` provided by the `texture()` function. 127 | public func uiImage() -> UIImage?{ 128 | if !canUpdate(){ return nil } 129 | guard let texture = texture() else { return nil } 130 | return UIImage.imageWithMTLTexture(texture) 131 | } 132 | 133 | 134 | 135 | /** 136 | Returns the colour values in the texture for specified positions, useful for using the texture as a heightmap. 137 | 138 | - parameter positions: The 2D positions in the texture for which to return greyscale values between `0.0 - 1.0`. 139 | - returns: The greyscale values between `0.0 - 1.0` for the specified pixel locations. 140 | */ 141 | public func greyscaleValuesAtPositions(_ positions: [CGPoint]) -> [Float]{ 142 | let size = textureSize() 143 | let pixelCount = size.width * size.height 144 | var array = [UInt8](repeating: 0, count: pixelCount*4) 145 | let region = MTLRegionMake2D(0, 0, size.width, size.height) 146 | texture()?.getBytes(&array, bytesPerRow: size.width * MemoryLayout.stride*4, from: region, mipmapLevel: 0) 147 | 148 | var returnArray = [Float](repeating: 0, count: positions.count) 149 | for (i, position) in positions.enumerated(){ 150 | if Int(position.x) >= size.width || Int(position.y) >= size.height{ 151 | print("AHNoise: ERROR - Unable to get value for \(position) as it is outside the texture bounds") 152 | continue 153 | } 154 | let index = (Int(position.x) + (Int(position.y) * size.width)) * 4 155 | returnArray[i] = Float(array[index])/255 156 | } 157 | return returnArray 158 | } 159 | 160 | 161 | 162 | /** 163 | Returns the colour values in the texture for specified positions, useful for using the texture as a heightmap. 164 | 165 | - parameter positions: The 2D positions in the texture for which to return colour values for red, green, blue and alpha between `0.0 - 1.0`. 166 | - returns: The colour values between `0.0 - 1.0` for the specified pixel locations. 167 | */ 168 | public func colourValuesAtPositions(_ positions: [CGPoint]) -> [(red: Float, green: Float, blue: Float, alpha: Float)]{ 169 | let size = textureSize() 170 | let pixelCount = size.width * size.height 171 | var array = [UInt8](repeating: 0, count: pixelCount*4) 172 | let region = MTLRegionMake2D(0, 0, size.width, size.height) 173 | texture()?.getBytes(&array, bytesPerRow: size.width * MemoryLayout.stride*4, from: region, mipmapLevel: 0) 174 | 175 | var returnArray = [(red: Float, green: Float, blue: Float, alpha: Float)](repeating: (0,0,0,0), count: positions.count) 176 | for (i, position) in positions.enumerated(){ 177 | if Int(position.x) >= size.width || Int(position.y) >= size.height{ 178 | print("AHNoise: ERROR - Unable to get value for \(position) at index \(i) as it is outside the texture bounds") 179 | continue 180 | } 181 | let index = (Int(position.x) + (Int(position.y) * size.width)) * 4 182 | let r = Float(array[index])/255 183 | let g = Float(array[index+1])/255 184 | let b = Float(array[index+2])/255 185 | let a = Float(array[index+3])/255 186 | returnArray[i] = (red: r, green: g, blue: b, alpha: a) 187 | } 188 | return returnArray 189 | } 190 | 191 | 192 | 193 | ///- returns: All points in the texture, use this as the input parameter for `colourValuesAtPositions` or `greyscaleValuesAtPositions` to return the values for the whole texture. 194 | public func allTexturePositions() -> [CGPoint]{ 195 | let size = textureSize() 196 | var array: [CGPoint] = [] 197 | for i in 0.. 10 | using namespace metal; 11 | 12 | 13 | // Add Combiner 14 | kernel void addCombiner(texture2d inTexture1 [[texture(0)]], 15 | texture2d inTexture2 [[texture(1)]], 16 | texture2d outTexture [[texture(2)]], 17 | constant bool &uniforms [[buffer(0)]], 18 | uint2 gid [[thread_position_in_grid]]) 19 | { 20 | float4 in1 = inTexture1.read(gid); 21 | float4 in2 = inTexture2.read(gid); 22 | float3 out = in1.rgb+in2.rgb; 23 | if (uniforms == true){ 24 | out /= 2; 25 | } 26 | 27 | outTexture.write(float4(out,1), gid); 28 | } 29 | 30 | // Subtract Combiner 31 | kernel void subtractCombiner(texture2d inTexture1 [[texture(0)]], 32 | texture2d inTexture2 [[texture(1)]], 33 | texture2d outTexture [[texture(2)]], 34 | uint2 gid [[thread_position_in_grid]]) 35 | { 36 | float4 in1 = inTexture1.read(gid); 37 | float4 in2 = inTexture2.read(gid); 38 | float3 out = in1.rgb-in2.rgb; 39 | 40 | outTexture.write(float4(out,1), gid); 41 | } 42 | 43 | // Multiply Combiner 44 | kernel void multiplyCombiner(texture2d inTexture1 [[texture(0)]], 45 | texture2d inTexture2 [[texture(1)]], 46 | texture2d outTexture [[texture(2)]], 47 | uint2 gid [[thread_position_in_grid]]) 48 | { 49 | float4 in1 = inTexture1.read(gid); 50 | float4 in2 = inTexture2.read(gid); 51 | float3 out = in1.rgb*in2.rgb; 52 | 53 | outTexture.write(float4(out,1), gid); 54 | } 55 | 56 | // Divide Combiner 57 | kernel void divideCombiner(texture2d inTexture1 [[texture(0)]], 58 | texture2d inTexture2 [[texture(1)]], 59 | texture2d outTexture [[texture(2)]], 60 | uint2 gid [[thread_position_in_grid]]) 61 | { 62 | float4 in1 = inTexture1.read(gid); 63 | float4 in2 = inTexture2.read(gid); 64 | float3 out = in1.rgb/in2.rgb; 65 | 66 | outTexture.write(float4(out,1), gid); 67 | } 68 | 69 | // Power Combiner 70 | kernel void powerCombiner(texture2d inTexture1 [[texture(0)]], 71 | texture2d inTexture2 [[texture(1)]], 72 | texture2d outTexture [[texture(2)]], 73 | uint2 gid [[thread_position_in_grid]]) 74 | { 75 | float4 in1 = inTexture1.read(gid); 76 | float4 in2 = inTexture2.read(gid); 77 | float3 out = pow(in1.rgb, in2.rgb); 78 | 79 | outTexture.write(float4(out,1), gid); 80 | } 81 | 82 | // Min Combiner 83 | kernel void minCombiner(texture2d inTexture1 [[texture(0)]], 84 | texture2d inTexture2 [[texture(1)]], 85 | texture2d outTexture [[texture(2)]], 86 | uint2 gid [[thread_position_in_grid]]) 87 | { 88 | float4 in1 = inTexture1.read(gid); 89 | float4 in2 = inTexture2.read(gid); 90 | float ave1 = (in1.r + in1.g + in1.b)/3; 91 | float ave2 = (in2.r + in2.g + in2.b)/3; 92 | 93 | if (ave1 < ave2){ 94 | outTexture.write(in1, gid); 95 | }else{ 96 | outTexture.write(in2, gid); 97 | } 98 | } 99 | 100 | // Max Combiner 101 | kernel void maxCombiner(texture2d inTexture1 [[texture(0)]], 102 | texture2d inTexture2 [[texture(1)]], 103 | texture2d outTexture [[texture(2)]], 104 | uint2 gid [[thread_position_in_grid]]) 105 | { 106 | float4 in1 = inTexture1.read(gid); 107 | float4 in2 = inTexture2.read(gid); 108 | float ave1 = (in1.r + in1.g + in1.b)/3; 109 | float ave2 = (in2.r + in2.g + in2.b)/3; 110 | 111 | if (ave1 > ave2){ 112 | outTexture.write(in1, gid); 113 | }else{ 114 | outTexture.write(in2, gid); 115 | } 116 | } -------------------------------------------------------------------------------- /Frameworks/AHNoise/Modifiers.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Modifiers.metal 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 24/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | #include 10 | using namespace metal; 11 | 12 | // MARK:- Absolute Modifier 13 | kernel void absoluteModifier(texture2d inTexture [[texture(0)]], 14 | texture2d outTexture [[texture(1)]], 15 | constant bool &uniforms [[buffer(0)]], 16 | uint2 gid [[thread_position_in_grid]]) 17 | { 18 | float4 in = (inTexture.read(gid)*2)-1; 19 | float3 out = abs(in.rgb); 20 | if (uniforms == false){ 21 | out = (out+1)/2; 22 | } 23 | outTexture.write(float4(out,1), gid); 24 | } 25 | 26 | // MARK:- Clamp Modifier 27 | struct ClampModifierUniforms { 28 | bool normalise; 29 | float2 clampValues; 30 | }; 31 | 32 | kernel void clampModifier(texture2d inTexture [[texture(0)]], 33 | texture2d outTexture [[texture(1)]], 34 | constant ClampModifierUniforms &uniforms [[buffer(0)]], 35 | uint2 gid [[thread_position_in_grid]]) 36 | { 37 | float4 in = inTexture.read(gid); 38 | bool normalise = uniforms.normalise; 39 | float mi = uniforms.clampValues.x; 40 | float ma = uniforms.clampValues.y; 41 | 42 | float3 out = clamp(in.rgb, mi, ma); 43 | float o = (out.r+out.g+out.b)/3; 44 | 45 | if (normalise == true){ 46 | float average = (mi+ma)/2; 47 | float movedMin = mi-average; 48 | float movedMax = ma-average; 49 | in.rgb -= average; 50 | if (o<0){ 51 | in.rgb /= abs(movedMin); 52 | }else{ 53 | in.rgb /= movedMax; 54 | } 55 | in.rgb /= 2; 56 | in.rgb += 0.5; 57 | out = in.rgb; 58 | } 59 | outTexture.write(float4(out,1), gid); 60 | } 61 | 62 | 63 | // MARK:- Step Modifier 64 | kernel void stepModifier(texture2d inTexture [[texture(0)]], 65 | texture2d outTexture [[texture(1)]], 66 | constant float3 &uniforms [[buffer(0)]], 67 | uint2 gid [[thread_position_in_grid]]) 68 | { 69 | float4 in = inTexture.read(gid); 70 | float out = (in.r+in.g+in.b)/3; 71 | if (out < uniforms.z){ 72 | out = uniforms.x; 73 | }else{ 74 | out = uniforms.y; 75 | } 76 | outTexture.write(float4(out,out,out,1), gid); 77 | } 78 | 79 | // MARK:- Invert Modifier 80 | kernel void invertModifier(texture2d inTexture [[texture(0)]], 81 | texture2d outTexture [[texture(1)]], 82 | uint2 gid [[thread_position_in_grid]]) 83 | { 84 | float4 in = inTexture.read(gid); 85 | float3 out = 1 - in.rgb; 86 | outTexture.write(float4(out,1), gid); 87 | } 88 | 89 | // MARK:- Scale Bias Modifier 90 | kernel void scaleBiasModifier(texture2d inTexture [[texture(0)]], 91 | texture2d outTexture [[texture(1)]], 92 | constant float2 &uniforms [[buffer(0)]], 93 | uint2 gid [[thread_position_in_grid]]) 94 | { 95 | float4 in = inTexture.read(gid); 96 | float3 out = (in.rgb * uniforms.x)+uniforms.y; 97 | outTexture.write(float4(out,1), gid); 98 | } 99 | 100 | // MARK:- Round Modifier 101 | kernel void roundModifier(texture2d inTexture [[texture(0)]], 102 | texture2d outTexture [[texture(1)]], 103 | constant float &uniforms [[buffer(0)]], 104 | uint2 gid [[thread_position_in_grid]]) 105 | { 106 | float4 in = inTexture.read(gid); 107 | float3 out = in.rgb; 108 | float n = uniforms; 109 | out /= n; 110 | out = round(out); 111 | out *= n; 112 | outTexture.write(float4(out,1), gid); 113 | } 114 | 115 | // MARK:- Loop Modifier 116 | struct LoopModifierUniforms { 117 | bool normalise; 118 | float loopValue; 119 | }; 120 | 121 | kernel void loopModifier(texture2d inTexture [[texture(0)]], 122 | texture2d outTexture [[texture(1)]], 123 | constant LoopModifierUniforms &uniforms [[buffer(0)]], 124 | uint2 gid [[thread_position_in_grid]]) 125 | { 126 | float4 in = inTexture.read(gid); 127 | bool normalise = uniforms.normalise; 128 | float loop = uniforms.loopValue; 129 | float out = ((in.r+in.g+in.b)/3) / loop; 130 | float fout = floor(out); 131 | out = out - fout; 132 | if (normalise == false){ 133 | out *= loop; 134 | } 135 | outTexture.write(float4(out,out,out,1), gid); 136 | } 137 | 138 | // MARK:- Stretch Modifier 139 | kernel void stretchModifier(texture2d inTexture [[texture(0)]], 140 | texture2d outTexture [[texture(1)]], 141 | constant float4 &uniforms [[buffer(0)]], 142 | uint2 gid [[thread_position_in_grid]], 143 | uint2 threads [[threads_per_grid]]) 144 | { 145 | float xScale = uniforms.x; 146 | float yScale = uniforms.y; 147 | float xAnchor = uniforms.z; 148 | float yAnchor = uniforms.w; 149 | float inX = float(gid.x); 150 | float inY = float(gid.y); 151 | uint2 halfSize = uint2(threads.x * xAnchor, threads.y * yAnchor); 152 | float scaledX = halfSize.x + ((inX - halfSize.x) / xScale); 153 | float scaledY = halfSize.y + ((inY - halfSize.y) / yScale); 154 | 155 | float4 out = float4(0,0,0,0); 156 | bool interpolate = true; 157 | if (scaledX < 0 || scaledX > threads.x || scaledY < 0 || scaledY > threads.y){ 158 | interpolate = false; 159 | } 160 | 161 | if (interpolate){ 162 | int scaledXF = floor(scaledX); 163 | int scaledXC = ceil(scaledX); 164 | float facX = fract(scaledX); 165 | int scaledYF = floor(scaledY); 166 | int scaledYC = ceil(scaledY); 167 | float facY = fract(scaledY); 168 | 169 | 170 | float4 in1 = inTexture.read(uint2(scaledXF, scaledYF)); 171 | float4 in2 = inTexture.read(uint2(scaledXF, scaledYC)); 172 | float4 in3 = inTexture.read(uint2(scaledXC, scaledYF)); 173 | float4 in4 = inTexture.read(uint2(scaledXC, scaledYC)); 174 | 175 | float4 out1 = mix(in1, in3, facX); 176 | float4 out2 = mix(in2, in4, facX); 177 | out = mix(out1, out2, facY); 178 | } 179 | outTexture.write(out, gid); 180 | } 181 | 182 | // MARK:- Normal Map Modifier - DEPRECATED! USES SPRITEKIT NOW 183 | struct NormalMapModifierUniforms { 184 | float intensity; 185 | int2 axes; 186 | }; 187 | 188 | kernel void normalMapModifier(texture2d inTexture [[texture(0)]], 189 | texture2d outTexture [[texture(1)]], 190 | constant NormalMapModifierUniforms &uniforms [[buffer(0)]], 191 | uint2 gid [[thread_position_in_grid]], 192 | uint2 threads [[threads_per_grid]]) 193 | {} 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | // MARK:- MARK:- Colour Modifier 202 | kernel void colourModifier(texture2d inTexture [[texture(0)]], 203 | texture2d outTexture [[texture(1)]], 204 | const device float4 *uniforms [[buffer(0)]], 205 | const device float *positions [[buffer(1)]], 206 | const device float *intensities [[buffer(2)]], 207 | constant int &count [[buffer(3)]], 208 | uint2 gid [[thread_position_in_grid]]) 209 | { 210 | float4 in = inTexture.read(gid); 211 | float ave = (in.r+in.g+in.b)/3; 212 | 213 | int colourCount = count; 214 | float4 out; 215 | 216 | float4 c1; float4 c2; 217 | float p1; float p2; 218 | float i1; float i2; 219 | 220 | for (int i = 0; i < colourCount; i++){ 221 | float p = positions[i]; 222 | if (ave < p){ 223 | c1 = i == 0 ? uniforms[i] : uniforms[i-1]; 224 | c2 = uniforms[i]; 225 | p1 = i == 0 ? 0 : positions[i-1]; 226 | p2 = p; 227 | i1 = i == 0 ? intensities[i] : intensities[i-1]; 228 | i2 = intensities[i]; 229 | break; 230 | } 231 | c1 = uniforms[colourCount-1]; 232 | c2 = uniforms[colourCount-1]; 233 | p1 = positions[colourCount-1]; 234 | p2 = 1; 235 | i1 = intensities[colourCount-1]; 236 | i2 = intensities[colourCount-1]; 237 | } 238 | 239 | float weight = (ave - p1)/(p2 - p1); 240 | out = mix(c1, c2, weight); 241 | float inten = (i2 * weight) + (i1 * (1-weight)); 242 | out = mix(in, out, inten); 243 | 244 | outTexture.write(out, gid); 245 | } 246 | 247 | // MARK:- Rotate Modifier 248 | kernel void rotateModifier(texture2d inTexture [[texture(0)]], 249 | texture2d outTexture [[texture(1)]], 250 | constant float4 &uniforms [[buffer(0)]], 251 | uint2 gid [[thread_position_in_grid]], 252 | uint2 threads [[threads_per_grid]]) 253 | { 254 | float xAnchor = uniforms.x; 255 | float yAnchor = uniforms.y; 256 | float angle = uniforms.z; 257 | float2 o = float2(threads.x * xAnchor, threads.y * yAnchor); 258 | 259 | float rotatedX = ( (gid.x-o.x) * cos(angle) ) - ( (gid.y-o.y) * sin(angle) ) + o.x; 260 | float rotatedY = ( (gid.y-o.y) * cos(angle) ) + ( (gid.x-o.x) * sin(angle) ) + o.y; 261 | 262 | float4 out = float4(0,0,0,0); 263 | bool interpolate = true; 264 | if (uniforms.w == 1){ 265 | if (rotatedX < 0 || rotatedX > threads.x || rotatedY < 0 || rotatedY > threads.y){ 266 | interpolate = false; 267 | } 268 | } 269 | 270 | if (interpolate){ 271 | int rotatedXF = floor(rotatedX); 272 | int rotatedXC = ceil(rotatedX); 273 | float facX = fract(rotatedX); 274 | int rotatedYF = floor(rotatedY); 275 | int rotatedYC = ceil(rotatedY); 276 | float facY = fract(rotatedY); 277 | 278 | 279 | float4 in1 = inTexture.read(uint2(rotatedXF, rotatedYF)); 280 | float4 in2 = inTexture.read(uint2(rotatedXF, rotatedYC)); 281 | float4 in3 = inTexture.read(uint2(rotatedXC, rotatedYF)); 282 | float4 in4 = inTexture.read(uint2(rotatedXC, rotatedYC)); 283 | 284 | float4 out1 = mix(in1, in3, facX); 285 | float4 out2 = mix(in2, in4, facX); 286 | out = mix(out1, out2, facY); 287 | } 288 | outTexture.write(out, gid); 289 | } 290 | 291 | // MARK:- Swirl Modifier 292 | kernel void swirlModifier(texture2d inTexture [[texture(0)]], 293 | texture2d outTexture [[texture(1)]], 294 | constant float4 &uniforms [[buffer(0)]], 295 | uint2 gid [[thread_position_in_grid]], 296 | uint2 threads [[threads_per_grid]]) 297 | { 298 | float xAnchor = uniforms.x; 299 | float yAnchor = uniforms.y; 300 | float2 o = float2(threads.x * xAnchor, threads.y * yAnchor); 301 | 302 | float dx = abs(gid.x - o.x) / threads.x; 303 | float dy = abs(gid.y - o.y) / threads.y; 304 | float i = sqrt(2.0)-sqrt(dx*dx + dy*dy); 305 | float angle = uniforms.z * i; 306 | 307 | float rotatedX = ( (gid.x-o.x) * cos(angle) ) - ( (gid.y-o.y) * sin(angle) ) + o.x; 308 | float rotatedY = ( (gid.y-o.y) * cos(angle) ) + ( (gid.x-o.x) * sin(angle) ) + o.y; 309 | 310 | float4 out = float4(0,0,0,0); 311 | bool interpolate = true; 312 | if (uniforms.w == 1){ 313 | if (rotatedX < 0 || rotatedX > threads.x || rotatedY < 0 || rotatedY > threads.y){ 314 | interpolate = false; 315 | } 316 | } 317 | if (interpolate){ 318 | int rotatedXF = floor(rotatedX); 319 | int rotatedXC = ceil(rotatedX); 320 | float facX = fract(rotatedX); 321 | int rotatedYF = floor(rotatedY); 322 | int rotatedYC = ceil(rotatedY); 323 | float facY = fract(rotatedY); 324 | 325 | 326 | float4 in1 = inTexture.read(uint2(rotatedXF, rotatedYF)); 327 | float4 in2 = inTexture.read(uint2(rotatedXF, rotatedYC)); 328 | float4 in3 = inTexture.read(uint2(rotatedXC, rotatedYF)); 329 | float4 in4 = inTexture.read(uint2(rotatedXC, rotatedYC)); 330 | 331 | float4 out1 = mix(in1, in3, facX); 332 | float4 out2 = mix(in2, in4, facX); 333 | out = mix(out1, out2, facY); 334 | } 335 | outTexture.write(out, gid); 336 | } 337 | 338 | // MARK:- Perspective Modifier 339 | kernel void perspectiveModifier(texture2d inTexture [[texture(0)]], 340 | texture2d outTexture [[texture(1)]], 341 | constant float3 &uniforms [[buffer(0)]], 342 | uint2 gid [[thread_position_in_grid]], 343 | uint2 threads [[threads_per_grid]]) 344 | { 345 | float xScale = uniforms.x; 346 | float yScale = uniforms.y; 347 | float direction = uniforms.z; 348 | 349 | float inX = float(gid.x); 350 | float inY = float(gid.y); 351 | uint2 halfSize = uint2(threads.x/2, 0); 352 | float scaledX = halfSize.x + ((inX - halfSize.x - (halfSize.x * direction * (inY/threads.y))) / (1 - (xScale * (inY/threads.y)))); 353 | float scaledY = halfSize.y + ((inY - halfSize.y) / yScale); 354 | 355 | 356 | float4 out = float4(0,0,0,0); 357 | bool interpolate = true; 358 | if (scaledX < 0 || scaledX > threads.x || scaledY < 0 || scaledY > threads.y){ 359 | interpolate = false; 360 | } 361 | 362 | if (interpolate){ 363 | int scaledXF = floor(scaledX); 364 | int scaledXC = ceil(scaledX); 365 | float facX = fract(scaledX); 366 | int scaledYF = floor(scaledY); 367 | int scaledYC = ceil(scaledY); 368 | float facY = fract(scaledY); 369 | 370 | 371 | float4 in1 = inTexture.read(uint2(scaledXF, scaledYF)); 372 | float4 in2 = inTexture.read(uint2(scaledXF, scaledYC)); 373 | float4 in3 = inTexture.read(uint2(scaledXC, scaledYF)); 374 | float4 in4 = inTexture.read(uint2(scaledXC, scaledYC)); 375 | 376 | float4 out1 = mix(in1, in3, facX); 377 | float4 out2 = mix(in2, in4, facX); 378 | out = mix(out1, out2, facY); 379 | } 380 | outTexture.write(out, gid); 381 | } 382 | 383 | struct ScaleCanvasProperties{ 384 | float4 scale; 385 | uint4 oldSize; 386 | }; 387 | 388 | // MARK:- ScaleCanvas Modifier 389 | kernel void scaleCanvasModifier(texture2d inTexture [[texture(0)]], 390 | texture2d outTexture [[texture(1)]], 391 | constant ScaleCanvasProperties &uniforms [[buffer(0)]], 392 | uint2 gid [[thread_position_in_grid]], 393 | uint2 threads [[threads_per_grid]]) 394 | { 395 | float xAnchor = uniforms.scale.x * threads.x; 396 | float yAnchor = uniforms.scale.y * threads.y; 397 | float xScale = uniforms.scale.z; 398 | float yScale = uniforms.scale.w; 399 | int inputWidth = uniforms.oldSize.x; 400 | int inputHeight = uniforms.oldSize.y; 401 | 402 | float inX = float(gid.x); 403 | float inY = float(gid.y); 404 | float scaledX = (inX-xAnchor)/xScale; 405 | float scaledY = (inY-yAnchor)/yScale; 406 | 407 | 408 | float4 out = float4(0,0,0,0); 409 | bool interpolate = true; 410 | if (scaledX < 0 || scaledX > inputWidth || scaledY < 0 || scaledY > inputHeight){ 411 | interpolate = false; 412 | } 413 | 414 | if (interpolate){ 415 | int scaledXF = floor(scaledX); 416 | int scaledXC = ceil(scaledX); 417 | float facX = fract(scaledX); 418 | int scaledYF = floor(scaledY); 419 | int scaledYC = ceil(scaledY); 420 | float facY = fract(scaledY); 421 | 422 | 423 | float4 in1 = inTexture.read(uint2(scaledXF, scaledYF)); 424 | float4 in2 = inTexture.read(uint2(scaledXF, scaledYC)); 425 | float4 in3 = inTexture.read(uint2(scaledXC, scaledYF)); 426 | float4 in4 = inTexture.read(uint2(scaledXC, scaledYC)); 427 | 428 | float4 out1 = mix(in1, in3, facX); 429 | float4 out2 = mix(in2, in4, facX); 430 | out = mix(out1, out2, facY); 431 | } 432 | outTexture.write(out, gid); 433 | } -------------------------------------------------------------------------------- /Frameworks/AHNoise/Selectors.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Selectors.metal 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 25/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | #include 10 | using namespace metal; 11 | 12 | 13 | // Blend Selector 14 | kernel void blendSelector(texture2d inTexture1 [[texture(0)]], 15 | texture2d inTexture2 [[texture(1)]], 16 | texture2d selector [[texture(2)]], 17 | texture2d outTexture [[texture(3)]], 18 | uint2 gid [[thread_position_in_grid]]) 19 | { 20 | float4 in1 = inTexture1.read(gid); 21 | float4 in2 = inTexture2.read(gid); 22 | float4 sel = selector.read(gid); 23 | float weight = (sel.r + sel.g + sel.b)/3; 24 | 25 | float3 out = mix(in1.rgb, in2.rgb, weight); 26 | 27 | outTexture.write(float4(out,1), gid); 28 | } 29 | 30 | // Select Selector 31 | kernel void selectSelector(texture2d inTexture1 [[texture(0)]], 32 | texture2d inTexture2 [[texture(1)]], 33 | texture2d selector [[texture(2)]], 34 | texture2d outTexture [[texture(3)]], 35 | constant float2 &uniforms [[buffer(0)]], 36 | uint2 gid [[thread_position_in_grid]]) 37 | { 38 | float4 in1 = inTexture1.read(gid); 39 | float4 in2 = inTexture2.read(gid); 40 | float4 sel = selector.read(gid); 41 | float weight = (sel.r + sel.g + sel.b)/3; 42 | float edge = uniforms.x; 43 | float bound = uniforms.y; 44 | float nedge = bound-(edge/2); 45 | 46 | if (weight <= nedge){ 47 | outTexture.write(in1, gid); 48 | }else if (weight > nedge && weight <((bound*2)-nedge)){ 49 | float fac = (((edge/2)-bound)+weight)/edge; 50 | outTexture.write(float4(mix(in1.rgb, in2.rgb, fac),1),gid); 51 | }else{ 52 | outTexture.write(in2, gid); 53 | } 54 | } -------------------------------------------------------------------------------- /Frameworks/AHNoise/UIImage+AHNoise.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+AHNoise.swift 3 | // AHNoise 4 | // 5 | // Created by Andrew Heard on 23/02/2016. 6 | // Copyright © 2016 Andrew Heard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | extension UIImage{ 13 | 14 | ///Converts the input `MTLTexture` into a UIImage. 15 | static func imageWithMTLTexture(_ texture: MTLTexture) -> UIImage{ 16 | assert(texture.pixelFormat == .rgba8Unorm, "Pixel format of texture must be MTLPixelFormatBGRA8Unorm to create UIImage") 17 | 18 | let imageByteCount: size_t = texture.width * texture.height * 4 19 | let imageBytes = malloc(imageByteCount) 20 | let bytesPerRow = texture.width * 4 21 | 22 | let region = MTLRegionMake2D(0, 0, texture.width, texture.height) 23 | texture.getBytes(imageBytes!, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0) 24 | 25 | let AHNReleaseDataCallback: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in 26 | free(UnsafeMutableRawPointer(mutating: data)) 27 | } 28 | 29 | guard let provider = CGDataProvider(dataInfo: nil, data: imageBytes!, size: imageByteCount, releaseData: AHNReleaseDataCallback) else { 30 | fatalError("AHNoise: Error creating CGDataProvider for conversion of MTLTexture to UIImage") 31 | } 32 | let bitsPerComponent = 8 33 | let bitsPerPixel = 32 34 | let colourSpaceRef = CGColorSpaceCreateDeviceRGB() 35 | let bitmapInfo: CGBitmapInfo = [CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue), CGBitmapInfo.byteOrder32Big] 36 | let renderingIntent: CGColorRenderingIntent = .defaultIntent 37 | 38 | let imageRef = CGImage( 39 | width: texture.width, 40 | height: texture.height, 41 | bitsPerComponent: bitsPerComponent, 42 | bitsPerPixel: bitsPerPixel, 43 | bytesPerRow: bytesPerRow, 44 | space: colourSpaceRef, 45 | bitmapInfo: bitmapInfo, 46 | provider: provider, 47 | decode: nil, 48 | shouldInterpolate: false, 49 | intent: renderingIntent) 50 | 51 | let image = UIImage(cgImage: imageRef!, scale: 0.0, orientation: UIImageOrientation.downMirrored) 52 | return image 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Planets!.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Planets!/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Planets! 4 | // 5 | // Created by Robert-Hein Hooijmans on 08/12/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | return true 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Planets!/Extensions/Color.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color.swift 3 | // demo 4 | // 5 | // Created by Robert-Hein Hooijmans on 18/11/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIColor { 13 | 14 | func components() -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { 15 | var r: CGFloat = 0 16 | var g: CGFloat = 0 17 | var b: CGFloat = 0 18 | var a: CGFloat = 0 19 | 20 | getRed(&r, green: &g, blue: &b, alpha: &a) 21 | 22 | return (r, g, b, a) 23 | } 24 | 25 | func combine(with color: UIColor, amount: CGFloat) -> UIColor { 26 | let fromComponents = components() 27 | let toComponents = color.components() 28 | 29 | let r = CGFloat(lerp(from: Float(fromComponents.red), to: Float(toComponents.red), alpha: Float(amount))) 30 | let g = CGFloat(lerp(from: Float(fromComponents.green), to: Float(toComponents.green), alpha: Float(amount))) 31 | let b = CGFloat(lerp(from: Float(fromComponents.blue), to: Float(toComponents.blue), alpha: Float(amount))) 32 | 33 | return UIColor(red: r, green: g, blue: b, alpha: 1) 34 | } 35 | 36 | static func rgb(_ r: CGFloat, _ g: CGFloat, _ b: CGFloat) -> UIColor { 37 | return UIColor(red: r/255, green: g/255, blue: b/255, alpha: 1) 38 | } 39 | 40 | static func random() -> UIColor { 41 | return UIColor(red: CGFloat(Float.random()), green: CGFloat(Float.random()), blue: CGFloat(Float.random()), alpha: 1) 42 | } 43 | 44 | func red() -> CGFloat { 45 | var red: CGFloat = 0 46 | getRed(&red, green: nil, blue: nil, alpha: nil) 47 | return red 48 | } 49 | 50 | func green() -> CGFloat { 51 | var green: CGFloat = 0 52 | getRed(nil, green: &green, blue: nil, alpha: nil) 53 | return green 54 | } 55 | 56 | func blue() -> CGFloat { 57 | var blue: CGFloat = 0 58 | getRed(nil, green: nil, blue: &blue, alpha: nil) 59 | return blue 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Planets!/Extensions/Constraints.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constraints.swift 3 | // demo 4 | // 5 | // Created by Robert-Hein Hooijmans on 06/11/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIView { 13 | 14 | public func center(in view: UIView, offset: CGPoint = .zero) { 15 | NSLayoutConstraint.activate([ 16 | centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: offset.x), 17 | centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: offset.y) 18 | ]) 19 | } 20 | 21 | public func frame(to view: UIView, insets: UIEdgeInsets = .zero) { 22 | NSLayoutConstraint.activate([ 23 | topAnchor.constraint(equalTo: view.topAnchor, constant: insets.top), 24 | leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: insets.left), 25 | bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: insets.bottom), 26 | trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: insets.right) 27 | ]) 28 | } 29 | 30 | public func size(_ size: CGSize) { 31 | NSLayoutConstraint.activate([ 32 | widthAnchor.constraint(equalToConstant: size.width), 33 | heightAnchor.constraint(equalToConstant: size.height) 34 | ]) 35 | } 36 | 37 | public func width(_ width: CGFloat) { 38 | constraints.filter { constraint -> Bool in 39 | constraint.firstAttribute == .width 40 | }.forEach { constraint in 41 | constraint.isActive = false 42 | } 43 | 44 | widthAnchor.constraint(equalToConstant: width).isActive = true 45 | } 46 | 47 | public func width(to view: UIView, _ dimension: NSLayoutDimension? = nil, offset: CGFloat = 0) { 48 | widthAnchor.constraint(equalTo: dimension ?? view.widthAnchor, constant: offset).isActive = true 49 | } 50 | 51 | public func height(_ height: CGFloat) { 52 | constraints.filter { constraint -> Bool in 53 | constraint.firstAttribute == .height 54 | }.forEach { constraint in 55 | constraint.isActive = false 56 | } 57 | 58 | heightAnchor.constraint(equalToConstant: height).isActive = true 59 | } 60 | 61 | public func height(to view: UIView, _ dimension: NSLayoutDimension? = nil, offset: CGFloat = 0) { 62 | heightAnchor.constraint(equalTo: dimension ?? view.heightAnchor, constant: offset).isActive = true 63 | } 64 | 65 | public func leading(to view: UIView, _ anchor: NSLayoutXAxisAnchor? = nil, offset: CGFloat = 0) { 66 | leadingAnchor.constraint(equalTo: anchor ?? view.leadingAnchor, constant: offset).isActive = true 67 | } 68 | 69 | public func left(to view: UIView, _ anchor: NSLayoutXAxisAnchor? = nil, offset: CGFloat = 0) { 70 | leftAnchor.constraint(equalTo: anchor ?? view.leftAnchor, constant: offset).isActive = true 71 | } 72 | 73 | public func trailing(to view: UIView, _ anchor: NSLayoutXAxisAnchor? = nil, offset: CGFloat = 0) { 74 | trailingAnchor.constraint(equalTo: anchor ?? view.trailingAnchor, constant: offset).isActive = true 75 | } 76 | 77 | public func right(to view: UIView, _ anchor: NSLayoutXAxisAnchor? = nil, offset: CGFloat = 0) { 78 | rightAnchor.constraint(equalTo: anchor ?? view.rightAnchor, constant: offset).isActive = true 79 | } 80 | 81 | public func top(to view: UIView, _ anchor: NSLayoutYAxisAnchor? = nil, offset: CGFloat = 0) { 82 | topAnchor.constraint(equalTo: anchor ?? view.topAnchor, constant: offset).isActive = true 83 | } 84 | 85 | public func bottom(to view: UIView, _ anchor: NSLayoutYAxisAnchor? = nil, offset: CGFloat = 0) { 86 | bottomAnchor.constraint(equalTo: anchor ?? view.bottomAnchor, constant: offset).isActive = true 87 | } 88 | 89 | public func centerX(to view: UIView, _ anchor: NSLayoutXAxisAnchor? = nil, offset: CGFloat = 0) { 90 | centerXAnchor.constraint(equalTo: anchor ?? view.centerXAnchor, constant: offset).isActive = true 91 | } 92 | 93 | public func centerY(to view: UIView, _ anchor: NSLayoutYAxisAnchor? = nil, offset: CGFloat = 0) { 94 | centerYAnchor.constraint(equalTo: anchor ?? view.centerYAnchor, constant: offset).isActive = true 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Planets!/Extensions/Foundation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Foundation.swift 3 | // demo 4 | // 5 | // Created by Robert-Hein Hooijmans on 08/11/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Float { 12 | 13 | static func random() -> Float { 14 | return Float(arc4random() % 1000) / 1000 15 | } 16 | } 17 | 18 | extension Int { 19 | 20 | static func random(_ max: Int) -> Int { 21 | return Int(arc4random_uniform(UInt32(max))) 22 | } 23 | 24 | static func random(lower: Int , upper: Int) -> Int { 25 | return lower + Int(arc4random_uniform(UInt32(upper - lower))) 26 | } 27 | } 28 | 29 | func lerp(from a: Float, to b: Float, alpha: Float) -> Float { 30 | return (1 - alpha) * a + alpha * b 31 | } 32 | -------------------------------------------------------------------------------- /Planets!/Extensions/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // demo 4 | // 5 | // Created by Robert-Hein Hooijmans on 08/11/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension String { 13 | 14 | func substring(_ location: Int, _ length: Int) -> String? { 15 | guard location + length <= characters.count else { return nil } 16 | 17 | let sub = String(characters.dropFirst(location)) 18 | let index = sub.index(sub.startIndex, offsetBy: length) 19 | return sub.substring(to: index) 20 | } 21 | 22 | func syllables() -> [Syllable] { 23 | var syllables = characters.map { Syllable.Vowel($0) } 24 | syllables.append(.Suffix) 25 | return syllables 26 | } 27 | } 28 | 29 | extension NSMutableAttributedString { 30 | 31 | func addAttributes(_ attrs: [String : Any], enclosingTag: String) { 32 | 33 | let length = self.length 34 | var range = NSRange(location: 0, length: length) 35 | var ranges: [NSRange] = [] 36 | 37 | while range.location != NSNotFound { 38 | range = (string as NSString).range(of: enclosingTag as String, options: NSString.CompareOptions.caseInsensitive, range: range, locale: Locale.current) 39 | 40 | if range.location != NSNotFound { 41 | ranges.append(range) 42 | range = NSRange(location: range.location + range.length, length: length - (range.location + range.length)) 43 | } 44 | } 45 | 46 | var offset = 0 47 | var previousRange = NSRange(location: 0, length: 0) 48 | 49 | ranges.forEach { range in 50 | deleteCharacters(in: NSRange(location: range.location - offset, length: range.length)) 51 | 52 | if NSEqualRanges(previousRange, NSRange(location: 0, length: 0)) { 53 | previousRange = range 54 | } else { 55 | addAttributes(attrs, range: NSRange(location: previousRange.location - offset + enclosingTag.characters.count, length: range.location - previousRange.location - enclosingTag.characters.count)) 56 | previousRange = NSRange(location: 0, length: 0) 57 | } 58 | 59 | offset += range.length 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Planets!/Game/GameViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameViewController.swift 3 | // Planets! 4 | // 5 | // Created by Robert-Hein Hooijmans on 08/12/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SceneKit 11 | 12 | class GameViewController: UIViewController { 13 | 14 | var universe: Universe! 15 | var velocity: CGPoint = .zero 16 | var pan: UIPanGestureRecognizer! 17 | var tap: UITapGestureRecognizer! 18 | var hud: HUD! 19 | 20 | let initialColor = UIColor.rgb(90, 50, 180) 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | universe = Universe(radius: 20) 26 | view.backgroundColor = initialColor 27 | 28 | guard let view = view as? SCNView else { return } 29 | view.scene = universe 30 | view.allowsCameraControl = false 31 | view.antialiasingMode = .multisampling4X 32 | view.delegate = self 33 | 34 | pan = UIPanGestureRecognizer(target: self, action: #selector(pan(with:))) 35 | view.addGestureRecognizer(pan) 36 | 37 | tap = UITapGestureRecognizer(target: self, action: #selector(tap(with:))) 38 | view.addGestureRecognizer(tap) 39 | 40 | hud = HUD() 41 | hud.translatesAutoresizingMaskIntoConstraints = false 42 | view.addSubview(hud) 43 | 44 | hud.frame(to: view) 45 | } 46 | 47 | override func viewDidAppear(_ animated: Bool) { 48 | super.viewDidAppear(animated) 49 | generateTexture(for: initialColor) 50 | } 51 | 52 | func pan(with gesture: UIPanGestureRecognizer) { 53 | velocity = gesture.velocity(in: gesture.view) 54 | } 55 | 56 | func tap(with gesture: UITapGestureRecognizer) { 57 | generateTexture(for: UIColor.random()) 58 | } 59 | 60 | func generateTexture(for color: UIColor) { 61 | tap.isEnabled = false 62 | 63 | Texture.generate(for: color, progress: hud.loader.progress) { texture in 64 | self.view.backgroundColor = texture.color 65 | self.universe.sun.light?.color = texture.color 66 | 67 | guard let material = self.universe.planet.geometry?.firstMaterial else { return } 68 | material.lightingModel = .physicallyBased 69 | material.diffuse.contents = texture.diffuse 70 | material.roughness.contents = texture.diffuse 71 | material.metalness.contents = texture.metalness 72 | material.normal.contents = texture.normal 73 | material.specular.contents = texture.color 74 | 75 | let generator = PlanetNameGenerator() 76 | let planetName = generator.generatePlanetName() 77 | 78 | self.hud.loader.progress(1.0) 79 | self.hud.title.text = planetName 80 | self.tap.isEnabled = true 81 | } 82 | } 83 | 84 | open override var prefersStatusBarHidden: Bool { 85 | return true 86 | } 87 | } 88 | 89 | extension GameViewController: SCNSceneRendererDelegate { 90 | 91 | func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) { 92 | 93 | guard !velocity.equalTo(.zero) else { 94 | return 95 | } 96 | 97 | let scale: CGFloat = 10000 98 | let decelerationRate: CGFloat = 0.97 99 | 100 | let currentRotation = universe.camera.rotation 101 | var rotation = GLKQuaternionMakeWithAngleAndAxis(currentRotation.w, currentRotation.x, currentRotation.y, currentRotation.z) 102 | let rotationX = GLKQuaternionMakeWithAngleAndAxis(Float(-velocity.x / scale), 0, 1, 0) 103 | let rotationY = GLKQuaternionMakeWithAngleAndAxis(Float(-velocity.y / scale), 1, 0, 0) 104 | let multipliedRotation = GLKQuaternionMultiply(rotationX, rotationY) 105 | rotation = GLKQuaternionMultiply(rotation, multipliedRotation) 106 | 107 | let axis = GLKQuaternionAxis(rotation) 108 | let angle = GLKQuaternionAngle(rotation) 109 | universe.camera.rotation = SCNVector4Make(axis.x, axis.y, axis.z, angle) 110 | 111 | if abs(velocity.x) < 1 { 112 | velocity.x = 0 113 | } else { 114 | velocity.x += velocity.x > 0 ? -1 : 1 115 | } 116 | 117 | if velocity.y > -1 && velocity.y < 1 { 118 | velocity.y = 0 119 | } else { 120 | velocity.y += velocity.y > 0 ? -1 : 1 121 | } 122 | 123 | velocity.x *= decelerationRate 124 | velocity.y *= decelerationRate 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Planets!/Game/HUD.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HUD.swift 3 | // Planets! 4 | // 5 | // Created by Robert-Hein Hooijmans on 20/12/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class HUD: UIView { 13 | 14 | var loader: Loader! 15 | var title: UILabel! 16 | 17 | required init?(coder aDecoder: NSCoder) { 18 | fatalError("init(coder:) has not been implemented") 19 | } 20 | 21 | override init(frame: CGRect) { 22 | super.init(frame: frame) 23 | 24 | loader = Loader() 25 | loader.translatesAutoresizingMaskIntoConstraints = false 26 | addSubview(loader) 27 | 28 | loader.top(to: self, offset: 5) 29 | loader.left(to: self, offset: 5) 30 | loader.right(to: self, offset: -5) 31 | loader.height(Loader.height) 32 | 33 | title = UILabel() 34 | title.translatesAutoresizingMaskIntoConstraints = false 35 | title.font = UIFont.systemFont(ofSize: 40, weight: -0.5) 36 | title.textAlignment = .center 37 | title.numberOfLines = 0 38 | title.textColor = UIColor.white.withAlphaComponent(0.8) 39 | addSubview(title) 40 | 41 | title.top(to: self) 42 | title.left(to: self) 43 | title.right(to: self) 44 | title.height(200) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Planets!/Game/Nodes/Camera.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Camera.swift 3 | // Planets! 4 | // 5 | // Created by Robert-Hein Hooijmans on 08/12/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SceneKit 11 | 12 | class Camera: SCNNode { 13 | 14 | convenience init(at distance: Float) { 15 | self.init() 16 | 17 | let camera = SCNCamera() 18 | camera.automaticallyAdjustsZRange = false 19 | camera.zNear = 0.0001 20 | camera.zFar = 999999999 21 | self.camera = camera 22 | 23 | position = SCNVector3Make(0, 0, 0) 24 | pivot = SCNMatrix4MakeTranslation(0, 0, -distance) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Planets!/Game/Nodes/Planet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Planet.swift 3 | // Planets! 4 | // 5 | // Created by Robert-Hein Hooijmans on 08/12/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SceneKit 11 | 12 | class Planet { 13 | 14 | static func node() -> SCNNode? { 15 | let planetScene = SCNScene(named: "art.scnassets/planet.obj") 16 | return planetScene?.rootNode.childNodes[0] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Planets!/Game/Nodes/Stars.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stars.swift 3 | // Planets! 4 | // 5 | // Created by Robert-Hein Hooijmans on 08/12/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SceneKit 11 | 12 | class Stars: SCNNode { 13 | 14 | required init?(coder aDecoder: NSCoder) { 15 | fatalError("init(coder:) has not been implemented") 16 | } 17 | 18 | override init() { 19 | super.init() 20 | 21 | guard let particles = SCNParticleSystem(named: "stars", inDirectory: "art.scnassets/") else { return } 22 | addParticleSystem(particles) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Planets!/Game/Nodes/Sun.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sun.swift 3 | // Planets! 4 | // 5 | // Created by Robert-Hein Hooijmans on 08/12/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SceneKit 11 | 12 | class Sun: SCNNode { 13 | 14 | convenience init(at distance: Float) { 15 | self.init() 16 | 17 | let light = SCNLight() 18 | light.type = SCNLight.LightType.directional 19 | light.intensity = 1000 20 | 21 | self.light = light 22 | position = SCNVector3(x: 0, y: 0, z: distance) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Planets!/Game/Nodes/Universe.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Universe.swift 3 | // Planets! 4 | // 5 | // Created by Robert-Hein Hooijmans on 08/12/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SceneKit 11 | 12 | class Universe: SCNScene { 13 | 14 | var camera: Camera! 15 | var sun: Sun! 16 | var planet: SCNNode! 17 | var stars: Stars! 18 | 19 | convenience init(radius z: Float) { 20 | self.init() 21 | 22 | lightingEnvironment.contents = UIImage(named: "envmap_blurred") 23 | lightingEnvironment.intensity = 4 24 | 25 | camera = Camera(at: z) 26 | rootNode.addChildNode(camera) 27 | 28 | sun = Sun(at: z) 29 | rootNode.addChildNode(sun) 30 | 31 | planet = Planet.node() 32 | rootNode.addChildNode(planet) 33 | 34 | stars = Stars() 35 | rootNode.addChildNode(stars) 36 | 37 | planet.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: -1, y: -1, z: -1, duration: 25))) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Planets!/Game/Objects/Combiner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Combiner.swift 3 | // Planets! 4 | // 5 | // Created by Robert-Hein Hooijmans on 08/12/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Combiner: Int { 12 | case add 13 | case divide 14 | case multiply 15 | case power 16 | case subtract 17 | 18 | private static let count: Combiner.RawValue = { 19 | var maxValue: Int = 0 20 | while let _ = Combiner(rawValue: maxValue) { 21 | maxValue += 1 22 | } 23 | return maxValue 24 | }() 25 | 26 | static func random() -> Combiner { 27 | let combiner = Combiner(rawValue: Int.random(count))! 28 | combiner.log() 29 | return combiner 30 | } 31 | 32 | func object() -> AHNCombiner { 33 | switch self { 34 | case .add: return AHNCombinerAdd() 35 | case .divide: return AHNCombinerDivide() 36 | case .multiply: return AHNCombinerMultiply() 37 | case .power: return AHNCombinerPower() 38 | case .subtract: return AHNCombinerSubtract() 39 | } 40 | } 41 | 42 | func log() { 43 | print("combiner:\(self)") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Planets!/Game/Objects/Generator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generator.swift 3 | // Planets! 4 | // 5 | // Created by Robert-Hein Hooijmans on 08/12/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Generator: Int { 12 | case billow 13 | case ridgedMulti 14 | case simplex 15 | 16 | private static let count: Generator.RawValue = { 17 | var maxValue: Int = 0 18 | while let _ = Generator(rawValue: maxValue) { 19 | maxValue += 1 20 | } 21 | return maxValue 22 | }() 23 | 24 | static func random() -> Generator { 25 | let generator = Generator(rawValue: Int.random(count))! 26 | generator.log() 27 | return generator 28 | } 29 | 30 | func object() -> AHNGeneratorCoherent { 31 | switch self { 32 | case .billow: return AHNGeneratorBillow() 33 | case .ridgedMulti: return AHNGeneratorRidgedMulti() 34 | case .simplex: return AHNGeneratorSimplex() 35 | } 36 | } 37 | 38 | func log() { 39 | print("generator:\(self)") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Planets!/Game/Objects/Loader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Loader.swift 3 | // Planets! 4 | // 5 | // Created by Robert-Hein Hooijmans on 08/12/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class Loader: UIView { 13 | 14 | var bar: UIView! 15 | var progress: TextureProgress! 16 | static let height: CGFloat = 4 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | override init(frame: CGRect) { 23 | super.init(frame: frame) 24 | 25 | bar = UIView() 26 | bar.translatesAutoresizingMaskIntoConstraints = false 27 | bar.backgroundColor = .white 28 | bar.clipsToBounds = true 29 | bar.layer.cornerRadius = 3 30 | addSubview(bar) 31 | 32 | bar.top(to: self) 33 | bar.left(to: self) 34 | bar.width(0) 35 | bar.height(Loader.height) 36 | 37 | progress = { percentage in 38 | self.set(CGFloat(percentage), animated: true) 39 | } 40 | } 41 | 42 | private func set(_ percentage: CGFloat, animated: Bool) { 43 | bar.width(percentage * bounds.width) 44 | 45 | if animated { 46 | let options: UIViewAnimationOptions = [.curveEaseOut, .beginFromCurrentState, .allowUserInteraction] 47 | UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: options, animations: { 48 | self.layoutIfNeeded() 49 | }) { success in 50 | if percentage >= 1.0 { 51 | self.set(0, animated: false) 52 | } 53 | } 54 | } else { 55 | layoutIfNeeded() 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Planets!/Game/Objects/PlanetName.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlanetName.swift 3 | // Planets! 4 | // 5 | // Created by Robert-Hein Hooijmans on 09/12/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | typealias PlanetName = String 12 | typealias Total = Int 13 | typealias Record = (Total, [Syllable: Total]) 14 | typealias Mapping = [Syllable: Record] 15 | 16 | enum Syllable: Hashable, Equatable { 17 | case Vowel(Character) 18 | case Suffix 19 | 20 | var hashValue: Int { 21 | switch self { 22 | case let .Vowel(string): return string.hashValue 23 | case .Suffix: return "".hashValue 24 | } 25 | } 26 | } 27 | 28 | func == (lhs: Syllable, rhs: Syllable) -> Bool { 29 | switch (lhs, rhs) { 30 | case let (.Vowel(l), .Vowel(r)): 31 | return l == r 32 | case (.Suffix, .Suffix): 33 | return true 34 | default: 35 | return false 36 | } 37 | } 38 | 39 | public struct PlanetNameGenerator { 40 | 41 | private var mapping: Mapping 42 | private var vowels = [Syllable]() 43 | 44 | public init() { 45 | mapping = PlanetNameGenerator.mappingFrom(self.existingPlanetNames) 46 | vowels = self.existingPlanetNames.filter { $0.characters.count != 0 }.map { .Vowel($0.characters.first!) } 47 | } 48 | 49 | private static func mappingFrom(_ planetNames: Set) -> Mapping { 50 | var mapping = Mapping() 51 | 52 | for planetName in planetNames { 53 | let syllables = planetName.syllables() 54 | 55 | for (current, next) in zip(syllables, syllables[1.. Syllable { 72 | let randomIndex = Int.random(lower: 0, upper: vowels.count) 73 | return vowels[randomIndex] 74 | } 75 | 76 | func nextSyllable(for syllable: Syllable) -> Syllable { 77 | let (totalCount, records) = mapping[syllable]! 78 | let randomValue = Int.random(lower: 0, upper: Int(totalCount)) 79 | 80 | var sum: Int = 0 81 | for (syllable, total) in records { 82 | sum = sum + Int(total) 83 | if randomValue < sum { 84 | return syllable 85 | } 86 | } 87 | 88 | fatalError("ERROR: RandomValue should never exceed total!") 89 | } 90 | 91 | func generatePlanetName() -> PlanetName { 92 | var syllables = [Syllable]() 93 | var syllable = randomStartingSyllable() 94 | while syllable != .Suffix { 95 | syllables.append(syllable) 96 | syllable = nextSyllable(for: syllable) 97 | } 98 | 99 | return planetNameFromSyllables(syllables: syllables) 100 | } 101 | 102 | private func planetNameFromSyllables(syllables: [Syllable]) -> PlanetName { 103 | return syllables.reduce("") { planetName, syllable in 104 | switch syllable { 105 | case let .Vowel(character): 106 | return planetName + String(character) 107 | case .Suffix: 108 | return planetName 109 | } 110 | } 111 | } 112 | 113 | var existingPlanetNames: Set = { 114 | return ["Apokolips", 115 | "Avalon", 116 | "Bismoll", 117 | "Bizarro World", 118 | "Citadel", 119 | "Competalia", 120 | "Daxam", 121 | "Gemworld", 122 | "H'lven", 123 | "Korugar", 124 | "Krypton", 125 | "Mogo", 126 | "Multiverse", 127 | "Naltor", 128 | "New Genesis", 129 | "Qward", 130 | "Rann", 131 | "Starhaven", 132 | "Takron-Galtos", 133 | "Tamaran", 134 | "Thanagar", 135 | "Urgrund", 136 | "Warworld", 137 | "Xolnar"] 138 | }() 139 | } 140 | -------------------------------------------------------------------------------- /Planets!/Game/Objects/Texture.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Texture.swift 3 | // Planets! 4 | // 5 | // Created by Robert-Hein Hooijmans on 09/12/16. 6 | // Copyright © 2016 Robert-Hein Hooijmans. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | typealias TextureProgress = ((Float) -> Void) 13 | typealias TextureClosure = (Texture) -> Void 14 | 15 | struct Texture { 16 | let color: UIColor 17 | let diffuse: UIImage 18 | let metalness: UIImage 19 | let normal: UIImage 20 | 21 | init(_ color: UIColor, _ diffuse: UIImage, _ metalness: UIImage, _ normal: UIImage) { 22 | self.color = color 23 | self.diffuse = diffuse 24 | self.metalness = metalness 25 | self.normal = normal 26 | } 27 | 28 | static func generate(for color: UIColor, progress: @escaping TextureProgress, completion: @escaping TextureClosure) { 29 | progress(0.1) 30 | 31 | DispatchQueue(label: "queue", qos: .userInitiated).async { 32 | let textureSize = 512 33 | 34 | let generator = Generator.random().object() 35 | generator.textureWidth = textureSize 36 | generator.textureHeight = textureSize 37 | generator.octaves = 1 38 | generator.frequency = Float.random() * 6 + 1 39 | generator.persistence = Float.random() 40 | generator.lacunarity = Float.random() * 10 41 | generator.sphereMap = true 42 | 43 | let colorConstant = AHNGeneratorConstant() 44 | colorConstant.textureWidth = textureSize 45 | colorConstant.textureHeight = textureSize 46 | colorConstant.red = Float(color.red()) 47 | colorConstant.green = Float(color.green()) 48 | colorConstant.blue = Float(color.blue()) 49 | 50 | let combiner = Combiner.random().object() 51 | combiner.provider = colorConstant 52 | combiner.provider2 = generator 53 | 54 | let modifier = AHNModifierMapNormal() 55 | modifier.provider = generator 56 | modifier.intensity = 3 57 | modifier.smoothing = 0 58 | 59 | DispatchQueue.main.async { progress(0.25) } 60 | 61 | let metalness = generator.uiImage() 62 | DispatchQueue.main.async { progress(0.5) } 63 | 64 | let diffuse = combiner.uiImage() 65 | DispatchQueue.main.async { progress(0.75) } 66 | 67 | let normal = modifier.uiImage() 68 | 69 | DispatchQueue.main.async { completion(Texture(color, diffuse!, metalness!, normal!)) } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Planets!/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /Planets!/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Planets!/Resources/Assets.xcassets/envmap.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "envmap.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Planets!/Resources/Assets.xcassets/envmap.imageset/envmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roberthein/Planets/c557b239d5ca106ad9161f121eaed589c1cfc2a9/Planets!/Resources/Assets.xcassets/envmap.imageset/envmap.jpg -------------------------------------------------------------------------------- /Planets!/Resources/Assets.xcassets/envmap2_blurred.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "envmap2_blurred.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Planets!/Resources/Assets.xcassets/envmap2_blurred.imageset/envmap2_blurred.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roberthein/Planets/c557b239d5ca106ad9161f121eaed589c1cfc2a9/Planets!/Resources/Assets.xcassets/envmap2_blurred.imageset/envmap2_blurred.png -------------------------------------------------------------------------------- /Planets!/Resources/Assets.xcassets/envmap_blurred.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "envmap_blurred.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Planets!/Resources/Assets.xcassets/envmap_blurred.imageset/envmap_blurred.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roberthein/Planets/c557b239d5ca106ad9161f121eaed589c1cfc2a9/Planets!/Resources/Assets.xcassets/envmap_blurred.imageset/envmap_blurred.jpg -------------------------------------------------------------------------------- /Planets!/Resources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Planets!/Resources/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Planets!/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarHidden 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Planets!/Resources/art.scnassets/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roberthein/Planets/c557b239d5ca106ad9161f121eaed589c1cfc2a9/Planets!/Resources/art.scnassets/star.png -------------------------------------------------------------------------------- /Planets!/Resources/art.scnassets/stars.scnp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roberthein/Planets/c557b239d5ca106ad9161f121eaed589c1cfc2a9/Planets!/Resources/art.scnassets/stars.scnp -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Planets! 2 | A random planet generator! 3 | 4 | This SceneKit experiment relies heavily upon [AHNoise](https://github.com/AndyHeardApps/AHNoise "AHNoise"). 5 | 6 | ![](https://raw.githubusercontent.com/roberthein/Planets/master/Artwork/header.png) 7 | --------------------------------------------------------------------------------