-
-
Save Dioarya/6ab5eea400317be20670a1d984ac1aee to your computer and use it in GitHub Desktop.
Perlin noise in Python
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
from itertools import product | |
import math | |
import random | |
def smoothstep(t): | |
return t * t * (3. - 2. * t) | |
def lerp(t, a, b): | |
return a + t * (b - a) | |
class PerlinNoiseFactory(object): | |
def __init__(self, dimension, octaves=1, tile=(), unbias=False): | |
self.dimension = dimension | |
self.octaves = octaves | |
self.tile = tile + (0,) * dimension | |
self.unbias = unbias | |
self.scale_factor = 2 * dimension ** -0.5 | |
self.gradient = {} | |
def _generate_gradient(self): | |
if self.dimension == 1: | |
return (random.uniform(-1, 1),) | |
random_point = [random.gauss(0, 1) for _ in range(self.dimension)] | |
scale = sum(n * n for n in random_point) ** -0.5 | |
return tuple(coord * scale for coord in random_point) | |
def get_plain_noise(self, *point): | |
"""Get plain noise for a single point, without taking into account | |
either octaves or tiling. | |
""" | |
if len(point) != self.dimension: | |
raise ValueError("Expected {} values, got {}".format( | |
self.dimension, len(point))) | |
grid_coords = [] | |
for coord in point: | |
min_coord = math.floor(coord) | |
max_coord = min_coord + 1 | |
grid_coords.append((min_coord, max_coord)) | |
dots = [] | |
for grid_point in product(*grid_coords): | |
if grid_point not in self.gradient: | |
self.gradient[grid_point] = self._generate_gradient() | |
gradient = self.gradient[grid_point] | |
dot = 0 | |
for i in range(self.dimension): | |
dot += gradient[i] * (point[i] - grid_point[i]) | |
dots.append(dot) | |
dim = self.dimension | |
while len(dots) > 1: | |
dim -= 1 | |
s = smoothstep(point[dim] - grid_coords[dim][0]) | |
next_dots = [] | |
while dots: | |
next_dots.append(lerp(s, dots.pop(0), dots.pop(0))) | |
dots = next_dots | |
return dots[0] * self.scale_factor | |
def __call__(self, *point): | |
ret = 0 | |
for o in range(self.octaves): | |
o2 = 1 << o | |
new_point = [] | |
for i, coord in enumerate(point): | |
coord *= o2 | |
if self.tile[i]: | |
coord %= self.tile[i] * o2 | |
new_point.append(coord) | |
ret += self.get_plain_noise(*new_point) / o2 | |
ret /= 2 - 2 ** (1 - self.octaves) | |
if self.unbias: | |
r = (ret + 1) / 2 | |
for _ in range(int(self.octaves / 2 + 0.5)): | |
r = smoothstep(r) | |
ret = r * 2 - 1 | |
return ret |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment