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)]
class NoiseField:
 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

NoiseField(scale: float = 0.002, seed: int = None)
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
scale
x_lin
y_lin
field
x_negative_buffer
y_negative_buffer
buffer_chunks
def buffer_field_right(self, to_extend: int) -> None:
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

def buffer_field_bottom(self, to_extend: int) -> None:
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

def buffer_field_left(self, to_extend: int) -> None:
 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

def buffer_field_top(self, to_extend: int) -> None:
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

def perlin_field( self, x_lin: List[float], y_lin: List[float]) -> Union[numpy.typing._array_like._SupportsArray[numpy.dtype], numpy.typing._nested_sequence._NestedSequence[numpy.typing._array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]]:
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

def lerp( self, a_array: Union[numpy.typing._array_like._SupportsArray[numpy.dtype], numpy.typing._nested_sequence._NestedSequence[numpy.typing._array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], b_array: Union[numpy.typing._array_like._SupportsArray[numpy.dtype], numpy.typing._nested_sequence._NestedSequence[numpy.typing._array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], x_array: Union[numpy.typing._array_like._SupportsArray[numpy.dtype], numpy.typing._nested_sequence._NestedSequence[numpy.typing._array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]]) -> Union[numpy.typing._array_like._SupportsArray[numpy.dtype], numpy.typing._nested_sequence._NestedSequence[numpy.typing._array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]]:
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

def fade( self, t_array: Union[numpy.typing._array_like._SupportsArray[numpy.dtype], numpy.typing._nested_sequence._NestedSequence[numpy.typing._array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]]) -> Union[numpy.typing._array_like._SupportsArray[numpy.dtype], numpy.typing._nested_sequence._NestedSequence[numpy.typing._array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]]:
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

def gradient( self, h_array: Union[numpy.typing._array_like._SupportsArray[numpy.dtype], numpy.typing._nested_sequence._NestedSequence[numpy.typing._array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], x_array: Union[numpy.typing._array_like._SupportsArray[numpy.dtype], numpy.typing._nested_sequence._NestedSequence[numpy.typing._array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], y_array: Union[numpy.typing._array_like._SupportsArray[numpy.dtype], numpy.typing._nested_sequence._NestedSequence[numpy.typing._array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]]) -> Union[numpy.typing._array_like._SupportsArray[numpy.dtype], numpy.typing._nested_sequence._NestedSequence[numpy.typing._array_like._SupportsArray[numpy.dtype]], bool, int, float, complex, str, bytes, numpy.typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]]:
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)

def noise(self, xy_coords: Tuple[int, int]):
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)

def recursive_noise( self, xy_coords: Tuple[int, int], depth: int = 1, feedback: float = 0.7) -> float:
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

def noise_fields( scale: Union[List[float], float] = 0.002, seed: Union[List[int], int] = None, channels: int = 3) -> List[NoiseField]:
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)]