#ifndef NOISE_HXX
#define NOISE_HXX

/* PerlinNoise2D */

// cosine interpolation
float cosrp(float a, float b, float x)
{
    float ft = x * 3.1415927;
    float f = (1 - cos(ft)) * 0.5;
    return  a*(1-f) + b*f;
}

// random number generator
float Noise2D(int x, int y)
{
    int n = x + y * 57;
    n = (n<<13) ^ n;
    float r = ( 1.0 - ( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0);;
    return r;
}

// interpolating noise
float SmoothNoise2D(float x, float y)
{
    double n1 = Noise2D((int)x, (int)y);
    double n2 = Noise2D((int)x + 1, (int)y);
    double n3 = Noise2D((int)x, (int)y + 1);
    double n4 = Noise2D((int)x + 1, (int)y + 1);
    
    double i1 = cosrp(n1, n2, x - (int)x);
    double i2 = cosrp(n3, n4, x - (int)x);
    
    return cosrp(i1, i2, y - (int)y);
}

float PerlinNoise2D(float x, float y, float persistence, float frequency, float octaves)
{
    double total = 0.0;
    double amplitude = 1;

    for(int lcv = 0; lcv < octaves; lcv++)
    {
	total = total + SmoothNoise2D(x * frequency, y * frequency) * amplitude;
	frequency = frequency * 2;
	amplitude = amplitude * persistence;
    }

    total = MIN(MAX(total,0), 1);
    
    return total;
}


/* PerlinNoise3D */

// random number generator
float Noise3D(int x, int y, int z)
{
    int n = x + y * 57 + z;
    n = (n<<13) ^ n;
    float r = ( 1.0 - ( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0);;
    return r;
}

// interpolating noise
float SmoothNoise3D(float x, float y, float z)
{
    double n1 = Noise3D((int)x, (int)y, (int)z);
    double n2 = Noise3D((int)x + 1, (int)y, (int)z);
    double n3 = Noise3D((int)x, (int)y + 1, (int)z);
    double n4 = Noise3D((int)x + 1, (int)y + 1, (int)z);
    double n5 = Noise3D((int)x, (int)y, (int)z + 1);
    double n6 = Noise3D((int)x + 1, (int)y, (int)z + 1);
    double n7 = Noise3D((int)x, (int)y + 1, (int)z + 1);
    double n8 = Noise3D((int)x + 1, (int)y + 1, (int)z + 1);
    
    double i1 = cosrp(n1, n2, x - (int)x);
    double i2 = cosrp(n3, n4, x - (int)x);
    double i3 = cosrp(n5, n6, x - (int)x);
    double i4 = cosrp(n7, n8, x - (int)x);
    
    double i5 = cosrp(i1, i2, y - (int)y);
    double i6 = cosrp(i3, i4, y - (int)y);
  
    return cosrp(i5, i6, z - (int)z);
}

float PerlinNoise3D(float x, float y, float z, float persistence, float frequency, float octaves)
{
    double total = 0.0;
    double amplitude = 1;
    
    for(int lcv = 0; lcv < octaves; lcv++)
    {
	total = total + SmoothNoise3D(x * frequency, y * frequency, z * frequency) * amplitude;
	frequency = frequency * 2;
	amplitude = amplitude * persistence;
    }

    total = MIN(MAX(total,0), 1);
    
    return total;
}


/* improved PerlinNoise3D */

void initializeMatrix(std::vector<int>& p)
{
    int permutations[512] = { 151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,
			      99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,
			      11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,
			      139,48,27,166, 77,146,158,231,83,111,229,122,60,211,133,230,220,105, 92,41,55,
			      46,245,40,244,102,143, 54,65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,
			      169,200,196,135,130,116,188,159, 86,164,100,109,198,173,186, 3,64, 52,217,226,
			      250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,
			      189,28,42,223,183,170,213,119,248,152, 2,44,154,163,70,221,153,101,155,167,43,
			      172,9,129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,
			      228,251, 34,242,193,238,210,144, 12,191,179,162,241, 81,51,145,235,249,14,239,
			      107,49,192,214,31,181,199,106,157,184, 84,204,176,115,121,50,45,127,4,150,254,
			      138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 };
    
    for(int i=0; i < 256 ; i++)
    {
	p[i] = permutations[i];
	p[256+i] = permutations[i];
    }
}

float fade(float t)
{
    return (t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f));
}

float grad(int hash, float x, float y, float z)
{
    int h = hash & 15;

    float u = (h < 8) ? x : y;
    float v = (h < 4) ? y : (h == 12 || h == 14) ? x : z;
    
    return ( ( ((h&1) == 0) ? u : -u ) + ( ((h&2) == 0) ? v : -v ));
}

float PerlinNoise3D_new(float x, float y, float z)
{
    std::vector<int> p(512);
    initializeMatrix(p);
    
    int X = (int)floor(x) & 255;
    int Y = (int)floor(y) & 255;
    int Z = (int)floor(z) & 255;
    
    x -= floor(x);
    y -= floor(y);
    z -= floor(z);
    
    float u = fade(x);
    float v = fade(y);
    float w = fade(z);
    
    int A = p[X] + Y;
    int AA = p[A] + Z;
    int AB = p[A+1] + Z;
    
    int B = p[X+1] + Y;
    int BA = p[B] + Z;
    int BB = p[B+1] + Z;
    
    return lerp(lerp(lerp(grad(p[AA  ], x  , y  , z   ),
			  grad(p[BA  ], x-1, y  , z   ),
			  u),
		     lerp(grad(p[AB  ], x  , y-1, z   ),
			  grad(p[BB  ], x-1, y-1, z   ),
			  u),
		     v),
		lerp(lerp(grad(p[AA+1], x  , y  , z-1 ),
			  grad(p[BA+1], x-1, y  , z-1 ),
			  u),
		     lerp(grad(p[AB+1], x  , y-1, z-1 ),
			  grad(p[BB+1], x-1, y-1, z-1 ),
			  u),
		     v),
		w);
    
}

#endif