Skip to content

Instantly share code, notes, and snippets.

@matthewreagan
Last active March 30, 2024 16:18
Show Gist options
  • Save matthewreagan/86b2060a118e2f5aaa5683e6b7ca8930 to your computer and use it in GitHub Desktop.
Save matthewreagan/86b2060a118e2f5aaa5683e6b7ca8930 to your computer and use it in GitHub Desktop.
Perlin noise generation in Swift
// 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