import math
import numpy as np
from matplotlib import pyplot

def norm(x):
    return x / math.sqrt(np.dot(x, x))

def mknode():
    return norm(np.random.uniform(-1, 1, 2))

def mkgrid(gridsize):
    grid = np.zeros([gridsize, gridsize, 2])
    for y in xrange(gridsize):
        for x in xrange(gridsize):
            grid[y,x,:] = mknode()
    return grid

rotstep = 0.2
rotate = np.array([[math.cos(rotstep), math.sin(rotstep)],
                   [-math.sin(rotstep), math.cos(rotstep)]])

def modgrid(grid):
    gridh, gridw, dim = grid.shape
    assert dim==2
    for y in xrange(gridh):
        for x in xrange(gridw):
            grid[y,x,:] = np.dot(rotate, grid[y,x,:])

def ease(p):
    return 3 * p * p - 2 * p * p * p

def sample(x, y, grid):
    gridw, gridh, dim = grid.shape
    assert dim==2
    
    ix = math.floor(x)
    iy = math.floor(y)

    dx = x - ix
    dy = y - iy

    s = np.dot(np.array([dx, dy]), grid[ix%gridw,iy%gridh])
    t = np.dot(np.array([dx-1, dy]), grid[(ix+1)%gridw,iy%gridh])
    u = np.dot(np.array([dx, dy-1]), grid[ix%gridw,(iy+1)%gridh])
    v = np.dot(np.array([dx-1, dy-1]), grid[(ix+1)%gridw,(iy+1)%gridh])

    easex = ease(dx)
    a = s + easex * (t - s)
    b = u + easex * (v - u)
    return a + ease(dy) * (b - a)
#    return s + dx * (t - s) + u + dy * (v - u)
           
def frame(grid, reso, squares):
    """Parameters: Grid, resolution (pixels / square), shown square count"""
    canvas = np.zeros([squares * reso, squares * reso])
    for y in xrange(squares * reso):
        for x in xrange(squares * reso):
            canvas[y,x] = sample(float(x) / reso, float(y) / reso, grid)
    return canvas


def cloudframe(grid, reso, squares):
    """Parameters: Grid, resolution (pixels / square), shown square count
    which should be less than 1/log2(grid size) to avoid artifacts"""
    canvas = np.zeros([squares * reso, squares * reso])
    for y in xrange(squares * reso):
        for x in xrange(squares * reso):
            fac = 1.0
            n = 1
            while n < reso:
                canvas[y,x] += fac * sample(float(x * n) / reso,
                                            float(y * n) / reso, grid)
                fac /= 2
                n *= 2
    return canvas
    

def showframe(grid, reso, squares):
    f = frame(grid, reso, squares)
    pyplot.imshow(f)
    pyplot.hot()
    pyplot.show()

def showcloud(grid, reso, squares):
    f = cloudframe(grid, reso, squares)
    pyplot.imshow(f)
    pyplot.hot()
    pyplot.show()

if __name__ == '__main__':
    grid = mkgrid(8)
    while(True):
        modgrid(grid)
        # showframe(grid, 32, 2)
        showcloud(grid, 32, 2)
