var sinLUT = new Array();
var cosLUT = new Array();
var SINCOS_PRECISION = 0.5;
var DEG_TO_RAD = Math.PI/180.0;
var SINCOS_LENGTH = Math.round(360 / SINCOS_PRECISION);

for (var i = 0; i < SINCOS_LENGTH; i++) {
    sinLUT[i] = Math.sin(i * DEG_TO_RAD * SINCOS_PRECISION);
    cosLUT[i] = Math.cos(i * DEG_TO_RAD * SINCOS_PRECISION);
}

var perlin;

var PERLIN_YWRAPB		= 4;	
var PERLIN_YWRAP		= 1<<PERLIN_YWRAPB;
var PERLIN_ZWRAPB		= 8;
var PERLIN_ZWRAP		= 1<<PERLIN_ZWRAPB;
var PERLIN_SIZE			= 4095;
var perlin_octaves		= 4;
var perlin_amp_falloff	= 0.5;

function noise(x, y, z) {
    if (z == null) z = 0;
    
    if (perlin == null) {
    	perlin = new Array(); //float[PERLIN_SIZE + 1];
    	for (var i = 0; i < PERLIN_SIZE + 1; i++) {
    		perlin[i] = Math.random();
    	}

    	perlin_cosTable = cosLUT;
    	perlin_TWOPI = perlin_PI = SINCOS_LENGTH;
    	perlin_PI >>= 1;
    }

    if (x<0) x=-x;
    if (y<0) y=-y;
    if (z<0) z=-z;

    var xi= x, yi=y, zi=z;
    var xf = x-xi;
    var yf = y-yi;
    var zf = z-zi;
    var rxf, ryf;

    var r = 0;
    var ampl = 0.5;

    var n1,n2,n3;

    for (var i=0; i<perlin_octaves; i++) {
    	var of=xi+(yi<<PERLIN_YWRAPB)+(zi<<PERLIN_ZWRAPB);

    	rxf=noise_fsc(xf);
    	ryf=noise_fsc(yf);

    	n1  = perlin[of&PERLIN_SIZE];
    	n1 += rxf*(perlin[(of+1)&PERLIN_SIZE]-n1);
    	n2  = perlin[(of+PERLIN_YWRAP)&PERLIN_SIZE];
    	n2 += rxf*(perlin[(of+PERLIN_YWRAP+1)&PERLIN_SIZE]-n2);
    	n1 += ryf*(n2-n1);

 		of += PERLIN_ZWRAP;
    	n2  = perlin[of&PERLIN_SIZE];
    	n2 += rxf*(perlin[(of+1)&PERLIN_SIZE]-n2);
    	n3  = perlin[(of+PERLIN_YWRAP)&PERLIN_SIZE];
    	n3 += rxf*(perlin[(of+PERLIN_YWRAP+1)&PERLIN_SIZE]-n3);
    	n2 += ryf*(n3-n2);

    	n1 += noise_fsc(zf)*(n2-n1);

    	r += n1*ampl;
    	ampl *= perlin_amp_falloff;
    	xi<<=1; xf*=2;
    	yi<<=1; yf*=2;
    	zi<<=1; zf*=2;

    	if (xf>=1.0) { xi++; xf--; }
    	if (yf>=1.0) { yi++; yf--; }
    	if (zf>=1.0) { zi++; zf--; }
    }
    return r;
}

function noise_fsc(i) {
    return 0.5*(1.0-perlin_cosTable[Math.round(i*perlin_PI)%perlin_TWOPI]);
}