Home programs math games animations 3D art ASCII art

Perlin noise


import numpy
import PIL
import math

img = []
grid = []
size = (50,50)
res = None

class Pixel:
    def __init__(self, x, y, color=(0,0,0)):
        self.x = x
        self.y = y
        self.color = color

class Node:
    def __init__(self,x,y):
        self.x=x
        self.y=y

class Vector2: # a*b <- a dot b 
    def __init__(self,x,y):
        self.x = x
        self.y = y
        self.length = math.sqrt(self.x**2+self.y**2)
    def dot_product(self, other):
        return (self.x*other.x + self.y * other.y)
    def __str__(self):
        return f"Vector2(x: {self.x:.3f}, y: {self.y:.3f})"
    def __mul__(self, other):
        if type(other)!=type(self):
            raise TypeError("Dot operand is not a vector")
        return (self.x*other.x + self.y * other.y)
 

def generateRandomUnitVector2(x,y, seed=0):
    if seed == 0:
        hash_code = abs(hash(f"{x},{y}"))
    else:
        hash_code = abs(hash(f"{x},{y},{seed}"))
    hash_length = len(str(hash_code))
    truncatedHash = round(hash_code/int("1"+(hash_length-3)*"0"))/1000
    angle=math.pi*2*truncatedHash
    return Vector2(math.sin(angle),math.cos(angle))



def initialize(Size, Res):
    global grid
    global size
    global res
    size = Size
    res = Res
    for y in range(size[1]*Res):
        row = []
        for x in range(size[0]*Res):
            row.append(Pixel(x,y))
        img.append(row)
    for y in range(size[1]):
        row = []
        for x in range(size[0]):
            row.append(0)
        grid.append(row)
    
def interpolate(a0, a1, w):
    return (a1-a0)*(3-w*2)*w*w+a0

def dotGridGradient(ix, iy, x, y, seed = 0):
    gradient = generateRandomUnitVector2(ix, iy, seed)
    dx = x - ix
    dy = y - iy
    distance = Vector2(dx, dy)
    return gradient*distance

def perlin(x, y, seed=0):
    x0 = int(x)
    y0 = int(y)
    x1 = x0 + 1
    y1 = y0 + 1
    sx = x - x0
    sy = y - y0
    n0 = dotGridGradient(x0, y0, x, y, seed)
    n1 = dotGridGradient(x1, y0, x, y, seed)
    ix0 = interpolate(n0,n1, sx)
    n0 = dotGridGradient(x0, y1, x, y, seed)
    n1 = dotGridGradient(x1, y1, x, y, seed)
    ix1 = interpolate(n0,n1, sx)
    value = interpolate(ix0, ix1, sy)
    return value

def makeTexture(numOfOctaves=1, seed=0):
    ampl = 1
    freq = 1
    for o in range(numOfOctaves):
        for y in range(size[1]*res):
            for x in range(size[0]*res):
                value = ampl*(perlin((x/res)*freq, (y/res)*freq, seed)+1)/2
                img[y][x].color = (min(img[y][x].color[0]+value, 1), min(img[y][x].color[1]+value, 1), min(img[y][x].color[2]+value, 1))
        ampl/=4
        freq*=2

def makePicture():
    data = numpy.zeros((size[0]*res,size[1]*res, 3), dtype=numpy.uint8)
    for y in range(size[1]*res):
        for x in range(size[0]*res):
            data[y, x] = [255*img[y][x].color[0], 255*img[y][x].color[1], 255*img[y][x].color[2]]
    image = PIL.Image.fromarray(data)
    image.show()
    
initialize((10,10), 40)

makeTexture(7, 12)
makePicture()