Perlin noise for plotting n coordinates?

I want to randomly plot an arbitrary number of x,y coordinates, but I don’t want them to look all “white noise”-y, I want them to look nice and clumpy like perlin noise does.

I guess I’m basically trying to do some kind of perlinPlotter function such that:

for i = 1, n do
newRandomPosition = perlinPlotter(i)
end

Is there a way to use the perlin noise generator to do that?

P.S. everything I’ve tried to do so far with the perlin noise generator so far just returns 0, so I must be getting something radically wrong.

@UberGoober Perlin noise kind of creates random values, but those random values are the same for the same x,z position when run. So each time you use it, you get the same y value for the same x,z . Also, you’ll always get a y value of zero if the x and z values are integers. To get other than zero, you need to use fractional positions for x and z. Run the below code with step 1 so the x,z values are integers. It will print just 0 for y. Change the step to .5 . At the half x,z values you’ll get some value for y. So if you want a lot of different randomness, you need to do small values for step starting and ending at some non integer value for x,z.

Here’s an example for perlin noise.

viewer.mode=STANDARD

function setup()
    val=2
    step=1
    h=craft.noise.perlin()
    for x=-val,val,step do
        for z=-val,val,step do           
            y=h:getValue(x,0,z)
            print("x= "..x.." z= "..z.." y= "..y)
        end
    end
end

@dave1707 I tried to apply your code to drawing an entire screen and it worked really well except for one puzzling thing.


function setup()
    
    --set up initial variables
    p = craft.noise.perlin()
    perlinHigh, perlinLow = 0, 0
    colorTable = {}
    
    --find increments that turn WIDTH and HEIGHT into ranges 0-1
    stepX = 1/WIDTH
    stepY = 1/HEIGHT
    
    --using those, get a Perlin z value for every x, y
    for x = 0, 1, stepX do
        for y = 0, 1, stepY do
            z = p:getValue(x, y, 0)
            z = math.abs(z)
            table.insert(colorTable, z)
            if z > perlinHigh then
                perlinHigh = z
            elseif z < perlinLow then
                perlinLow = z 
            end                
        end
    end
    
    --turn the range of perlin values into a fraction of 255
    perlinRange = perlinHigh - perlinLow
    colorMultiplier = 255 / perlinRange
    
    --use that fraction to turn each value into a color value
    for i, decimal in ipairs(colorTable) do
        rgbVal = decimal * colorMultiplier
        colorTable[i] = color(rgbVal, rgbVal, rgbVal, rgbVal)
    end
    
    --make an image and context
    perlinImage = image(WIDTH, HEIGHT)
    setContext(perlinImage)
    
    --draw each color to that image at the right place
    indexCount = 0
    for x = 1, WIDTH do
        for y = 1, HEIGHT do     
            indexCount = indexCount + 1   
            perlinImage:set(x, y, colorTable[indexCount])
        end
    end
end

function draw()
    background(67, 172, 236)
    sprite(perlinImage, WIDTH/2, HEIGHT/2)
end

It works perfectly when run on my iPhone in landscape mode, but when run in portrait mode there’s a weird slash at the lower right (see images).

@UberGoober Your width height sizes don’t match the color table size, so you’re off each iteration thru the for loops. Make the change below. I increased the width and height by 1.

    --draw each color to that image at the right place
    indexCount = 0
    for x = 1, WIDTH+1 do
        for y = 1, HEIGHT+1 do     
            indexCount = indexCount + 1   
            perlinImage:set(x, y, colorTable[indexCount])
        end
    end

@UberGoober Heres another change to allow shifting of where the perlin noise starts to give a different view. Change the values of xOffset,yOffset to get different views.

    --using those, get a Perlin z value for every x, y
    xOffset,yOffset=10,30
    for x = 0, 1, stepX do
        for y = 0, 1, stepY do
            z = p:getValue(x+xOffset, y+yOffset, 0)
            z = math.abs(z)
            table.insert(colorTable, z)
            if z > perlinHigh then
                perlinHigh = z
            elseif z < perlinLow then
                perlinLow = z 
            end                
        end
    end

@dave1707 thanks for your help. I found that your offset-by-one code throws an out of range error unless amended to:


    for x = 1, WIDTH+1 do
        for y = 1, HEIGHT+1 do     
            indexCount = indexCount + 1   
            if colorTable[indexCount] then
                perlinImage:set(x, y, colorTable[indexCount])
            end
        end
    end

But even then, I find that it only works in portrait mode. Attached is an image of the output in landscape mode. Do I have to change the code depending on the orientation? That seems illogical.

@dave1707 I like your offset trick. By changing the offsets to random values it becomes a lovely little cloud generator. :slight_smile:

@UberGoober I guess the problem is what device the code is running on because the step calculations are probably causing problems with different width and height values. Need to change the way things are calculated. Maybe instead of just a straight table of values, you need a multiple table for [x][y].

@UberGoober Maybe you need a for x=1,WIDTH for y=1,HEIGHT to do calculations. Then in those loops your calculations for perlin will be fractions of x+offset and y+offset and then save the value at [x][y] table position.

@dave1707 really? I mean, you’re probably right, but if the difference between it working in one direction and it working in another direction is just adding one or not, it just kind of logically seems like there must be a simpler fix than rewriting everything.

@UberGoober It’s not that it works in one direction and not another, your original code didn’t work right in any direction on my iPad. Here’s some code I put together real quick to give you an example of the for loops and the x,y table. You can add your code for the high and low z values and do your other calculations for the cloud.

viewer.mode=FULLSCREEN

function setup()    
    p=craft.noise.perlin()
    w,h=WIDTH,HEIGHT    
    tab={}

    xOffset,yOffset=12,15
    
    for x=1,w do
        tab[x]={}
        x1=x/w
        for y=1,h do
            tab[x][y]={}
            y1=y/h
            z=p:getValue(x1+xOffset,y1+yOffset,0)
            tab[x][y]=z
        end
    end 
       
    img=image(w,h)
    setContext(img)
    for x=1,w do
        for y=1,h do
            c=(tab[x][y]*255)//1
            img:set(x,y,c,c,c,255)
        end
    end
    setContext()    
end
    
function draw()
    background(0)
    sprite(img,WIDTH/2,HEIGHT/2)
end

Well that works, no doubt, thanks! A couple things I’m curious about: why are you dividing the X and Y by the width and height, respectively? Is that to generate decimal values? And what does the operator // do?

@UberGoober Dividing by w and h gives me a fraction between 0 and 1 for the width and height loops. Then I use that fraction and add it to the xOffset, yOffset to give me the fractional values from those values. The // is an integer divide so the color value was always an integer otherwise I got an error. The way you did your color values is ok, I was just in a hurry and didn’t try to add all of your code into this.

Here’s the lovely cloud generator thanks to @dave1707:


function setup()
    
    --set up initial variables
    p = craft.noise.perlin()
    perlinHigh, perlinLow = 0, 0
    perlinTable = {}
    colorTable = {}
    
    --get a Perlin z value for every x, y
    xOffset,yOffset=math.random(10), math.random(30)
    for x = 1, WIDTH do
        perlinTable[x] = {}
        x1 = x/WIDTH
        for y = 1, HEIGHT do
            perlinTable[x][y] = {}
            y1 = y/HEIGHT
            z = p:getValue(x1+xOffset, y1+yOffset, 0)
            z = math.abs(z)
            perlinTable[x][y] = z
            if z > perlinHigh then
                perlinHigh = z
            elseif z < perlinLow then
                perlinLow = z 
            end
        end
    end
    
    --turn the range of perlin values into a fraction of 255
    perlinRange = perlinHigh - perlinLow
    colorMultiplier = 255 / perlinRange
    
    --use that fraction to turn each value into a color value
    for x, yTable in ipairs(perlinTable) do
        colorTable[x] = {}
        for y, decimal in ipairs(yTable) do
            rgbVal = decimal * colorMultiplier
            newColor = color(rgbVal, rgbVal, rgbVal, rgbVal)
            colorTable[x][y] = newColor
        end
    end 
    
    --make an image
    perlinImage = image(WIDTH, HEIGHT)
    
    --draw each color to that image at the right place
    setContext(perlinImage)
    for x=1, WIDTH do
        for y=1, HEIGHT do
            perlinImage:set(x,y,colorTable[x][y])
        end
    end
    setContext()    

end

function draw()
    background(67, 172, 236)
    sprite(perlinImage, WIDTH/2, HEIGHT/2)
end

@dave1707 @UberGoober - from what I remember generating these images requires images to be squares, so that there is no difference between the lengths of the axes. I think that’s why ratioing the dimensions of the axes to 1 works.

If you want to make bigger areas I used to modify the tiles to match, at the borders, by either inverting them or programming to blend in at the overlaps (seamless tiles).

@UberGoober Here’s a version.

PS. Made changes, added parameters.

viewer.mode=STANDARD

function setup() 
    parameter.integer("xVal",-20,20,0)   
    parameter.integer("yVal",-20,20,0) 
    parameter.action("cloud",cloud)  
    p=craft.noise.perlin()
    cloud()
end

function cloud()   
    local tab={}
    local xOffset,yOffset=xVal,yVal  
    local min,max=0,0    
    for x=1,WIDTH do
        tab[x]={}
        local x1=x/WIDTH
        for y=1,HEIGHT do
            tab[x][y]={}
            local y1=y/HEIGHT
            local z=p:getValue(x1+xOffset,y1+yOffset,0)
            if z>max then
                max=z
            elseif z<min then
                min=z
            end
            tab[x][y]=z
        end
    end
    local col=256/(max-min)    
    img=image(WIDTH,HEIGHT)
    setContext(img)
    for x=1,WIDTH do
        for y=1,HEIGHT do
            local c=(tab[x][y]+math.abs(min))*col
            img:set(x,y,color(c,c,c,c))
        end
    end
    setContext() 
end
    
function draw()
    background(33, 140, 204)
    sprite(img,WIDTH/2,HEIGHT/2)
end

@UberGoober Here’s another version. This has a range parameter that will increase the cloud area from 1x1 to 10x10. If you just increase the range parameter by 1 value each time, you’ll notice that the image shrinks to the lower left corner as the range increases.

viewer.mode=STANDARD

function setup() 
    parameter.integer("xVal",-20,20,0)   
    parameter.integer("yVal",-20,20,0) 
    parameter.integer("range",1,10,1)
    parameter.action("cloud",cloud)  
    p=craft.noise.perlin()
    cloud()
end

function cloud()   
    local tab={}
    local xOffset,yOffset=xVal,yVal  
    local min,max=0,0    
    for x=1,WIDTH do
        tab[x]={}
        local x1=x/(WIDTH/range)
        for y=1,HEIGHT do
            tab[x][y]={}
            local y1=y/(HEIGHT/range)
            local z=p:getValue(x1+xOffset,y1+yOffset,0)
            if z>max then
                max=z
            elseif z<min then
                min=z
            end
            tab[x][y]=z
        end
    end
    local col=256/(max-min)    
    img=image(WIDTH,HEIGHT)
    setContext(img)
    for x=1,WIDTH do
        for y=1,HEIGHT do
            local c=(tab[x][y]+math.abs(min))*col
            img:set(x,y,color(c,c,c,c))
        end
    end
    setContext() 
end
    
function draw()
    background(33, 140, 204)
    sprite(img,WIDTH/2,HEIGHT/2)
end

@dave1707 these are great.

I noticed that even changing the X offset by one produces a dramatically different image, which surprises me, because I had assumed that gradual changes would result in gradual visual differences.

@UberGoober If you use my latest code and set the range to 10, when you change the x or y value by 1, you’ll see the image shift by 1/10 the image size. At a range of 1, the whole image is shifted by the whole screen size. I used to have some perlin code that covered a larger area so as you changed the x or y values, you would see the image scroll across the screen. I’m not sure where that is anymore.

It’s visible nice and smoothly when I use these parameters:


    parameter.number("xVal",-5,5,0, function()
        cloud()
    end)   
    parameter.number("yVal",-5,5,0, function()
        cloud()
    end)
    parameter.integer("range",1,10,1, function()
        cloud()
    end)

I wonder if the perlin calculations are fast enough to make that into an animation…