Skip to content

Instantly share code, notes, and snippets.

@Dioarya
Forked from eevee/perlin.py
Last active March 6, 2022 20:44
Show Gist options
  • Save Dioarya/6ab5eea400317be20670a1d984ac1aee to your computer and use it in GitHub Desktop.
Save Dioarya/6ab5eea400317be20670a1d984ac1aee to your computer and use it in GitHub Desktop.
Perlin noise in Python
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