Search code examples
javascriptalgorithmnoiseperlin-noisenoise-generator

What is wrong with my perlin noise function?


So I am currently working on a perlin noise generator, for some reason I'm getting incorrect output:

enter image description here

The perlin noise is in a "grid" and does not resemble traditional perlin noise. Here is my code:

var perlinNoise = {
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}],
fade: function(t){
    return t * t * t * (t * (t * 6 - 15) + 10);
},
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(randomFromCoords(cellX + halfCell, cellY + halfCell)*8)];
    SEvector = this.vectors[Math.floor(randomFromCoords(cellX + halfCell, cellY - halfCell)*8)];
    SWvector = this.vectors[Math.floor(randomFromCoords(cellX - halfCell, cellY - halfCell)*8)];
    NWvector = this.vectors[Math.floor(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){
    var cellCoord = this.pointToCell(x, y);

    //Get the positions of the x and y coordinants relative to the cell
    var Xoffset = this.fade(x - cellCoord.x);
    var Yoffset = this.fade(y - cellCoord.y);

    var vectors = this.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: 1 - Xoffset, y: 1 - Yoffset};
    var NEdotProduct = this.dotProduct(NEoffset, vectors.NE);

    var SEoffset = {x: 1 - Xoffset, 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: 1 - Yoffset};
    var NWdotProduct = this.dotProduct(NWoffset, vectors.NW);

    var Nlerp = this.lerp(NWdotProduct, NEdotProduct, Xoffset);

    var Slerp = this.lerp(SWdotProduct, SEdotProduct, Xoffset);

    var finalValue = this.lerp(Slerp, Nlerp, Yoffset);
    if(finalValue < -.5 ){
        console.log("less than -.5");
    }
    return finalValue;
},
noise: function(width, height){
    var values = [];
    for (var y = 0; y < height; ++y) {
        for (var x = 0; x < width; ++x) {
                values[x + y * width] = (this.perlinNoise(x/30,y/30)+1)/2;
        }
    }
    return values;
},
element: document.getElementById("PerlinDisplay1"),
loop: function (){},
initialize: function(){
    initCanvas("PerlinDisplay1Canvas");
    var values = this.noise(canvas.width, canvas.height);
    //maps values from 0 to 1 to grey scale pixels, already tested.
    var valuesAsPixels = values.map(x => ValueToPixel(x));
    UpdateCanvas(valuesAsPixels);
},
deinitialize: function(){}

}

The functions initCanvas, ValueToPixel, and UpdateCanvas work and have been tested with other use cases. I would really appreciate some insight as to what I am doing wrong.

Edit: Upon request I am adding the functions ValueToPixel and UpdateCanvas.

function UpdateCanvas(pixels){
    var n = 0;
    for (var y = 0; y < canvas.height; ++y) {
        for (var x = 0; x < canvas.width; ++x) {
            var index = (y * canvas.width + x) * 4;
            for(var channel = 0; channel < 4; ++channel){
                data[index + channel]  = pixels[index/4][channel];
                n++;
            }
        }
    }
    context.putImageData(imageData, 0, 0);
}

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

Solution

  • I figured it out! I was calculating the offsets from the corners incorrectly. I was wrongly calculating the absolute values of the distance from the corners rather than the offset from the corner. Here is the corrected code snipped below:

    //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);
    

    Note that instead of subtracting the offset from 1, 1 is being subtracted from the offset.