shades.noise_fields API documentation

# shades.noise_fields

noise_fields

Functions and classes relating to Shades' NoiseField

View Source

      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: View Source

     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) View Source

    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: View Source

    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: View Source

    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: View Source

     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: View Source

    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]]]: View Source

    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]]]: View Source

    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]]]: View Source

    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]]]: View Source

    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]): View Source

    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: View Source

    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]: View Source

    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)]