diff --git a/perlin_numpy/perlin2d.py b/perlin_numpy/perlin2d.py index 2dde627..f49e678 100644 --- a/perlin_numpy/perlin2d.py +++ b/perlin_numpy/perlin2d.py @@ -1,12 +1,23 @@ import numpy as np +from warnings import warn + def interpolant(t): - return t*t*t*(t*(t*6 - 15) + 10) + return t * t * t * (t * (t * 6 - 15) + 10) + + +def check_res(res, shape): + for dim, dim_res in enumerate(res): + assert ( + shape[dim] % dim_res == 0 + ), f"Dimension {dim} has a length of {shape[dim]} which can not be divided using the resolution specified: {dim_res}" + + return def generate_perlin_noise_2d( - shape, res, tileable=(False, False), interpolant=interpolant + shape, res, tileable=(False, False), interpolant=interpolant, seed=None ): """Generate a 2D numpy array of perlin noise. @@ -27,38 +38,57 @@ def generate_perlin_noise_2d( Raises: ValueError: If shape is not a multiple of res. """ + check_res(res, shape) + delta = (res[0] / shape[0], res[1] / shape[1]) d = (shape[0] // res[0], shape[1] // res[1]) - grid = np.mgrid[0:res[0]:delta[0], 0:res[1]:delta[1]]\ - .transpose(1, 2, 0) % 1 + + grid = np.mgrid[0 : res[0] : delta[0], 0 : res[1] : delta[1]].transpose(1, 2, 0) % 1 + # Gradients - angles = 2*np.pi*np.random.rand(res[0]+1, res[1]+1) + if seed is not None: + np.random.seed(seed) + + angles = 2 * np.pi * np.random.rand(res[0] + 1, res[1] + 1) gradients = np.dstack((np.cos(angles), np.sin(angles))) + if tileable[0]: - gradients[-1,:] = gradients[0,:] + gradients[-1, :] = gradients[0, :] if tileable[1]: - gradients[:,-1] = gradients[:,0] + gradients[:, -1] = gradients[:, 0] + gradients = gradients.repeat(d[0], 0).repeat(d[1], 1) - g00 = gradients[ :-d[0], :-d[1]] - g10 = gradients[d[0]: , :-d[1]] - g01 = gradients[ :-d[0],d[1]: ] - g11 = gradients[d[0]: ,d[1]: ] + + g00 = gradients[: -d[0], : -d[1]] + g10 = gradients[d[0] :, : -d[1]] + g01 = gradients[: -d[0], d[1] :] + g11 = gradients[d[0] :, d[1] :] + # Ramps - n00 = np.sum(np.dstack((grid[:,:,0] , grid[:,:,1] )) * g00, 2) - n10 = np.sum(np.dstack((grid[:,:,0]-1, grid[:,:,1] )) * g10, 2) - n01 = np.sum(np.dstack((grid[:,:,0] , grid[:,:,1]-1)) * g01, 2) - n11 = np.sum(np.dstack((grid[:,:,0]-1, grid[:,:,1]-1)) * g11, 2) + n00 = np.sum(np.dstack((grid[:, :, 0], grid[:, :, 1])) * g00, 2) + n10 = np.sum(np.dstack((grid[:, :, 0] - 1, grid[:, :, 1])) * g10, 2) + n01 = np.sum(np.dstack((grid[:, :, 0], grid[:, :, 1] - 1)) * g01, 2) + n11 = np.sum(np.dstack((grid[:, :, 0] - 1, grid[:, :, 1] - 1)) * g11, 2) + # Interpolation t = interpolant(grid) - n0 = n00*(1-t[:,:,0]) + t[:,:,0]*n10 - n1 = n01*(1-t[:,:,0]) + t[:,:,0]*n11 - return np.sqrt(2)*((1-t[:,:,1])*n0 + t[:,:,1]*n1) + n0 = n00 * (1 - t[:, :, 0]) + t[:, :, 0] * n10 + n1 = n01 * (1 - t[:, :, 0]) + t[:, :, 0] * n11 + + noise = np.sqrt(2) * ((1 - t[:, :, 1]) * n0 + t[:, :, 1] * n1) + + return noise def generate_fractal_noise_2d( - shape, res, octaves=1, persistence=0.5, - lacunarity=2, tileable=(False, False), - interpolant=interpolant + shape, + res, + octaves=1, + persistence=0.5, + lacunarity=2, + tileable=(False, False), + interpolant=interpolant, + seed=None, ): """Generate a 2D numpy array of fractal noise. @@ -87,10 +117,14 @@ def generate_fractal_noise_2d( noise = np.zeros(shape) frequency = 1 amplitude = 1 + for _ in range(octaves): - noise += amplitude * generate_perlin_noise_2d( - shape, (frequency*res[0], frequency*res[1]), tileable, interpolant + octave_noise = generate_perlin_noise_2d( + shape, (frequency * res[0], frequency * res[1]), tileable, interpolant, seed ) + + noise += amplitude * octave_noise frequency *= lacunarity amplitude *= persistence + return noise diff --git a/perlin_numpy/perlin3d.py b/perlin_numpy/perlin3d.py index 5df701a..da224c2 100644 --- a/perlin_numpy/perlin3d.py +++ b/perlin_numpy/perlin3d.py @@ -1,11 +1,10 @@ import numpy as np -from .perlin2d import interpolant +from .perlin2d import interpolant, check_res def generate_perlin_noise_3d( - shape, res, tileable=(False, False, False), - interpolant=interpolant + shape, res, tileable=(False, False, False), interpolant=interpolant, seed=None ): """Generate a 3D numpy array of perlin noise. @@ -26,56 +25,110 @@ def generate_perlin_noise_3d( Raises: ValueError: If shape is not a multiple of res. """ + check_res(res, shape) + delta = (res[0] / shape[0], res[1] / shape[1], res[2] / shape[2]) d = (shape[0] // res[0], shape[1] // res[1], shape[2] // res[2]) - grid = np.mgrid[0:res[0]:delta[0],0:res[1]:delta[1],0:res[2]:delta[2]] - grid = np.mgrid[0:res[0]:delta[0],0:res[1]:delta[1],0:res[2]:delta[2]] + + grid = np.mgrid[0 : res[0] : delta[0], 0 : res[1] : delta[1], 0 : res[2] : delta[2]] + grid = np.mgrid[0 : res[0] : delta[0], 0 : res[1] : delta[1], 0 : res[2] : delta[2]] grid = grid.transpose(1, 2, 3, 0) % 1 + # Gradients - theta = 2*np.pi*np.random.rand(res[0] + 1, res[1] + 1, res[2] + 1) - phi = 2*np.pi*np.random.rand(res[0] + 1, res[1] + 1, res[2] + 1) + if seed is not None: + np.random.seed(seed) + + theta = 2 * np.pi * np.random.rand(res[0] + 1, res[1] + 1, res[2] + 1) + phi = 2 * np.pi * np.random.rand(res[0] + 1, res[1] + 1, res[2] + 1) + gradients = np.stack( - (np.sin(phi)*np.cos(theta), np.sin(phi)*np.sin(theta), np.cos(phi)), - axis=3 + (np.sin(phi) * np.cos(theta), np.sin(phi) * np.sin(theta), np.cos(phi)), axis=3 ) + if tileable[0]: - gradients[-1,:,:] = gradients[0,:,:] + gradients[-1, :, :] = gradients[0, :, :] if tileable[1]: - gradients[:,-1,:] = gradients[:,0,:] + gradients[:, -1, :] = gradients[:, 0, :] if tileable[2]: - gradients[:,:,-1] = gradients[:,:,0] + gradients[:, :, -1] = gradients[:, :, 0] + gradients = gradients.repeat(d[0], 0).repeat(d[1], 1).repeat(d[2], 2) - g000 = gradients[ :-d[0], :-d[1], :-d[2]] - g100 = gradients[d[0]: , :-d[1], :-d[2]] - g010 = gradients[ :-d[0],d[1]: , :-d[2]] - g110 = gradients[d[0]: ,d[1]: , :-d[2]] - g001 = gradients[ :-d[0], :-d[1],d[2]: ] - g101 = gradients[d[0]: , :-d[1],d[2]: ] - g011 = gradients[ :-d[0],d[1]: ,d[2]: ] - g111 = gradients[d[0]: ,d[1]: ,d[2]: ] + + g000 = gradients[: -d[0], : -d[1], : -d[2]] + g100 = gradients[d[0] :, : -d[1], : -d[2]] + g010 = gradients[: -d[0], d[1] :, : -d[2]] + g110 = gradients[d[0] :, d[1] :, : -d[2]] + g001 = gradients[: -d[0], : -d[1], d[2] :] + g101 = gradients[d[0] :, : -d[1], d[2] :] + g011 = gradients[: -d[0], d[1] :, d[2] :] + g111 = gradients[d[0] :, d[1] :, d[2] :] + # Ramps - n000 = np.sum(np.stack((grid[:,:,:,0] , grid[:,:,:,1] , grid[:,:,:,2] ), axis=3) * g000, 3) - n100 = np.sum(np.stack((grid[:,:,:,0]-1, grid[:,:,:,1] , grid[:,:,:,2] ), axis=3) * g100, 3) - n010 = np.sum(np.stack((grid[:,:,:,0] , grid[:,:,:,1]-1, grid[:,:,:,2] ), axis=3) * g010, 3) - n110 = np.sum(np.stack((grid[:,:,:,0]-1, grid[:,:,:,1]-1, grid[:,:,:,2] ), axis=3) * g110, 3) - n001 = np.sum(np.stack((grid[:,:,:,0] , grid[:,:,:,1] , grid[:,:,:,2]-1), axis=3) * g001, 3) - n101 = np.sum(np.stack((grid[:,:,:,0]-1, grid[:,:,:,1] , grid[:,:,:,2]-1), axis=3) * g101, 3) - n011 = np.sum(np.stack((grid[:,:,:,0] , grid[:,:,:,1]-1, grid[:,:,:,2]-1), axis=3) * g011, 3) - n111 = np.sum(np.stack((grid[:,:,:,0]-1, grid[:,:,:,1]-1, grid[:,:,:,2]-1), axis=3) * g111, 3) + n000 = np.sum( + np.stack((grid[:, :, :, 0], grid[:, :, :, 1], grid[:, :, :, 2]), axis=3) * g000, + 3, + ) + n100 = np.sum( + np.stack((grid[:, :, :, 0] - 1, grid[:, :, :, 1], grid[:, :, :, 2]), axis=3) + * g100, + 3, + ) + n010 = np.sum( + np.stack((grid[:, :, :, 0], grid[:, :, :, 1] - 1, grid[:, :, :, 2]), axis=3) + * g010, + 3, + ) + n110 = np.sum( + np.stack((grid[:, :, :, 0] - 1, grid[:, :, :, 1] - 1, grid[:, :, :, 2]), axis=3) + * g110, + 3, + ) + n001 = np.sum( + np.stack((grid[:, :, :, 0], grid[:, :, :, 1], grid[:, :, :, 2] - 1), axis=3) + * g001, + 3, + ) + n101 = np.sum( + np.stack((grid[:, :, :, 0] - 1, grid[:, :, :, 1], grid[:, :, :, 2] - 1), axis=3) + * g101, + 3, + ) + n011 = np.sum( + np.stack((grid[:, :, :, 0], grid[:, :, :, 1] - 1, grid[:, :, :, 2] - 1), axis=3) + * g011, + 3, + ) + n111 = np.sum( + np.stack( + (grid[:, :, :, 0] - 1, grid[:, :, :, 1] - 1, grid[:, :, :, 2] - 1), axis=3 + ) + * g111, + 3, + ) + # Interpolation t = interpolant(grid) - n00 = n000*(1-t[:,:,:,0]) + t[:,:,:,0]*n100 - n10 = n010*(1-t[:,:,:,0]) + t[:,:,:,0]*n110 - n01 = n001*(1-t[:,:,:,0]) + t[:,:,:,0]*n101 - n11 = n011*(1-t[:,:,:,0]) + t[:,:,:,0]*n111 - n0 = (1-t[:,:,:,1])*n00 + t[:,:,:,1]*n10 - n1 = (1-t[:,:,:,1])*n01 + t[:,:,:,1]*n11 - return ((1-t[:,:,:,2])*n0 + t[:,:,:,2]*n1) + n00 = n000 * (1 - t[:, :, :, 0]) + t[:, :, :, 0] * n100 + n10 = n010 * (1 - t[:, :, :, 0]) + t[:, :, :, 0] * n110 + n01 = n001 * (1 - t[:, :, :, 0]) + t[:, :, :, 0] * n101 + n11 = n011 * (1 - t[:, :, :, 0]) + t[:, :, :, 0] * n111 + n0 = (1 - t[:, :, :, 1]) * n00 + t[:, :, :, 1] * n10 + n1 = (1 - t[:, :, :, 1]) * n01 + t[:, :, :, 1] * n11 + + noise = (1 - t[:, :, :, 2]) * n0 + t[:, :, :, 2] * n1 + + return noise def generate_fractal_noise_3d( - shape, res, octaves=1, persistence=0.5, lacunarity=2, - tileable=(False, False, False), interpolant=interpolant + shape, + res, + octaves=1, + persistence=0.5, + lacunarity=2, + tileable=(False, False, False), + interpolant=interpolant, + seed=None, ): """Generate a 3D numpy array of fractal noise. @@ -104,13 +157,18 @@ def generate_fractal_noise_3d( noise = np.zeros(shape) frequency = 1 amplitude = 1 + for _ in range(octaves): - noise += amplitude * generate_perlin_noise_3d( + octave_noise = generate_perlin_noise_3d( shape, - (frequency*res[0], frequency*res[1], frequency*res[2]), + (frequency * res[0], frequency * res[1], frequency * res[2]), tileable, - interpolant + interpolant, + seed, ) + + noise += amplitude * octave_noise frequency *= lacunarity amplitude *= persistence + return noise