From 10fe6b1049fbc60cfb57fff5456cf404e93b88fd Mon Sep 17 00:00:00 2001 From: Elliot Hallmark Date: Sat, 10 Feb 2024 15:37:06 -0600 Subject: [PATCH] best practice: use local RNG --- README.md | 4 ++++ perlin_numpy/perlin2d.py | 20 ++++++++++++++++---- perlin_numpy/perlin3d.py | 26 ++++++++++++++++++++------ 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3f80611..d823a7e 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ The function `generate_perlin_noise_2d` generates a 2D texture of perlin noise. * `shape`: shape of the generated array (tuple of 2 ints) * `res`: number of periods of noise to generate along each axis (tuple of 2 ints) * `tileable`: if the noise should be tileable along each axis (tuple of 2 bools) +* seed : seed for internal RNG if desired. if None use global RNG Note: `shape` must be a multiple of `res` @@ -43,6 +44,7 @@ The function `generate_fractal_noise_2d` combines several octaves of 2D perlin n * `persistence`: scaling factor between two octaves (float) * `lacunarity`: frequency factor between two octaves (float) * `tileable`: if the noise should be tileable along each axis (tuple of 2 bools) +* seed : seed for internal RNG if desired. if None use global RNG Note: `shape` must be a multiple of `lacunarity^(octaves-1)*res` @@ -54,6 +56,7 @@ The function `generate_perlin_noise_3d` generates a 3D texture of perlin noise. * `shape`: shape of the generated array (tuple of 3 ints) * `res`: number of periods of noise to generate along each axis (tuple of 3 ints) * `tileable`: if the noise should be tileable along each axis (tuple of 3 bools) +* seed : seed for internal RNG if desired. if None use global RNG Note: `shape` must be a multiple of `res` @@ -65,6 +68,7 @@ The function `generate_fractal_noise_2d` combines several octaves of 3D perlin n * `persistence`: scaling factor between two octaves (float) * `lacunarity`: frequency factor between two octaves (float) * `tileable`: if the noise should be tileable along each axis (tuple of 3 bools) +* seed : seed for internal RNG if desired. if None use global RNG Note: `shape` must be a multiple of `lacunarity^(octaves-1)*res` diff --git a/perlin_numpy/perlin2d.py b/perlin_numpy/perlin2d.py index 2dde627..4126499 100644 --- a/perlin_numpy/perlin2d.py +++ b/perlin_numpy/perlin2d.py @@ -6,7 +6,7 @@ def interpolant(t): 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. @@ -20,6 +20,8 @@ def generate_perlin_noise_2d( (tuple of two bools). Defaults to (False, False). interpolant: The interpolation function, defaults to t*t*t*(t*(t*6 - 15) + 10). + seed: if not None, use an internal RNG with seed=seed, + otherwise use numpy global RNG Returns: A numpy array of shape shape with the generated noise. @@ -27,12 +29,17 @@ def generate_perlin_noise_2d( Raises: ValueError: If shape is not a multiple of res. """ + if seed is not None: + rng = np.random.default_rng(seed) + else: + rng = np.random + 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 # Gradients - angles = 2*np.pi*np.random.rand(res[0]+1, res[1]+1) + angles = 2*np.pi*rng.uniform(size=(res[0]+1, res[1]+1)) gradients = np.dstack((np.cos(angles), np.sin(angles))) if tileable[0]: gradients[-1,:] = gradients[0,:] @@ -87,9 +94,14 @@ def generate_fractal_noise_2d( noise = np.zeros(shape) frequency = 1 amplitude = 1 - for _ in range(octaves): + if seed is None: + seeds = [None] * octaves + else: + rng = np.random.default_rng(seed) + seeds = list(rng.uniform(size=(octaves,))) + for S in seeds: noise += amplitude * generate_perlin_noise_2d( - shape, (frequency*res[0], frequency*res[1]), tileable, interpolant + shape, (frequency*res[0], frequency*res[1]), tileable, interpolant, seed=seed ) frequency *= lacunarity amplitude *= persistence diff --git a/perlin_numpy/perlin3d.py b/perlin_numpy/perlin3d.py index 5df701a..a3e58ae 100644 --- a/perlin_numpy/perlin3d.py +++ b/perlin_numpy/perlin3d.py @@ -5,7 +5,7 @@ def generate_perlin_noise_3d( shape, res, tileable=(False, False, False), - interpolant=interpolant + interpolant=interpolant, seed=None ): """Generate a 3D numpy array of perlin noise. @@ -19,6 +19,8 @@ def generate_perlin_noise_3d( (tuple of three bools). Defaults to (False, False, False). interpolant: The interpolation function, defaults to t*t*t*(t*(t*6 - 15) + 10). + seed: if not None, use an internal RNG with seed=seed, + otherwise use numpy global RNG Returns: A numpy array of shape shape with the generated noise. @@ -26,14 +28,19 @@ def generate_perlin_noise_3d( Raises: ValueError: If shape is not a multiple of res. """ + if seed is not None: + rng = np.random.default_rng(seed) + else: + rng = np.random + 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 = 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) + theta = 2*np.pi*rng.uniform(size=(res[0] + 1, res[1] + 1, res[2] + 1)) + phi = 2*np.pi*rng.uniform(size=(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 @@ -75,7 +82,8 @@ def generate_perlin_noise_3d( def generate_fractal_noise_3d( shape, res, octaves=1, persistence=0.5, lacunarity=2, - tileable=(False, False, False), interpolant=interpolant + tileable=(False, False, False), interpolant=interpolant, + seed = None ): """Generate a 3D numpy array of fractal noise. @@ -104,12 +112,18 @@ def generate_fractal_noise_3d( noise = np.zeros(shape) frequency = 1 amplitude = 1 - for _ in range(octaves): + if seed is None: + seeds = [None] * octaves + else: + rng = np.random.default_rng(seed) + seeds = list(rng.uniform(size=(octaves,))) + for S in seeds: noise += amplitude * generate_perlin_noise_3d( shape, (frequency*res[0], frequency*res[1], frequency*res[2]), tileable, - interpolant + interpolant, + seed=S ) frequency *= lacunarity amplitude *= persistence