shades.noise_fields
noise_fields
Functions and classes relating to Shades' NoiseField
1""" 2noise_fields 3 4Functions and classes relating to Shades' NoiseField 5""" 6import random 7import math 8 9from typing import List, Tuple, Union 10 11import numpy as np 12 13from numpy.typing import ArrayLike 14 15 16 17class NoiseField(): 18 """ 19 An object to calculate and store perlin noise data. 20 21 Initialisation takes float (recommend very low number < 0.1) 22 and random seed 23 """ 24 25 def __init__(self, scale: float = 0.002, seed: int = None) -> None: 26 if seed is None: 27 self.seed = random.randint(0,9999) 28 else: 29 self.seed = seed 30 self.scale = scale 31 size = 10 32 self.x_lin = np.linspace(0, (size*self.scale), size, endpoint=False) 33 self.y_lin = np.linspace(0, (size*self.scale), size, endpoint=False) 34 self.field = self.perlin_field(self.x_lin, self.y_lin) 35 self.x_negative_buffer = 0 36 self.y_negative_buffer = 0 37 self.buffer_chunks = 500 38 39 40 def _roundup(self, to_round: float, nearest_n: float) -> float: 41 """ 42 Internal function to round up number to_round to nearest_n 43 """ 44 return int(math.ceil(to_round / nearest_n)) * nearest_n 45 46 47 def buffer_field_right(self, to_extend: int) -> None: 48 """ 49 Extends object's noise field right 50 """ 51 # y is just gonna stay the same, but x needs to be picking up 52 max_lin = self.x_lin[-1] 53 54 additional_x_lin = np.linspace( 55 max_lin+self.scale, 56 max_lin+(to_extend*self.scale), 57 to_extend, 58 endpoint=False, 59 ) 60 self.field = np.concatenate( 61 [self.field, 62 self.perlin_field(additional_x_lin, self.y_lin)], 63 axis=1, 64 ) 65 self.x_lin = np.concatenate([self.x_lin, additional_x_lin]) 66 67 68 def buffer_field_bottom(self, to_extend: int) -> None: 69 """ 70 Extends object's noise field downwards 71 """ 72 max_lin = self.y_lin[-1] 73 additional_y_lin = np.linspace( 74 max_lin+self.scale, 75 max_lin+(to_extend*self.scale), 76 to_extend, 77 endpoint=False, 78 ) 79 self.field = np.concatenate( 80 [self.field, 81 self.perlin_field(self.x_lin, additional_y_lin)], 82 axis=0, 83 ) 84 self.y_lin = np.concatenate([self.y_lin, additional_y_lin]) 85 86 87 def buffer_field_left(self, to_extend: int) -> None: 88 """ 89 Extends object's noise field left 90 """ 91 min_lin = self.x_lin[0] 92 additional_x_lin = np.linspace( 93 min_lin-(to_extend*self.scale), 94 min_lin, 95 to_extend, 96 endpoint=False, 97 ) 98 self.field = np.concatenate( 99 [self.perlin_field(additional_x_lin, self.y_lin), 100 self.field], 101 axis=1, 102 ) 103 self.x_lin = np.concatenate([additional_x_lin, self.x_lin]) 104 self.x_negative_buffer += to_extend 105 106 107 def buffer_field_top(self, to_extend: int) -> None: 108 """ 109 Extends object's noise field upwards 110 """ 111 min_lin = self.y_lin[0] 112 additional_y_lin = np.linspace( 113 min_lin-(to_extend*self.scale), 114 min_lin, 115 to_extend, 116 endpoint=False, 117 ) 118 self.field = np.concatenate( 119 [self.perlin_field(self.x_lin, additional_y_lin), 120 self.field], 121 axis=0, 122 ) 123 self.y_lin = np.concatenate([additional_y_lin, self.y_lin]) 124 self.y_negative_buffer += to_extend 125 126 def perlin_field(self, x_lin: List[float], y_lin: List[float]) -> ArrayLike: 127 """ 128 generate field from x and y linear points 129 130 credit to tgirod for stack overflow on numpy perlin noise (most of this code from answer) 131 https://stackoverflow.com/questions/42147776/producing-2d-perlin-noise-with-numpy 132 """ 133 # remembering the random state (so we can put it back after) 134 initial_random_state = np.random.get_state() 135 x_grid, y_grid = np.meshgrid(x_lin, y_lin) 136 x_grid %= 512 137 y_grid %= 512 138 # permutation table 139 np.random.seed(self.seed) 140 field_256 = np.arange(256, dtype=int) 141 np.random.shuffle(field_256) 142 field_256 = np.stack([field_256, field_256]).flatten() 143 # coordinates of the top-left 144 x_i, y_i = x_grid.astype(int), y_grid.astype(int) 145 # internal coordinates 146 x_f, y_f = x_grid - x_i, y_grid - y_i 147 # fade factors 148 u_array, v_array = self.fade(x_f), self.fade(y_f) 149 # noise components 150 n00 = self.gradient(field_256[(field_256[x_i%512] + y_i)%512], x_f, y_f) 151 n01 = self.gradient(field_256[(field_256[x_i%512] + y_i + 1)%512], x_f, y_f - 1) 152 n11 = self.gradient( 153 field_256[(field_256[((x_i%512)+1)%512] + y_i + 1)%512], x_f - 1, y_f - 1) 154 n10 = self.gradient(field_256[(field_256[((x_i%512)+1)%512] + y_i)%512], x_f - 1, y_f) 155 # combine noises 156 x_1 = self.lerp(n00, n10, u_array) 157 x_2 = self.lerp(n01, n11, u_array) 158 # putting the random state back in place 159 np.random.set_state(initial_random_state) 160 field = self.lerp(x_1, x_2, v_array) 161 field += 0.5 162 return field 163 164 165 def lerp(self, a_array: ArrayLike, b_array: ArrayLike, x_array: ArrayLike) -> ArrayLike: 166 "linear interpolation" 167 return a_array + x_array * (b_array - a_array) 168 169 170 def fade(self, t_array: ArrayLike) -> ArrayLike: 171 "6t^5 - 15t^4 + 10t^3" 172 return 6 * t_array**5 - 15 * t_array**4 + 10 * t_array**3 173 174 175 def gradient(self, h_array: ArrayLike, x_array: ArrayLike, y_array: ArrayLike) -> ArrayLike: 176 "grad converts h to the right gradient vector and return the dot product with (x,y)" 177 vectors = np.array([[0, 1], [0, -1], [1, 0], [-1, 0]]) 178 g_array = vectors[h_array % 4] 179 return g_array[:, :, 0] * x_array + g_array[:, :, 1] * y_array 180 181 182 def noise(self, xy_coords: Tuple[int, int]): 183 """ 184 Returns noise of xy coords 185 Also manages noise_field (will dynamically recalcuate as needed) 186 """ 187 if self.scale == 0: 188 return 0 189 x_coord, y_coord = xy_coords 190 x_coord += self.x_negative_buffer 191 y_coord += self.y_negative_buffer 192 if x_coord < 0: 193 # x negative buffer needs to be increased 194 x_to_backfill = self._roundup(abs(x_coord), self.buffer_chunks) 195 self.buffer_field_left(x_to_backfill) 196 x_coord, y_coord = xy_coords 197 x_coord += self.x_negative_buffer 198 y_coord += self.y_negative_buffer 199 if y_coord < 0: 200 # y negative buffer needs to be increased 201 y_to_backfill = self._roundup(abs(y_coord), self.buffer_chunks) 202 self.buffer_field_top(y_to_backfill) 203 x_coord, y_coord = xy_coords 204 x_coord += self.x_negative_buffer 205 y_coord += self.y_negative_buffer 206 try: 207 return self.field[int(y_coord)][int(x_coord)] 208 except IndexError: 209 # ran out of generated noise, so need to extend the field 210 height, width = self.field.shape 211 x_to_extend = x_coord - width + 1 212 y_to_extend = y_coord - height + 1 213 if x_to_extend > 0: 214 x_to_extend = self._roundup(x_to_extend, self.buffer_chunks) 215 self.buffer_field_right(x_to_extend) 216 if y_to_extend > 0: 217 y_to_extend = self._roundup(y_to_extend, self.buffer_chunks) 218 self.buffer_field_bottom(y_to_extend) 219 return self.noise(xy_coords) 220 221 222 def recursive_noise( 223 self, 224 xy_coords: Tuple[int, int], 225 depth: int = 1, 226 feedback: float = 0.7, 227 ) -> float: 228 """Returns domain warped recursive perlin noise 229 (number between 0 and 1 from xy coordinates) 230 Recomended feedback (which determines affect of recursive call) around 0-2 231 """ 232 # base case 233 if depth <= 0: 234 return self.noise(xy_coords) 235 # else recur with noise of noise 236 noise_from_xy_coords = int(self.noise(xy_coords) * feedback * 100) 237 return self.recursive_noise( 238 (noise_from_xy_coords, noise_from_xy_coords), 239 depth-1, 240 feedback, 241 ) 242 243 244 245def noise_fields( 246 scale: Union[List[float], float] = 0.002, 247 seed: Union[List[int], int] = None, 248 channels: int = 3, 249 ) -> List[NoiseField]: 250 """ 251 Create multiple NoiseField objects in one go. 252 This is a quality of life function, rather than adding new behaviour 253 shades.noise_fields(scale=0.2, channels=3) rather than 254 [shades.NoiseField(scale=0.2) for i in range(3)] 255 """ 256 257 if not isinstance(scale, list): 258 scale = [scale for i in range(channels)] 259 if not isinstance(seed, list): 260 seed = [seed for i in range(channels)] 261 262 return [NoiseField(scale=scale[i], seed=seed[i]) for i in range(channels)]
18class NoiseField(): 19 """ 20 An object to calculate and store perlin noise data. 21 22 Initialisation takes float (recommend very low number < 0.1) 23 and random seed 24 """ 25 26 def __init__(self, scale: float = 0.002, seed: int = None) -> None: 27 if seed is None: 28 self.seed = random.randint(0,9999) 29 else: 30 self.seed = seed 31 self.scale = scale 32 size = 10 33 self.x_lin = np.linspace(0, (size*self.scale), size, endpoint=False) 34 self.y_lin = np.linspace(0, (size*self.scale), size, endpoint=False) 35 self.field = self.perlin_field(self.x_lin, self.y_lin) 36 self.x_negative_buffer = 0 37 self.y_negative_buffer = 0 38 self.buffer_chunks = 500 39 40 41 def _roundup(self, to_round: float, nearest_n: float) -> float: 42 """ 43 Internal function to round up number to_round to nearest_n 44 """ 45 return int(math.ceil(to_round / nearest_n)) * nearest_n 46 47 48 def buffer_field_right(self, to_extend: int) -> None: 49 """ 50 Extends object's noise field right 51 """ 52 # y is just gonna stay the same, but x needs to be picking up 53 max_lin = self.x_lin[-1] 54 55 additional_x_lin = np.linspace( 56 max_lin+self.scale, 57 max_lin+(to_extend*self.scale), 58 to_extend, 59 endpoint=False, 60 ) 61 self.field = np.concatenate( 62 [self.field, 63 self.perlin_field(additional_x_lin, self.y_lin)], 64 axis=1, 65 ) 66 self.x_lin = np.concatenate([self.x_lin, additional_x_lin]) 67 68 69 def buffer_field_bottom(self, to_extend: int) -> None: 70 """ 71 Extends object's noise field downwards 72 """ 73 max_lin = self.y_lin[-1] 74 additional_y_lin = np.linspace( 75 max_lin+self.scale, 76 max_lin+(to_extend*self.scale), 77 to_extend, 78 endpoint=False, 79 ) 80 self.field = np.concatenate( 81 [self.field, 82 self.perlin_field(self.x_lin, additional_y_lin)], 83 axis=0, 84 ) 85 self.y_lin = np.concatenate([self.y_lin, additional_y_lin]) 86 87 88 def buffer_field_left(self, to_extend: int) -> None: 89 """ 90 Extends object's noise field left 91 """ 92 min_lin = self.x_lin[0] 93 additional_x_lin = np.linspace( 94 min_lin-(to_extend*self.scale), 95 min_lin, 96 to_extend, 97 endpoint=False, 98 ) 99 self.field = np.concatenate( 100 [self.perlin_field(additional_x_lin, self.y_lin), 101 self.field], 102 axis=1, 103 ) 104 self.x_lin = np.concatenate([additional_x_lin, self.x_lin]) 105 self.x_negative_buffer += to_extend 106 107 108 def buffer_field_top(self, to_extend: int) -> None: 109 """ 110 Extends object's noise field upwards 111 """ 112 min_lin = self.y_lin[0] 113 additional_y_lin = np.linspace( 114 min_lin-(to_extend*self.scale), 115 min_lin, 116 to_extend, 117 endpoint=False, 118 ) 119 self.field = np.concatenate( 120 [self.perlin_field(self.x_lin, additional_y_lin), 121 self.field], 122 axis=0, 123 ) 124 self.y_lin = np.concatenate([additional_y_lin, self.y_lin]) 125 self.y_negative_buffer += to_extend 126 127 def perlin_field(self, x_lin: List[float], y_lin: List[float]) -> ArrayLike: 128 """ 129 generate field from x and y linear points 130 131 credit to tgirod for stack overflow on numpy perlin noise (most of this code from answer) 132 https://stackoverflow.com/questions/42147776/producing-2d-perlin-noise-with-numpy 133 """ 134 # remembering the random state (so we can put it back after) 135 initial_random_state = np.random.get_state() 136 x_grid, y_grid = np.meshgrid(x_lin, y_lin) 137 x_grid %= 512 138 y_grid %= 512 139 # permutation table 140 np.random.seed(self.seed) 141 field_256 = np.arange(256, dtype=int) 142 np.random.shuffle(field_256) 143 field_256 = np.stack([field_256, field_256]).flatten() 144 # coordinates of the top-left 145 x_i, y_i = x_grid.astype(int), y_grid.astype(int) 146 # internal coordinates 147 x_f, y_f = x_grid - x_i, y_grid - y_i 148 # fade factors 149 u_array, v_array = self.fade(x_f), self.fade(y_f) 150 # noise components 151 n00 = self.gradient(field_256[(field_256[x_i%512] + y_i)%512], x_f, y_f) 152 n01 = self.gradient(field_256[(field_256[x_i%512] + y_i + 1)%512], x_f, y_f - 1) 153 n11 = self.gradient( 154 field_256[(field_256[((x_i%512)+1)%512] + y_i + 1)%512], x_f - 1, y_f - 1) 155 n10 = self.gradient(field_256[(field_256[((x_i%512)+1)%512] + y_i)%512], x_f - 1, y_f) 156 # combine noises 157 x_1 = self.lerp(n00, n10, u_array) 158 x_2 = self.lerp(n01, n11, u_array) 159 # putting the random state back in place 160 np.random.set_state(initial_random_state) 161 field = self.lerp(x_1, x_2, v_array) 162 field += 0.5 163 return field 164 165 166 def lerp(self, a_array: ArrayLike, b_array: ArrayLike, x_array: ArrayLike) -> ArrayLike: 167 "linear interpolation" 168 return a_array + x_array * (b_array - a_array) 169 170 171 def fade(self, t_array: ArrayLike) -> ArrayLike: 172 "6t^5 - 15t^4 + 10t^3" 173 return 6 * t_array**5 - 15 * t_array**4 + 10 * t_array**3 174 175 176 def gradient(self, h_array: ArrayLike, x_array: ArrayLike, y_array: ArrayLike) -> ArrayLike: 177 "grad converts h to the right gradient vector and return the dot product with (x,y)" 178 vectors = np.array([[0, 1], [0, -1], [1, 0], [-1, 0]]) 179 g_array = vectors[h_array % 4] 180 return g_array[:, :, 0] * x_array + g_array[:, :, 1] * y_array 181 182 183 def noise(self, xy_coords: Tuple[int, int]): 184 """ 185 Returns noise of xy coords 186 Also manages noise_field (will dynamically recalcuate as needed) 187 """ 188 if self.scale == 0: 189 return 0 190 x_coord, y_coord = xy_coords 191 x_coord += self.x_negative_buffer 192 y_coord += self.y_negative_buffer 193 if x_coord < 0: 194 # x negative buffer needs to be increased 195 x_to_backfill = self._roundup(abs(x_coord), self.buffer_chunks) 196 self.buffer_field_left(x_to_backfill) 197 x_coord, y_coord = xy_coords 198 x_coord += self.x_negative_buffer 199 y_coord += self.y_negative_buffer 200 if y_coord < 0: 201 # y negative buffer needs to be increased 202 y_to_backfill = self._roundup(abs(y_coord), self.buffer_chunks) 203 self.buffer_field_top(y_to_backfill) 204 x_coord, y_coord = xy_coords 205 x_coord += self.x_negative_buffer 206 y_coord += self.y_negative_buffer 207 try: 208 return self.field[int(y_coord)][int(x_coord)] 209 except IndexError: 210 # ran out of generated noise, so need to extend the field 211 height, width = self.field.shape 212 x_to_extend = x_coord - width + 1 213 y_to_extend = y_coord - height + 1 214 if x_to_extend > 0: 215 x_to_extend = self._roundup(x_to_extend, self.buffer_chunks) 216 self.buffer_field_right(x_to_extend) 217 if y_to_extend > 0: 218 y_to_extend = self._roundup(y_to_extend, self.buffer_chunks) 219 self.buffer_field_bottom(y_to_extend) 220 return self.noise(xy_coords) 221 222 223 def recursive_noise( 224 self, 225 xy_coords: Tuple[int, int], 226 depth: int = 1, 227 feedback: float = 0.7, 228 ) -> float: 229 """Returns domain warped recursive perlin noise 230 (number between 0 and 1 from xy coordinates) 231 Recomended feedback (which determines affect of recursive call) around 0-2 232 """ 233 # base case 234 if depth <= 0: 235 return self.noise(xy_coords) 236 # else recur with noise of noise 237 noise_from_xy_coords = int(self.noise(xy_coords) * feedback * 100) 238 return self.recursive_noise( 239 (noise_from_xy_coords, noise_from_xy_coords), 240 depth-1, 241 feedback, 242 )
An object to calculate and store perlin noise data.
Initialisation takes float (recommend very low number < 0.1) and random seed
26 def __init__(self, scale: float = 0.002, seed: int = None) -> None: 27 if seed is None: 28 self.seed = random.randint(0,9999) 29 else: 30 self.seed = seed 31 self.scale = scale 32 size = 10 33 self.x_lin = np.linspace(0, (size*self.scale), size, endpoint=False) 34 self.y_lin = np.linspace(0, (size*self.scale), size, endpoint=False) 35 self.field = self.perlin_field(self.x_lin, self.y_lin) 36 self.x_negative_buffer = 0 37 self.y_negative_buffer = 0 38 self.buffer_chunks = 500
48 def buffer_field_right(self, to_extend: int) -> None: 49 """ 50 Extends object's noise field right 51 """ 52 # y is just gonna stay the same, but x needs to be picking up 53 max_lin = self.x_lin[-1] 54 55 additional_x_lin = np.linspace( 56 max_lin+self.scale, 57 max_lin+(to_extend*self.scale), 58 to_extend, 59 endpoint=False, 60 ) 61 self.field = np.concatenate( 62 [self.field, 63 self.perlin_field(additional_x_lin, self.y_lin)], 64 axis=1, 65 ) 66 self.x_lin = np.concatenate([self.x_lin, additional_x_lin])
Extends object's noise field right
69 def buffer_field_bottom(self, to_extend: int) -> None: 70 """ 71 Extends object's noise field downwards 72 """ 73 max_lin = self.y_lin[-1] 74 additional_y_lin = np.linspace( 75 max_lin+self.scale, 76 max_lin+(to_extend*self.scale), 77 to_extend, 78 endpoint=False, 79 ) 80 self.field = np.concatenate( 81 [self.field, 82 self.perlin_field(self.x_lin, additional_y_lin)], 83 axis=0, 84 ) 85 self.y_lin = np.concatenate([self.y_lin, additional_y_lin])
Extends object's noise field downwards
88 def buffer_field_left(self, to_extend: int) -> None: 89 """ 90 Extends object's noise field left 91 """ 92 min_lin = self.x_lin[0] 93 additional_x_lin = np.linspace( 94 min_lin-(to_extend*self.scale), 95 min_lin, 96 to_extend, 97 endpoint=False, 98 ) 99 self.field = np.concatenate( 100 [self.perlin_field(additional_x_lin, self.y_lin), 101 self.field], 102 axis=1, 103 ) 104 self.x_lin = np.concatenate([additional_x_lin, self.x_lin]) 105 self.x_negative_buffer += to_extend
Extends object's noise field left
108 def buffer_field_top(self, to_extend: int) -> None: 109 """ 110 Extends object's noise field upwards 111 """ 112 min_lin = self.y_lin[0] 113 additional_y_lin = np.linspace( 114 min_lin-(to_extend*self.scale), 115 min_lin, 116 to_extend, 117 endpoint=False, 118 ) 119 self.field = np.concatenate( 120 [self.perlin_field(self.x_lin, additional_y_lin), 121 self.field], 122 axis=0, 123 ) 124 self.y_lin = np.concatenate([additional_y_lin, self.y_lin]) 125 self.y_negative_buffer += to_extend
Extends object's noise field upwards
127 def perlin_field(self, x_lin: List[float], y_lin: List[float]) -> ArrayLike: 128 """ 129 generate field from x and y linear points 130 131 credit to tgirod for stack overflow on numpy perlin noise (most of this code from answer) 132 https://stackoverflow.com/questions/42147776/producing-2d-perlin-noise-with-numpy 133 """ 134 # remembering the random state (so we can put it back after) 135 initial_random_state = np.random.get_state() 136 x_grid, y_grid = np.meshgrid(x_lin, y_lin) 137 x_grid %= 512 138 y_grid %= 512 139 # permutation table 140 np.random.seed(self.seed) 141 field_256 = np.arange(256, dtype=int) 142 np.random.shuffle(field_256) 143 field_256 = np.stack([field_256, field_256]).flatten() 144 # coordinates of the top-left 145 x_i, y_i = x_grid.astype(int), y_grid.astype(int) 146 # internal coordinates 147 x_f, y_f = x_grid - x_i, y_grid - y_i 148 # fade factors 149 u_array, v_array = self.fade(x_f), self.fade(y_f) 150 # noise components 151 n00 = self.gradient(field_256[(field_256[x_i%512] + y_i)%512], x_f, y_f) 152 n01 = self.gradient(field_256[(field_256[x_i%512] + y_i + 1)%512], x_f, y_f - 1) 153 n11 = self.gradient( 154 field_256[(field_256[((x_i%512)+1)%512] + y_i + 1)%512], x_f - 1, y_f - 1) 155 n10 = self.gradient(field_256[(field_256[((x_i%512)+1)%512] + y_i)%512], x_f - 1, y_f) 156 # combine noises 157 x_1 = self.lerp(n00, n10, u_array) 158 x_2 = self.lerp(n01, n11, u_array) 159 # putting the random state back in place 160 np.random.set_state(initial_random_state) 161 field = self.lerp(x_1, x_2, v_array) 162 field += 0.5 163 return field
generate field from x and y linear points
credit to tgirod for stack overflow on numpy perlin noise (most of this code from answer) https://stackoverflow.com/questions/42147776/producing-2d-perlin-noise-with-numpy
166 def lerp(self, a_array: ArrayLike, b_array: ArrayLike, x_array: ArrayLike) -> ArrayLike: 167 "linear interpolation" 168 return a_array + x_array * (b_array - a_array)
linear interpolation
171 def fade(self, t_array: ArrayLike) -> ArrayLike: 172 "6t^5 - 15t^4 + 10t^3" 173 return 6 * t_array**5 - 15 * t_array**4 + 10 * t_array**3
6t^5 - 15t^4 + 10t^3
176 def gradient(self, h_array: ArrayLike, x_array: ArrayLike, y_array: ArrayLike) -> ArrayLike: 177 "grad converts h to the right gradient vector and return the dot product with (x,y)" 178 vectors = np.array([[0, 1], [0, -1], [1, 0], [-1, 0]]) 179 g_array = vectors[h_array % 4] 180 return g_array[:, :, 0] * x_array + g_array[:, :, 1] * y_array
grad converts h to the right gradient vector and return the dot product with (x,y)
183 def noise(self, xy_coords: Tuple[int, int]): 184 """ 185 Returns noise of xy coords 186 Also manages noise_field (will dynamically recalcuate as needed) 187 """ 188 if self.scale == 0: 189 return 0 190 x_coord, y_coord = xy_coords 191 x_coord += self.x_negative_buffer 192 y_coord += self.y_negative_buffer 193 if x_coord < 0: 194 # x negative buffer needs to be increased 195 x_to_backfill = self._roundup(abs(x_coord), self.buffer_chunks) 196 self.buffer_field_left(x_to_backfill) 197 x_coord, y_coord = xy_coords 198 x_coord += self.x_negative_buffer 199 y_coord += self.y_negative_buffer 200 if y_coord < 0: 201 # y negative buffer needs to be increased 202 y_to_backfill = self._roundup(abs(y_coord), self.buffer_chunks) 203 self.buffer_field_top(y_to_backfill) 204 x_coord, y_coord = xy_coords 205 x_coord += self.x_negative_buffer 206 y_coord += self.y_negative_buffer 207 try: 208 return self.field[int(y_coord)][int(x_coord)] 209 except IndexError: 210 # ran out of generated noise, so need to extend the field 211 height, width = self.field.shape 212 x_to_extend = x_coord - width + 1 213 y_to_extend = y_coord - height + 1 214 if x_to_extend > 0: 215 x_to_extend = self._roundup(x_to_extend, self.buffer_chunks) 216 self.buffer_field_right(x_to_extend) 217 if y_to_extend > 0: 218 y_to_extend = self._roundup(y_to_extend, self.buffer_chunks) 219 self.buffer_field_bottom(y_to_extend) 220 return self.noise(xy_coords)
Returns noise of xy coords Also manages noise_field (will dynamically recalcuate as needed)
223 def recursive_noise( 224 self, 225 xy_coords: Tuple[int, int], 226 depth: int = 1, 227 feedback: float = 0.7, 228 ) -> float: 229 """Returns domain warped recursive perlin noise 230 (number between 0 and 1 from xy coordinates) 231 Recomended feedback (which determines affect of recursive call) around 0-2 232 """ 233 # base case 234 if depth <= 0: 235 return self.noise(xy_coords) 236 # else recur with noise of noise 237 noise_from_xy_coords = int(self.noise(xy_coords) * feedback * 100) 238 return self.recursive_noise( 239 (noise_from_xy_coords, noise_from_xy_coords), 240 depth-1, 241 feedback, 242 )
Returns domain warped recursive perlin noise (number between 0 and 1 from xy coordinates) Recomended feedback (which determines affect of recursive call) around 0-2
246def noise_fields( 247 scale: Union[List[float], float] = 0.002, 248 seed: Union[List[int], int] = None, 249 channels: int = 3, 250 ) -> List[NoiseField]: 251 """ 252 Create multiple NoiseField objects in one go. 253 This is a quality of life function, rather than adding new behaviour 254 shades.noise_fields(scale=0.2, channels=3) rather than 255 [shades.NoiseField(scale=0.2) for i in range(3)] 256 """ 257 258 if not isinstance(scale, list): 259 scale = [scale for i in range(channels)] 260 if not isinstance(seed, list): 261 seed = [seed for i in range(channels)] 262 263 return [NoiseField(scale=scale[i], seed=seed[i]) for i in range(channels)]
Create multiple NoiseField objects in one go. This is a quality of life function, rather than adding new behaviour shades.noise_fields(scale=0.2, channels=3) rather than [shades.NoiseField(scale=0.2) for i in range(3)]