Added perlin noise generator.

This commit is contained in:
Sander Schobers 2020-05-08 17:23:30 +02:00
parent 85bee43640
commit a77de45eba

151
noise/perlin.go Normal file
View File

@ -0,0 +1,151 @@
package noise
import (
"math"
"math/rand"
)
const perlinB = 0x100
const perlinBM = 0xff
const perlinN = 0x1000
// Perlin contains a perlin noise function (both 1D and 2D).
type Perlin struct {
Seed int64
p [perlinB + perlinB + 2]int
g1 [perlinB + perlinB + 2]float64
g2 [perlinB + perlinB + 2][2]float64
}
// NewPerlin creates a new perlin noise function with seed seed.
func NewPerlin(seed int64) *Perlin {
p := &Perlin{Seed: seed}
p.Init()
return p
}
func perlinSmoothCurve(t float64) float64 { return t * t * (3. - 2.*t) }
func perlinLerp(t, a, b float64) float64 { return a + t*(b-a) }
func perlinAt2D(q [2]float64, rx, ry float64) float64 { return rx*q[0] + ry*q[1] }
func (p *Perlin) noise1d(x float64) float64 {
tx := x + perlinN
bx0 := int(tx) & perlinBM
bx1 := (bx0 + 1) & perlinBM
rx0 := tx - math.Floor(tx)
rx1 := rx0 - 1.
sx := perlinSmoothCurve(rx0)
u := rx0 * p.g1[p.p[bx0]]
v := rx1 * p.g1[p.p[bx1]]
return perlinLerp(sx, u, v)
}
func (p *Perlin) noise2d(x, y float64) float64 {
tx := x + perlinN
bx0 := int(tx) & perlinBM
bx1 := (bx0 + 1) & perlinBM
rx0 := tx - math.Floor(tx)
rx1 := rx0 - 1.
ty := y + perlinN
by0 := int(ty) & perlinBM
by1 := (by0 + 1) & perlinBM
ry0 := ty - math.Floor(ty)
ry1 := ry0 - 1.
i := p.p[bx0]
j := p.p[bx1]
b00 := p.p[i+by0]
b10 := p.p[j+by0]
b01 := p.p[i+by1]
b11 := p.p[j+by1]
sx := perlinSmoothCurve(rx0)
sy := perlinSmoothCurve(ry0)
u := perlinAt2D(p.g2[b00], rx0, ry0)
v := perlinAt2D(p.g2[b10], rx1, ry0)
a := perlinLerp(sx, u, v)
u = perlinAt2D(p.g2[b01], rx0, ry1)
v = perlinAt2D(p.g2[b11], rx1, ry1)
b := perlinLerp(sx, u, v)
return perlinLerp(sy, a, b)
}
func normalize2D(v [2]float64) [2]float64 {
var s float64
s = math.Sqrt(v[0]*v[0] + v[1]*v[1])
v[0] /= s
v[1] /= s
return v
}
// Init initialised the Perlin noise source. Must be called unless struct was created with NewPerlin(...).
func (p *Perlin) Init() {
r := rand.New(rand.NewSource(p.Seed))
for i := 0; i < perlinB; i++ {
p.p[i] = i
p.g1[i] = float64((r.Int()%(perlinB+perlinB))-perlinB) / perlinB
for j := 0; j < 2; j++ {
p.g2[i][j] = float64((r.Int()%(perlinB+perlinB))-perlinB) / perlinB
}
p.g2[i] = normalize2D(p.g2[i])
for j := 0; j < 3; j++ {
r.Int()
}
}
for i := perlinB; i > 0; i-- {
j := r.Int() % perlinB
p.p[i], p.p[j] = p.p[j], p.p[i]
}
for i := 0; i < perlinB+2; i++ {
p.p[perlinB+i] = p.p[i]
p.g1[perlinB+i] = p.g1[i]
for j := 0; j < 2; j++ {
p.g2[perlinB+i][j] = p.g2[i][j]
}
}
}
// Noise1D generates the noise value for x. alpha defines the weight when the sum is formed. Typically it is 2 (function is noisier when alpha approaches 1). beta is the harmonic scaling/spacing (typically 2). n defines the number of harmonics.
func (p *Perlin) Noise1D(x, alpha, beta float64, n int) float64 {
var val, sum float64
var scale float64 = 1
var coord float64 = x
for i := 0; i < n; i++ {
val = p.noise1d(coord)
sum += val / scale
scale *= alpha
coord *= beta
}
return sum
}
// Noise2D generates the noise value for coordinate (x, y). alpha defines the weight when the sum is formed. Typically it is 2 (function is noisier when alpha approaches 1). beta is the harmonic scaling/spacing (typically 2). n defines the number of harmonics.
func (p *Perlin) Noise2D(x, y, alpha, beta float64, n int) float64 {
var val, sum float64
var scale float64 = 1
var coord = [2]float64{x, y}
for i := 0; i < n; i++ {
val = p.noise2d(coord[0], coord[1])
sum += val / scale
scale *= alpha
coord[0] *= beta
coord[1] *= beta
}
return sum
}