# MINIMAL PYTHON ONLY DYNOISE MODULE IMPLEMENTATION 
from math import floor, pow 

class NGEN: 
    def __init__(self, seed=1): 
        self.InitNoise(seed)
        
    def InitNoise(self, seed): 
        self.IA = 16807 
        self.IM = 2147483647 
        self.AM = 1.0/self.IM 
        self.IQ = 127773 
        self.IR = 2836 
        self.MASK = 123459876 
        self.seed = seed 
        self.__InitFNoise() 

    def __InitFNoise(self, MaxNoise=16): 
        'Intialize noise module with size MaxNoise (must be a power of 2)' 
        self.NK1 = MaxNoise+1 
        self.NK2 = self.NK1*self.NK1 
        self.NCUBE = self.NK2*self.NK1 
        self.NMask = MaxNoise-1 
        self.xyz1 = 1 
        self.xy1z = self.NK1 
        self.xy1z1 = self.NK1+1 
        self.x1yz = self.NK2 
        self.x1yz1 = self.NK2+1 
        self.x1y1z = self.NK2+self.NK1 
        self.x1y1z1 = self.NK2+self.NK1+1 
        self.NMTX = [] 
        for i in range(self.NCUBE): 
            self.NMTX.append(2.0*self.random()-1.0) 
        for x in range(self.NK1): 
            i = x 
            if i==MaxNoise: i=0 
            for y in range(self.NK1): 
                j = y 
                if j==MaxNoise: j=0 
                for z in range(self.NK1): 
                    k = z 
                    if k==MaxNoise: k=0 
                    self.NMTX[x*self.NK2 + y*self.NK1 + z] = self.NMTX[i*self.NK2 + j*self.NK1 + k] 

    def FNoise(self, pos): 
        'Return a noise value at position x,y,z' 
        x = abs(pos[0]) 
        y = abs(pos[1]) 
        z = abs(pos[2]) 
        try: 
            ix = int(x) & self.NMask 
            iy = int(y) & self.NMask 
            iz = int(z) & self.NMask 
        except: 
            #float too large 
            ix = int(x % self.MaxNoise) 
            iy = int(y % self.MaxNoise) 
            iz = int(z % self.MaxNoise) 
        ox = x - floor(x) 
        oy = y - floor(y) 
        oz = z - floor(z) 
        ox = ox * ox * (3.0 - 2.0 * ox) 
        IDX = ix*self.NK2 + iy*self.NK1 + iz 
        NS = self.NMTX 
        p000 = NS[IDX] 
        p001 = NS[IDX + self.xyz1] 
        p010 = NS[IDX + self.xy1z] 
        p011 = NS[IDX + self.xy1z1] 
        p00 = (NS[IDX + self.x1yz] - p000) * ox + p000 
        p01 = (NS[IDX + self.x1yz1] - p001) * ox + p001 
        p10 = (NS[IDX + self.x1y1z] - p010) * ox + p010 
        p11 = (NS[IDX + self.x1y1z1] - p011) * ox + p011 
        oy = oy * oy * (3.0 - 2.0 * oy) 
        p0 = (p10 - p00) * oy + p00 
        p1 = (p11 - p01) * oy + p01 
        return ((p1 - p0) * (oz * oz * (3.0 - 2.0 * oz)) + p0) 

    def HTerrain(self, pos, H, lacunarity, octaves, offset): 
        pwHL = pwr = pow(lacunarity, -H) 
        v = [pos[0], pos[1], pos[2]]   # don't modify original 
        val = offset + self.FNoise(v) 
        v[0] *= lacunarity 
        v[1] *= lacunarity 
        v[2] *= lacunarity 

        # fractal build 
        for i in range(1,octaves): 
            inc = (self.FNoise(v) + offset) * pwr * val 
            val += inc 
            pwr *= pwHL 
            if pwr<1.0e-5: break 
            v[0] *= lacunarity 
            v[1] *= lacunarity 
            v[2] *= lacunarity 

        # remainder 
        rem = octaves - floor(octaves) 
        if rem!=0.0: 
            inc = (self.FNoise(v) + offset) * pwr 
            val += rem * inc * val 

        return val 

    def random(self): 
        # minimal standard 
        # no lin.con. (int.overfl.) 
        self.seed ^= self.MASK 
        k = self.seed / self.IQ 
        self.seed = self.IA * (self.seed - k*self.IQ) - self.IR*k 
        if self.seed<0: self.seed += self.IM 
        R = self.AM * self.seed 
        self.seed ^= self.MASK 
        return R 

# as module 

NG = NGEN() 

def InitNoise(seed): 
    NG.InitNoise(seed) 

def random(): 
    return NG.random() 

def HTerrain(pos, H, lacunarity, octaves, offset): 
    return NG.HTerrain(pos, H, lacunarity, octaves, offset) 
