naka-chan-dayo/src/naka/noise.cr

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