# The noise fields a dither can draw on, as the GPU would compute them at gl_FragCoord (x, y).
# Each returns u in [0, 1). numpy only.
import numpy as np
from bluenoise import bluenoise

f32 = np.float32

def _fract(v):
    return (v - np.floor(v)).astype(f32)

def sin_hash(x, y, model='M2'):
    """Starlock's DITHER hash, fract(sin(dot(p, (12.9898, 78.233))) * 43758.5453), in float32.
    M1: sin correctly rounded. M2: sin reduced the way GPUs commonly lower it, from the fraction
    of a turn fract(x / 2pi) held in float32 (SwiftShader, read back, matches M2's statistics)."""
    x = np.asarray(x, f32); y = np.asarray(y, f32)
    d = (x * f32(12.9898) + y * f32(78.233)).astype(f32)
    if model == 'M1':
        s = np.sin(d.astype(np.float64)).astype(f32)
    else:
        turn = _fract((d * f32(1 / (2 * np.pi))).astype(f32))
        s = np.sin(2 * np.pi * turn.astype(np.float64)).astype(f32)
    return _fract((s * f32(43758.5453)).astype(f32)).astype(np.float64)

def ign(x, y):
    """Interleaved gradient noise (Jimenez 2014), float32."""
    x = np.asarray(x, f32); y = np.asarray(y, f32)
    return _fract((f32(52.9829189) * _fract((x * f32(0.06711056) + y * f32(0.00583715)).astype(f32))).astype(f32)).astype(np.float64)

_TILES = {}
def blue(x, y, n=64, seed=1):
    """A void-and-cluster blue-noise tile, repeated: (rank + .5) / n^2, read at texel (x mod n, y mod n).
    Shipped as 8 bits: floor(u * 256) / 255, as the texture would hold it."""
    if (n, seed) not in _TILES:
        _TILES[(n, seed)] = np.floor(bluenoise(n, seed) * 256) / 255.
    t = _TILES[(n, seed)]
    xi = np.floor(np.asarray(x)).astype(int) % n; yi = np.floor(np.asarray(y)).astype(int) % n
    return t[yi, xi]

def r2(x, y):
    """Roberts' R2 low-discrepancy sequence on the pixel grid: fract(x / g + y / g^2), g = 1.3247..."""
    x = np.asarray(x, f32); y = np.asarray(y, f32)
    return _fract((x * f32(0.7548776662) + y * f32(0.5698402910)).astype(f32)).astype(np.float64)

def white(x, y, seed=0):
    """An ideal random field (numpy's generator), for reference only."""
    rng = np.random.default_rng(seed + 7919 * int(np.asarray(x).flat[0]) + 104729 * int(np.asarray(y).flat[0]))
    return rng.random(np.shape(x))

def tri(u):
    """Gjøl's remap of one uniform to a triangular distribution on (-1, 1)."""
    t = 2 * u - 1
    return np.sign(t) * (1 - np.sqrt(1 - np.abs(t)))
