Last active
March 30, 2024 16:18
-
-
Save matthewreagan/86b2060a118e2f5aaa5683e6b7ca8930 to your computer and use it in GitHub Desktop.
Perlin noise generation in Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Perlin.swift | |
// Created by Matthew Reagan on 8/7/18. | |
// | |
// Quick implementation of the the classic Perlin noise | |
// generation function, useful for creating organic textures | |
// or terrain. Perlin noise can also be easily generated with | |
// Apple's GameplayKit framework, this code is mostly for | |
// experimentation purposes. (Disclaimer: This code is not | |
// optimized, nor particularly elegant, but it can be used as | |
// a jumping off point for writing custom noise functions.) | |
import Foundation | |
class Perlin { | |
let permutations: [UInt8] = { | |
var p = [UInt8]() | |
for i in 0..<256 { | |
p.append(UInt8(i)) | |
} | |
for _ in 0...1000 { | |
let i1 = Int(arc4random() % 256) | |
let i2 = Int(arc4random() % 256) | |
let swap = p[i1] | |
p[i1] = p[i2] | |
p[i2] = p[i1] | |
} | |
return p | |
}() | |
let gradients: [CGPoint] = { | |
var p = [CGPoint]() | |
for _ in 0..<256 { | |
let randomAngle = CGFloat(Random.between0And1() * (Float.pi * 2.0)) | |
p.append(CGPoint(x: cos(randomAngle), y: sin(randomAngle))) | |
} | |
return p | |
}() | |
func gradientForGridPoint(x: Int, y: Int) -> CGPoint { | |
let index = (x + Int(permutations[y])) % 256 | |
return gradients[index] | |
} | |
func dotProd(p1: CGPoint, p2: CGPoint) -> CGFloat { | |
return (p1.x * p2.x) + (p1.y * p2.y) | |
} | |
func noise(x argX: Int, y argY: Int, | |
resolution: Int, | |
mapSize: Int) -> CGFloat { | |
let x = CGFloat(argX) / CGFloat(mapSize) * CGFloat(resolution) | |
let y = CGFloat(argY) / CGFloat(mapSize) * CGFloat(resolution) | |
let gridX1 = Int(floor(x)) | |
let gridY1 = Int(floor(y)) | |
let gridX2 = gridX1 + 1 | |
let gridY2 = gridY1 + 1 | |
let gridPoints: [CGPoint] = { | |
return [gradientForGridPoint(x: gridX1, y: gridY1), | |
gradientForGridPoint(x: gridX2, y: gridY1), | |
gradientForGridPoint(x: gridX1, y: gridY2), | |
gradientForGridPoint(x: gridX2, y: gridY2)] | |
}() | |
let vectorsToXY: [CGPoint] = { | |
return [CGPoint(x: x - CGFloat(gridX1), y: y - CGFloat(gridY1)), | |
CGPoint(x: x - CGFloat(gridX2), y: y - CGFloat(gridY1)), | |
CGPoint(x: x - CGFloat(gridX1), y: y - CGFloat(gridY2)), | |
CGPoint(x: x - CGFloat(gridX2), y: y - CGFloat(gridY2))] | |
}() | |
let s = dotProd(p1: gridPoints[0], p2: vectorsToXY[0]) | |
let t = dotProd(p1: gridPoints[1], p2: vectorsToXY[1]) | |
let u = dotProd(p1: gridPoints[2], p2: vectorsToXY[2]) | |
let v = dotProd(p1: gridPoints[3], p2: vectorsToXY[3]) | |
let xFract: CGFloat = x.truncatingRemainder(dividingBy: 1.0) | |
let yFract: CGFloat = y.truncatingRemainder(dividingBy: 1.0) | |
let Sx = (3.0 * pow(xFract, 2.0)) - (2.0 * pow(xFract, 3.0)) | |
let a = s + (Sx * (t - s)) | |
let b = u + (Sx * (v - u)) | |
let Sy = (3.0 * pow(yFract, 2.0)) - (2.0 * pow(yFract, 3.0)) | |
let weightedAverage = a + ((b - a) * Sy) | |
return weightedAverage | |
} | |
struct Random { | |
static func between0And1() -> Float { return Float(drand48()) } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment