var canvas; var context; var imageData; var data; var timeSinceUpdate = 0; function Canvas(id){

this.canvas = document.getElementById(id);
this.context = this.canvas.getContext('2d');
this.imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
this.data = this.imageData.data;
this.height = this.canvas.height;
this.width = this.canvas.width;
this.update = function(pixels){
    if(this.canvas.width != this.width){
        console.log("regetting image data");
        this.imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        this.data = this.imageData.data;
        this.height = this.canvas.height;
        this.width = this.canvas.width;
    }
    for (var y = 0; y < this.canvas.height; y++) {
        for (var x = 0; x < this.canvas.width; x++) {
            var index = (y * this.canvas.width + x) * 4;
            for(var channel = 0; channel < 4; channel++){
                this.data[index + channel]  = pixels[index/4][channel];
            }
        }
    }
    this.context.putImageData(this.imageData, 0, 0);
}

} var canvasUtils = {

valueToPixel: function(value){
    // red, green, blue, alpha
    return [value*255, value*255, value*255, 255];
}

} var noiseFunctions = {

seed: 0,
randomFromCoords: function(x, y){
    var h = this.seed + x*374761393 + y*668265263; // all constants are prime
    h = (h^(h >> 13))*1274126177;
    return (h^(h >> 16)) / 2147483647.0;
},
perlin: {
    vectors: [{x:1,y:0},{x:1,y:-1},{x:0,y:-1},{x:-1,y:-1},{x:-1,y:0},{x:-1,y:1},{x:0,y:1},{x:1,y:1}],
    pointToCell: function(x, y){
        cellX = Math.floor(x);
        cellY = Math.floor(y);
        return {x:cellX, y:cellY};
    },
    cellToVectors: function(cellX, cellY){
        halfCell = .5;
        // I use the four intercardinal directions to label the vectors.
        // The random values are multiplied by 8 to map them to the 8 entries of the vectors array.
        NEvector = this.vectors[Math.floor(noiseFunctions.randomFromCoords(cellX + halfCell, cellY + halfCell)*8)];
        SEvector = this.vectors[Math.floor(noiseFunctions.randomFromCoords(cellX + halfCell, cellY - halfCell)*8)];
        SWvector = this.vectors[Math.floor(noiseFunctions.randomFromCoords(cellX - halfCell, cellY - halfCell)*8)];
        NWvector = this.vectors[Math.floor(noiseFunctions.randomFromCoords(cellX - halfCell, cellY + halfCell)*8)];
        return {NE: NEvector, SE: SEvector, SW: SWvector, NW: NWvector};
    },
    dotProduct: function(vector1, vector2){
    // Another way to calculate the dot product. This is more performance friendly than cosine calculations.
    return vector1.x * vector2.x + vector1.y * vector2.y;
    },
    lerp: function(value1, value2, t){
        return (1 - t) * value1 + t * value2;
    },
    perlinNoise: function(x,y, fadeFunction){
        var cellCoord = noiseFunctions.perlin.pointToCell(x, y);
        // Get the positions of the x and y coordinants relative to the cell
        var Xoffset = x - cellCoord.x;
        var Yoffset = y - cellCoord.y;
        var vectors = noiseFunctions.perlin.cellToVectors(cellCoord.x, cellCoord.y);
        // The offset from each corner is calculated.
        // Then the dotproduct between the offset vector and the random vector is calculated.
        var NEoffset = {x: Xoffset - 1, y: Yoffset - 1};
        var NEdotProduct = this.dotProduct(NEoffset, vectors.NE);
        var SEoffset = {x: Xoffset - 1, y: Yoffset};
        var SEdotProduct = this.dotProduct(SEoffset, vectors.SE);
        var SWoffset = {x: Xoffset, y: Yoffset};
        var SWdotProduct = this.dotProduct(SWoffset, vectors.SW);
        var NWoffset = {x: Xoffset, y: Yoffset - 1};
        var NWdotProduct = this.dotProduct(NWoffset, vectors.NW);
        var Nlerp = this.lerp(NWdotProduct, NEdotProduct, fadeFunction(Xoffset));
        var Slerp = this.lerp(SWdotProduct, SEdotProduct, fadeFunction(Xoffset));
        var finalValue = this.lerp(Slerp, Nlerp, fadeFunction(Yoffset));
        return finalValue;
    }
}

}

var constantValueDisplay = {

valueSlider : document.getElementById('ConstantValueFillValue'),
element : document.getElementById("ConstantValueFillDisplay"),
canvas : new Canvas("ConstantValueFillDisplayCanvas"),
loop : function(deltaTime){
    // An array of values between 0 and 1
    var values = []
    for (var y = 0; y < this.canvas.canvas.height; ++y) {
        for (var x = 0; x < this.canvas.canvas.width; ++x) {
            // valueSlider will give a value between 0 and 100
            values[x + y *this.canvas.canvas.width] = this.valueSlider.value / 100.0;
        }
    }
    // Converts the array to pixel values with red, green, blue, and alpha channels
    var valuesAsPixels = values.map(x => canvasUtils.valueToPixel(x));
    this.canvas.update(valuesAsPixels);
},
initialize : function(){},
deinitialize : function(){}

}

var nonDeterministicWhiteNoise = {

frequencySlider: document.getElementById('DynamicStaticUpdateFrequency'),
canvas: new Canvas("WhiteNoiseLoopDisplayCanvas"),
noise: function(width, height){
    var values = [];
    for (var y = 0; y < height; ++y) {
        for (var x = 0; x < width; ++x) {
                values[x + y * width] = Math.random();
        }
    }
    return values;
},
element: document.getElementById("WhiteNoiseLoopDisplay"),
loop: function(deltaTime){
    if(timeSinceUpdate > (1000 - this.frequencySlider.value)){
        whiteNoiseValues = this.noise(this.canvas.canvas.width, this.canvas.canvas.height);
        // Converts the array to pixel values with red, green, blue, and alpha channels
        var valuesAsPixels = whiteNoiseValues.map(x => canvasUtils.valueToPixel(x));
        this.canvas.update(valuesAsPixels);
        timeSinceUpdate = 0;
    }
    timeSinceUpdate = timeSinceUpdate + deltaTime;
},
initialize: function(){},
deinitialize: function(){}

}

var deterministicWhiteNoise = {

frequencySlider: document.getElementById('StaticStaticUpdateFrequency'),
canvas: new Canvas("StaticWhiteNoiseLoopDisplayCanvas"),
noise: function(width, height){
    var values = [];
    for (var y = 0; y < height; ++y) {
        for (var x = 0; x < width; ++x) {
                values[x + y * width] = noiseFunctions.randomFromCoords(x,y);
        }
    }
    return values;
},
element: document.getElementById("StaticWhiteNoiseLoopDisplay"),
loop: function (deltaTime){
    if(timeSinceUpdate > (1000 - this.frequencySlider.value)){
        whiteNoiseValues = this.noise(this.canvas.canvas.width, this.canvas.canvas.height);
        // Converts the array to pixel values with red, green, blue, and alpha channels
        var valuesAsPixels = whiteNoiseValues.map(x => canvasUtils.valueToPixel(x));
        this.canvas.update(valuesAsPixels);
        timeSinceUpdate = 0;
    }
    timeSinceUpdate = timeSinceUpdate + deltaTime;
},
initialize: function(){},
deinitialize: function(){}

}

var deterministicWhiteNoiseWithScale = {

scaleSlider: document.getElementById('StaticWhiteNoiseScale'),
canvas: new Canvas("StaticWhiteNoiseScaleDisplayCanvas"),
noise: function(width, height,scale){
    var values = [];
    for (var y = 0; y < height; ++y) {
        for (var x = 0; x < width; ++x) {
                values[x + y * width] = noiseFunctions.randomFromCoords(Math.floor(x/scale),Math.floor(y/scale));
        }
    }
    return values;
},
element: document.getElementById("StaticWhiteNoiseScaleDisplay"),
loop: function (deltaTime){
    whiteNoiseValues = this.noise(this.canvas.canvas.width, this.canvas.canvas.height, this.scaleSlider.value/50);
    // Converts the array to pixel values with red, green, blue, and alpha channels
    var valuesAsPixels = whiteNoiseValues.map(x => canvasUtils.valueToPixel(x));
    this.canvas.update(valuesAsPixels);
    timeSinceUpdate = 0;
},
initialize: function(){},
deinitialize: function(){}

}

var lerpPerlinNoise = {

canvas: new Canvas("PerlinDisplay1Canvas"),
fade: function(t){
    return t;
},
noise: function(width, height){
    var values = [];
    for (var y = 0; y < height; ++y) {
        for (var x = 0; x < width; ++x) {
                values[x + y * width] = (noiseFunctions.perlin.perlinNoise(x/30,y/30,this.fade)+1)/2;
        }
    }
    return values;
},
element: document.getElementById("PerlinDisplay1"),
loop: function (){},
initialize: function(){
    var values = this.noise(this.canvas.canvas.width, this.canvas.canvas.height);
    var valuesAsPixels = values.map(x => canvasUtils.valueToPixel(x));
    this.canvas.update(valuesAsPixels);
},
deinitialize: function(){}

}

var truePerlinNoise = {

canvas: new Canvas("PerlinDisplay2Canvas"),
fade: function(t){
    // return t;
    return t * t * t * (t * (t * 6 - 15) + 10);
},
noise: function(width, height){
    var values = [];
    for (var y = 0; y < height; ++y) {
        for (var x = 0; x < width; ++x) {
                values[x + y * width] = (noiseFunctions.perlin.perlinNoise(x/30,y/30,this.fade)+1)/2;
        }
    }
    return values;
},
element: document.getElementById("PerlinDisplay2"),
loop: function (){},
initialize: function(){
    var values = this.noise(this.canvas.canvas.width, this.canvas.canvas.height);
    var valuesAsPixels = values.map(x => canvasUtils.valueToPixel(x));
    this.canvas.update(valuesAsPixels);
},
deinitialize: function(){}

}

var scalePerlinNoise = {

canvas: new Canvas("ScalePerlinNoiseDisplayCanvas"),
scaleSlider: document.getElementById('ScalePerlinNoiseSlider'),
fade: function(t){
    // return t;
    return t * t * t * (t * (t * 6 - 15) + 10);
},
noise: function(width, height){
    var values = [];
    for (var y = 0; y < height; ++y) {
        for (var x = 0; x < width; ++x) {
                values[x + y * width] = (noiseFunctions.perlin.perlinNoise(x/this.scaleSlider.value,y/this.scaleSlider.value,this.fade)+1)/2;
        }
    }
    return values;
},
element: document.getElementById("ScalePerlinNoiseDisplay"),
loop: function (){
    var values = this.noise(this.canvas.canvas.width, this.canvas.canvas.height);
    var valuesAsPixels = values.map(x => canvasUtils.valueToPixel(x));
    this.canvas.update(valuesAsPixels);
},
initialize: function(){},
deinitialize: function(){}

}

var fractalPerlinNoise = {

canvas: new Canvas("FractalPerlinNoiseDisplayCanvas"),
scaleSlider: document.getElementById('FractalPerlinNoiseScaleSlider'),
lastScaleSliderValue: -1,
octaveSlider: document.getElementById('FractalPerlinNoiseOctaveSlider'),
lastOctaveSliderValue: -1,
persistenceSlider: document.getElementById('FractalPerlinNoisePersistenceSlider'),
lastPersistenceSliderValue: -1,
fade: function(t){
    // return t;
    return t * t * t * (t * (t * 6 - 15) + 10);
},
noise: function(width, height, scale, octaves, persistence){
    var values = [];
    seed = 20;
    var maxValue = 0;
    for (var y = 0; y < height; ++y) {
        for (var x = 0; x < width; ++x) {
            var value = 0;
            // For each octave... 
            for(var n = 0; n < octaves; n++){
                // A = p^n
                var amplitude = Math.pow(persistence, n);
                // F = 2^n
                var frequency = Math.pow(2, n);
                // Add each noise value to the total value of that pixel
                value += (noiseFunctions.perlin.perlinNoise((x/scale) * frequency, (y/scale) * frequency, this.fade) * amplitude + 1) / 2;
            }
            maxValue = value > maxValue ? value : maxValue;
            values[x + y * width] = value;
        }
    }
    console.log(maxValue);
    var otherValues = values.map(x => x/maxValue);
    return otherValues;
},
element: document.getElementById("FractalPerlinNoiseDisplay"),
loop: function (){
    if(this.scaleSlider.value != this.lastScaleSliderValue || 
       this.octaveSlider.value != this.lastOctaveSliderValue || 
       this.persistenceSlider.value != this.lastPersistenceSliderValue ){
        var values = this.noise(this.canvas.canvas.width, this.canvas.canvas.height, this.scaleSlider.value, this.octaveSlider.value, this.persistenceSlider.value/100);
        var valuesAsPixels = values.map(x => canvasUtils.valueToPixel(x));
        this.canvas.update(valuesAsPixels);
        this.lastScaleSliderValue = this.scaleSlider.value;
        this.lastOctaveSliderValue = this.octaveSlider.value;
        this.lastPersistenceSliderValue = this.persistenceSlider.value;
    }
},
initialize: function(){},
deinitialize: function(){}

}

codeDisplays.push(constantValueDisplay); codeDisplays.push(nonDeterministicWhiteNoise); codeDisplays.push(deterministicWhiteNoise); codeDisplays.push(deterministicWhiteNoiseWithScale); codeDisplays.push(lerpPerlinNoise); codeDisplays.push(truePerlinNoise); codeDisplays.push(scalePerlinNoise); codeDisplays.push(fractalPerlinNoise);