106 lines
2.0 KiB
Crystal
106 lines
2.0 KiB
Crystal
|
|
# FIXME: We may want to generate noise of other numbers of dimensions.
|
|
class Naka::Noise2D
|
|
class Octave
|
|
record Vec2, x : Float64, y : Float64
|
|
|
|
private def lerp(a, b, v)
|
|
a * (1.0 - v) + b * v
|
|
end
|
|
|
|
private def smooth(v)
|
|
v * v * (3.0 - 2.0 * v)
|
|
end
|
|
|
|
private def gradient(orig, grad, p)
|
|
sp = Vec2.new(p.x - orig.x, p.y - orig.y)
|
|
grad.x * sp.x + grad.y * sp.y
|
|
end
|
|
|
|
private def random_gradient
|
|
v = rand * Math::PI * 2.0
|
|
Vec2.new(Math.cos(v), Math.sin(v))
|
|
end
|
|
|
|
@frequency : Float64 | Int32
|
|
@amplitude : Float64 | Int32
|
|
|
|
def amplitude
|
|
@amplitude.to_f
|
|
end
|
|
def frequency
|
|
@frequency.to_f
|
|
end
|
|
|
|
def initialize(@frequency, @amplitude)
|
|
@rgradients = StaticArray(Vec2, 256).new { random_gradient }
|
|
@permutations = StaticArray(Int32, 256).new { |i| i }
|
|
@permutations.shuffle!
|
|
end
|
|
|
|
def get_gradient(x, y)
|
|
idx = @permutations[x & 255] + @permutations[y & 255]
|
|
@rgradients[idx & 255]
|
|
end
|
|
|
|
def get_gradients(x, y)
|
|
x0f = x.floor
|
|
y0f = y.floor
|
|
x0 = x0f.to_i
|
|
y0 = y0f.to_i
|
|
x1 = x0 + 1
|
|
y1 = y0 + 1
|
|
|
|
{
|
|
{
|
|
get_gradient(x0, y0),
|
|
get_gradient(x1, y0),
|
|
get_gradient(x0, y1),
|
|
get_gradient(x1, y1),
|
|
},
|
|
{
|
|
Vec2.new(x0f + 0.0, y0f + 0.0),
|
|
Vec2.new(x0f + 1.0, y0f + 0.0),
|
|
Vec2.new(x0f + 0.0, y0f + 1.0),
|
|
Vec2.new(x0f + 1.0, y0f + 1.0),
|
|
},
|
|
}
|
|
end
|
|
|
|
def get(x, y)
|
|
x = x.to_f / @frequency
|
|
y = y.to_f / @frequency
|
|
|
|
p = Vec2.new(x, y)
|
|
gradients, origins = get_gradients(x, y)
|
|
v0 = gradient(origins[0], gradients[0], p)
|
|
v1 = gradient(origins[1], gradients[1], p)
|
|
v2 = gradient(origins[2], gradients[2], p)
|
|
v3 = gradient(origins[3], gradients[3], p)
|
|
fx = smooth(x - origins[0].x)
|
|
vx0 = lerp(v0, v1, fx)
|
|
vx1 = lerp(v2, v3, fx)
|
|
fy = smooth(y - origins[0].y)
|
|
|
|
lerp(vx0, vx1, fy) * @amplitude.to_f
|
|
end
|
|
end
|
|
|
|
|
|
@storage = Array(Octave).new
|
|
def initialize
|
|
end
|
|
def initialize(a, f)
|
|
add_octave a, f
|
|
end
|
|
|
|
def add_octave(a, f)
|
|
@storage << Octave.new a, f
|
|
end
|
|
|
|
def get(x, y)
|
|
@storage.reduce 0 { |a, e| a + e.get(x, y) }
|
|
end
|
|
end
|
|
|